Blazor 001 : 一个激进的Web开发框架
本文从比较高的位置俯瞰一下 .NET Blazor 技术方向,主要是给大家介绍一下“什么是 Blazor”
文章后半部分会给出一个 Blazor 中的 Hello World 示例
1. 概览
1.1 什么是 Blazor?
Blazor 是一个.NET 全家桶里的一个 Web UI 开发框架,简单来说,你可以把它理解为 React/Angular/Vue 的替代品:这是一个用于开发 Web UI 的框架。
从框架使用者的角度来说,最直接的使用体验,就是使用 C#替换掉了 JavaScript 或 TypeScript。
那除了使用 C#而非 JS这一点外, Blazor 和如今流行的三大前端框架 React/Angular/Vue 相比较,还有什么差异呢?
在聊这些差异之前,我们需要先明确一个基础知识点:UI = 视觉 + 交互。这个等式并不严谨,但你应当明白我说的是什么意思,所有前端 UI 框架其实都在做两件核心的事
- 指挥 DOM 去渲染视觉样式(调用 DOM API)。这其中框架开发者不可避免的要去学习 HTML 和 CSS 的相关知识
- 响应用户输入,然后完成一些交互计算,再根据交互计算去高效的更新视觉样式。而交互计算又分为两部分
- 直接在浏览器中,用 JavaScript 做的相关计算
- 浏览器没法算的,则是通过通信方式(HTTP 请求),去调用服务端 API
简化一下,其实就是做了三件事:
- 调用 DOM API
- 使用浏览器能运行的代码,来完成交互计算
- 使用 HTTP 协议调用服务端 API
Blazor 其实也是在做这三件事,但 Blazor 有两种做法:
方法一 : Blazor WebAssembly
- 把 C#编译成 WebAssembly,然后放在浏览器上去执行:去调用 DOM API 渲染视觉,以及完成交互计算
- 依然使用 HTTP 协议调用服务端 API
这种做法其实和 React/Angular/Vue 的做法是一样的,唯一的区别就是为了使用 C#,使用了 WebAssembly 来做中间层。如下图所示:
代价也是有的,就是 WebAssembly 虽然干其它事很牛逼,但调用 DOM API 时,只能间接的去使用 interop 方式去调用 JavaScript 的 DOM API。并且初次载入页面时,慢到令人窒息。
方法二 : Blazor Server
除了第一次访问,后续访问均摒弃 HTTP 协议,使用 WebSocket 在服务端和浏览器之间维持一个长连接,浏览器上的代码只做一件事:根据 WebSocket 消息来调用 DOM API。
- 当需要更新 DOM 时,服务端向浏览器发送二进制消息。浏览器按指令调用 DOM API 更新视觉就行
- 当有用户输入,需要进行交互计算的时候,浏览器不算,而是把用户输入以二进制发送给服务端,服务端来做交互计算,然后回传计算结果,浏览器只管更新 DOM
- 当交互计算代码需要访问业务逻辑代码时,就不存在所谓的“调用 API”了,而是直接在服务端调用函数即可
如下图所示:
这种做法非常激进,你以为你点开的网页是网页,是 B/S 架构的,其实是一个需要维持长连接的 C/S 构架的应用:浏览器只是一个样式渲染器而已,用户本质上使用的是一个远程应用,所有计算,无论大小,都运行在服务端。
总结
- 边缘 Web 开发技术,不流行,缺点相当难以忽视
- 优点也相当难以忽视:使用 C#,而不是 JavaScript。配合.NET 服务端技术,一人全栈相当舒服。
你要认识到,当我们说不使用 JavaScript时,不光是抛开了 JS 或 TS,更重要的是抛开了传统前端的所有工具链: node, webpack, babel 等等等等,这些东西对于一个专业的前端开发工程师来说不算什么,但对于一个只是想攒一个小应用,小网站,小博客的个人来说,这些玩意是巨大的心智负担。 - 但你依然无法逃脱 HTML 和 CSS
- 目前没有比较好的,有说服力,广为人知的商业互联网产品使用 Blazor 技术栈
1.2 Blazor 中的组件:Razor Components
Blazor 其它 UI 框架一样,也是分治于“组件”的思想,一个组件可以是一个大到页面,小到对话框、按钮、输入表单这样的 UI 元素。
上面介绍了 Blazor 有两种工作模式,Blazor 中的组件比较神奇的点在于:组件和具体的工作方式是无关的。配套的工具链会帮你去处理两种工作方式之间的差异,但对于框架开发者来说,组件就是组件,开发组件的时候不需要考虑是 Blazor Server 还是 Blazor WebAssembly。
从代码角度来看,React 中的组件是一个 JS 函数,而 Blazor 中的组件则是一个 C#类。在组件内部,要写上下面的东西:
- 组件内部要写上 UI 渲染逻辑
- 组件应当在必要的时候处理用户的输入,即处理“事件”
- 组件应当是可嵌套的,可复用的
- 组件可以被打成一个类库,或者打成一个 NuGet 包分发在互联网平台上,供他人“借用”
虽然本质上,每个 Blazor 组件都是一个 C#的类,但如果全然用public class xxxx
去写这样的类,是非常不直观的,于是 Blazor 的解决方式有点类似于上古时期的 JSP 技术:给你一套四不像的标记语言,让你去写一种看起来像是 HTML 但又不是 HTML 的东西,然后这个东西再被工具链在编译的时候转换成*.cs
文件,再编译成一个.NET 类
这种四不像的文件以*.razor
结尾,在这种文件里,你可以写 HTML,可以写内嵌的 CSS,然后还可以用一套别扭的语法去再写一些 C#代码,这个玩意大概长下面这样:
<div class="card" style="width:22rem">
<div class="card-body">
<h3 class="card-title">@Title</h3>
<p class="card-text">@ChildContent</p>
<button @onclick="OnYes">Yes!</button>
</div>
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string Title { get; set; }
private void OnYes()
{
Console.WriteLine("Write to the console in C#! 'Yes' button selected.");
}
}
在上面的代码示例中,你可以理解为,整个@code {xxx}
包起来的区域,相当于你写了一个partial class
,你在里面可以定义属性,定义方法,定义字段等。这样理解起来,OnYes
就是一个方法,而在上面的类 HTML 代码中,使用了@onclick="OnYes"
这种奇怪的写法,将一个按钮的点击回调,绑定到了一个 C#方法上,而且用@Title
和@ChildContent
这样奇怪的语法,将类中的属性引用到了 HTML 中。
那么虽然目前我们并不明白所有有关*.razor
的书写规范以及工作原理,但我们已经能大致猜出来它的工作流程了:在运行时,这个特殊的类会被实例化,它内部的两样东西:
- 它要控制页面如何渲染,或者换句话说,这个类内部最终还是要输出一种能被浏览器直接渲染的东西的,我们暂时可以认为这个类内部有一个方法可以输出 HTML+CSS 代码(但实际并不是这样
- 它本身可以拥有一些属性与方法来表达业务逻辑,这些属性与方法可以在代码中用
@属性
或@onclick=方法
这种语法被我们使用
假如上面这个代码文件叫做Dialog.razor
的话,按照组件应当被复用嵌套的要求,这个组件可以被嵌套在另外一个组件里,比如我们下面将它嵌套在一个叫Index.razor
的文件中:
@page "/"
<h1>Hello, world!</h1>
<p>
Welcome to your new app.
</p>
<Dialog Title="Learn More">
Do you want to <i>learn more</i> about Blazor?
</Dialog>
这时,我们就可以理解为,在最终的编译出来的.NET 类中,当Index
类要输出渲染时,它会实例化一个Dialog
类的实例,并且将dialog.Title
的值设置为Learn More
, 将diaglog.ChildContent
的值设置为Do you want to <i>learn more</i> about Blazor?
,然后在必要的位置将两个diaglog
渲染出来
如果 DOM 是渲染的目标的话,那么这个Index
的实例,会被渲染成对应的一颗 DOM 树,而其中的一颗子树,其实是Dialog
的样子
2. Hello Blazor
这一节,我们将创建两个项目,分别先快速体验一下 Blazor 技术。到今天为止(2022-03-15),.NET 的版本号已经到 6 了,但我们这里依然使用 5.0 版本做演示,原因嘛,没有原因,就是倔强。
另外,显然,你需要在电脑上安装 .Net Core SDK,建议你先安装个版本号为 5 开头的与本文同步。。不要紧张,.NetCore SDK 可以同时安装 N 多个版本,可以安全共存,最终,在命令行中输入dotnet --list-sdks
时,你要能找到一个以 5 开头的版本,如下:
2.1 Hello Blazor Server
命令: dotnet new blazorserver -o HelloBlazorServer --framework 'net5.0'
默认创建出了这样的一个项目:
这其实是一个非常典型的 ASP 项目,如果你点开Program.cs
和Startup.cs
看的话,它与普通的 ASP 项目基本没什么区别,核心的点在Start.cs
中的ConfigureServices
和Configure
两个方法中:
再介绍一下其它目录:
Program.cs
,Startup.cs
: 经典的 ASP .Net Core 项目入口appsettings.json
和appsettings.Development.json
: 项目配置文件HelloBlazorServer.csproj
: 项目编译脚本_Imports.razor
: 相当于所有*.razor
文件的公共头,点开看,里面全是@using
指令App.razor
: 这是整个项目的 UI 根组件,它内部其实只写了一个<Router>
元素,逻辑是:如果路由匹配成功了,则去把匹配 UI 组件包在MainLayout
组件里面进行渲染,否则将一个提示字符串包在MainLayout
组件里渲染出来
wwwroot
目录:里面包含了静态资源,与普通 的 ASP .Net Core 项目一样。需要注意的是,默认创建的项目中,这里面给你自带了bootstrap
样式库和open-iconic
图标库Properties
目录:与经典的 ASP 项目一样,里面一个launchSettings.json
描述了一些配置项Data
目录:这里面包含两个类,一个是默认自带的WeatherForecast.cs
,是一个 Model 类,另外一个是WeatherForecastService.cs
类,里面写着真正的“业务逻辑”,并且这个SeatherForecastService
还被在Startup.cs
中注册成了一个全局单例的服务Shared
目录:这里面包含了一些自带的 Blazor 组件,这些组件都是被广泛使用的公共组件,所以被放在这个目录中。你会注意到MainLayout
和NavMenu
组件除了本身的*.razor
定义文件,还有单独的描述样式的css
文件Pages
目录:这里就包含了所有的页面 Blazor 组件,如果你要开发一个网站的话,这里应该是你最常工作的地方。这个目录中除了Counter
,FetchData
和Index
三个 Blazor 组件,还有_Host.cshtml
,Error.cshtml
,Error.cshtml.cs
三个文件,你可以暂时理解为,*.cshtml
和*.cshtml.cs
是 Blazor 组件的传统写法
我们再打包一下,观察一个 Blazor Server App 在打包后会变成什么,我们将当前这个项目打包到 Linux 平台上,然后观察一下输出:
可以看到,整个应用其实被打包成了一个典型的 ASP 应用:你只需要将这个目录部署在某台 Linux 机器上,然后运行./HelloBlazorServer
就行了。。你所定义的所有 Blazor 组件都被编译进了HelloBlazorServer.dll
这个 Assembly 中
接下来我们再来验证一下东西:我们要来验证,在 Blazor Server App 中,所有的计算都是在服务端完成的。默认创建的这个 App 有个页面,在本地启动的话地址是https://localhost:5001/counter
,这个页面上有一个计数器,按一下按钮,计数器就+1
通过查看Pages/Counter.razor
我们得知,这个计数器中的数值,其实就是Counter
类中的一个属性,每次点击按钮,其实调用的是Counter
实例下的IncrementCount()
方法
按照上面我们介绍 Blazor Server App 运行模式的说法,那么每次客户端用户在浏览器中点击这个按钮时,其实都会发送一个网络消息给服务端,然后服务端去执行counter.IncrementCount()
,执行结束后,服务端的counter
实例去分析 UI 上有哪些地方需要更新,然后再通过网络消息,指挥浏览器更新页面上渲染的数字。
如果这套理论是正确的,那意味着每次用户点击这个按钮,背后都会有数据在浏览器与服务端之间来回传递。我们用dotnet run
命令将整个项目在本地启动起来,然后打开浏览器,按 F12 打开调试控制台,切换到网络选项卡,然后刷新地址去访问这个计数器页面,我们会看到一个奇怪的网络请求:
这其实是一个全双工的 WebSocket 连接,点进去,切换到消息
选项卡,然后随着你每一次点击页面上的按钮,你都会看到有二进制数据在背后一来一回
2.2 Hello Blazor WebAssembly
命令: dotnet new blazorwasm -o HelloBlazorWASM --framework 'net5.0'
创建出这样一个项目:
这个项目,就和 ASP 没关系了,我们打开它的Program.cs
去看一眼,如果你对.NET 稍微熟悉的话,你就会立即明白:这个东西,不正常,这个东西,不寻常:
namespace HelloBlazorWASM
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}
}
}
这个程序入口创建了一个WebAssemblyHost
,然后把这个 Host 运行了起来。而其实呢,这个WebAssemblyHost
就是个静态的 WebServer 而已,跟你用create-react-app
创建一个 react 项目,然后npm start
起来的原理是差不多一样的。
另外,在Blazor Server
的例子中,FetchData
页面的数据,是通过在服务端随机生成的,服务端的逻辑写在WeatherForecastService.cs
中。而现在在Blazor WebAssembly
中,有两个显著的区别
- 没有了
Data
目录,也没有了WeatherForecast.cs
和WeatherForecastService.cs
- 在
FetchData.razor
中- 就地定义了结构体
WeatherForecast
- 数据是从一个静态的,托管在服务端上的
json
文件中获取的
- 就地定义了结构体
@page "/fetchdata"
@inject HttpClient Http
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public string Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
再结合我们上面讲的Blazor WebAssembly
的工作方式,我们就能得出一个结论:在这个样例Blazor WebAssembly
程序中,所有的计算负载,其实都是运行在客户端的浏览器上的。
服务端纯粹就是一个静态文件托管 Web Server,然后我们把这个项目打包一下,看看它长什么样子:
打包后这玩意就长这样:
其实所有的东西都在那个wwwroot
目录里,而外边那个web.config
,其实是写给 IIS 看的。
看到没?这玩意纯粹就是个前端项目!跟webpack
后的前端项目不能说一模一样吧,但至少是有异曲同工之妙,把整个dist
目录现在随便放在一个 Nginx 里托管起来,它就能跑起来。而我们在Program.cs
中看到的所谓WebAssemblyHost
,其实就他妈是一个 Kestrel Server,用来托管静态文件而已
你不信?行,我下载一个 Windows 版本的 Nginx,给你跑一下你就信了,我把上面的wwwroot
目录拷到 Nginx 目录下的HelloBlazorWASM
目录中,然后把 Nginx 的配置文件改成下面这样:
# ...
server {
listen 9438;
server_name localhost;
location / {
root HelloBlazorWASM/wwwroot;
index index.html index.htm;
}
# ...
}
# ...
然后给你跑起来,你看,一点毛病没有!
2.3 再看 Blazor 的优缺点
通过上面两个例子,我们对 Blazor 到底是什么有了一个更直观的认识,无论是 Blazor Server 还是 Blazor Webassembly,其实都不会革现在流行的前端框架的命,整个 Blazor 技术的优势和缺陷都相当激进,它的应用范围也相当受限。
优势主要来源于 C#和 .NET 工具链,如果你对 Azure 熟悉的话,也会知道微软在云这方面,真的是给 .NET 做了无缝对接。从感观上来说,Blazor 的技术,适合于个人与小团队创业者,做一些负载有限的 Web 应用。
缺陷处十分明显,对于 Blazor Server 来说,一直需要客户端浏览器通过SingalR
与服务端建立一个 WebSocket 连接,所有计算都在服务端,长连接还一直得保持,并且,每新开一个 Tab,就相当于新开了一个连接,性能问题十分堪忧。甚至我们不能说这是一个 Web 应用,这本质上其实是一个以浏览器为客户端的远程应用
在 Blazor WebAssembly 这边,你也看到了,即便是我们创建的样例程序 ,在加载时都非常能明显的看到一个Loading
字样,如果你再细心的去翻浏览器的调试面板的话,你会看到这个网页啥啥干不干,先给你加载个 70 多 KB 的blazor.webassembly.js
,再给你加载一个 200 多 KB 的dotnet.5.0.12.js
,你品,你细品。再网页点开了,业务逻辑计算代码被编成了 WebAssembly,UI 更新还得脱裤子放屁间接调 JS 去操控 DOM,你品,你细品。
但是,话说回来,话说说说回来,各位,扪心自问一下,如果一个 Web 产品,做到了峰值并发上万,规模得有多大?而这样的数据并发规模,我不知道你的后台业务逻辑得有多复杂,即使复杂的要死,说难听点,也就是加几台机器的问题,瓶颈如果有,也一定是在存储层。
Blazor 生态第二大的问题是:没有 UI 库,没有一个类似于 ant-d 的库可以用,这是一个非常大的问题。对于创业团队或者个人开发者来说,这个问题要比什么狗屁性能问题大一万倍。
Blazor 001 : 一个激进的Web开发框架的更多相关文章
- python Web开发框架-Django (1)
以前用web.py(另外一款轻量级web开发框架)做一个监控管理平台,没有做特别的记录就不好拾起来.最近做一个日志聚合系统,使用的是django,这次就记下来,方便查询. Django是一个高效的we ...
- 常见Python的Web开发框架
在今天,Python里有很多开发框架用来帮助你轻松创建web应用.web开发框架存在的意义就在于可以快速便捷的构建应用,而不用去在意那些没必要的技术细节(协议.报文.数据结构). 到2020年为止,基 ...
- 一个web开发框架
一个web开发框架 怎么才能成为一名架构师?需要具备哪些条件? 作为一名码农我迫切希望自己成为一个比较合格的web架构师,昨晚心血来潮小弟花了4个小时的时间整了个简易的web开发框架,由于第一次搭建框 ...
- 我的第一个python web开发框架(3)——怎么开始?
小白与小美公司经过几次接触商谈,好不容易将外包签订了下来,准备开始大干一场.不过小白由于没有太多的项目经验,学过python懂得python的基本语法,在公司跟着大家做过简单功能,另外还会一些HTML ...
- 我的第一个python web开发框架(14)——后台管理系统登录功能
接下来正式进入网站的功能开发.要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX. 在进行接口开发之前, ...
- 我的第一个python web开发框架(41)——总结
我的第一个python web开发框架系列博文从17年6.7月份开始写(存了近十章稿留到9月份才开始发布),到今天结束,一年多时间,想想真不容易啊. 整个过程断断续续,中间有段时间由于工作繁忙停了好长 ...
- 分享一个基于 Node.js 的 Web 开发框架 - Nokitjs
简介 Nokit 是一个简单易用的基于 Nodejs 的 Web 开发框架,默认提供了 MVC / NSP / RESTful 等支持,并提供对应项目模板.管理工具. 资源 GitHub https: ...
- 我的第一个web开发框架
怎么才能成为一名架构师?需要具备哪些条件? 作为一名码农我迫切希望自己成为一个比较合格的web架构师,昨晚心血来潮小弟花了4个小时的时间整了个简易的web开发框架,本着开源的精神做个分享,希望和更多的 ...
- 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用
由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...
随机推荐
- 实现redis哨兵,模拟master故障场景
由于主从架构无法实现master和slave角色的自动切换,所以在发送master节点宕机时,redis主从复制无法实现自动的故障转移,即将slave 自动提升为新的master.因此,需要配置哨兵来 ...
- Maven获取resources的文件路径、读取resources的文件
路径问题一切要看编译后的文件路径 比如,源文件路径是: 而编译后的文件路径为: 也就是说,resources文件夹下的文件在编译后,都是为根目录,这种情况下,比如我要读取resources 文件夹下的 ...
- 基于Oracle数据库登陆界面及功能实现 Java版
首先要在Oracle数据库创建表文件,包括建立表头以及关键字(唯一标识符),此次程序所用的表名称为SW_USER_INFO,表头有UNAME.UKEY.USEX等,关键字为UCC,然后添加一条记录,用 ...
- Mysql的基本操作知识
顺带,我会在后面把我整理的一整套CSS3,PHP,MYSQL的开发的笔记打包放到百度云,有需要可以直接去百度云下载,这样以后你们开发就可以直接翻笔记不用百度搜那么麻烦了. 笔记链接:http://p ...
- Solution -「NOI 模拟赛」出题人
\(\mathcal{Description}\) 给定 \(\{a_n\}\),求一个 \(\{b_{n-1}\}\),使得 \(\forall x\in\{a_n\},\exists i,j\ ...
- Redis 中常见的集群部署方案
Redis 的高可用集群 前言 几种常用的集群方案 主从集群模式 全量同步 增量同步 哨兵机制 什么是哨兵机制 如何保证选主的准确性 如何选主 选举主节点的规则 哨兵进行主节点切换 切片集群 Redi ...
- docker迁移工作目录
yum安装的docker 工作目录在系统盘,迁移到数据盘 首先需要停止docker服务 systemctl stop docker.service 通过命令df -h 先去看下磁盘大概的情况,找一个大 ...
- Java高频面试题70道
1.作用域public,private,protected,以及不写时的区别? 答:区别如下: 作用域 当前类 同一packag 子孙类 其他package public √ √ √ √ prote ...
- excel制作折线图太麻烦?试试这些折线图在线生成工具
折线图是以折线的上升或下降来表示统计数量的增减变化的统计图,叫作折线统计图.用折线的起伏表示数据的增减变化情况,不仅可以表示数量的多少,而且可以反映数据的增减变化情况.并且折线图也是目前最方便的一种统 ...
- BI和报表的区别在哪?还傻傻分不清楚吗?
1.面向人群不同 报表主要针对IT人员,或者专业的报表开发人员.用户需要具备一定的编程知识.制作一张报表通常需要先由业务人员提出需求,再由IT部门人员取数制作报表. BI主要面向业务人员和管理人员.B ...