本文从比较高的位置俯瞰一下 .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 框架其实都在做两件核心的事

  1. 指挥 DOM 去渲染视觉样式(调用 DOM API)。这其中框架开发者不可避免的要去学习 HTML 和 CSS 的相关知识
  2. 响应用户输入,然后完成一些交互计算,再根据交互计算去高效的更新视觉样式。而交互计算又分为两部分
    1. 直接在浏览器中,用 JavaScript 做的相关计算
    2. 浏览器没法算的,则是通过通信方式(HTTP 请求),去调用服务端 API

简化一下,其实就是做了三件事:

  1. 调用 DOM API
  2. 使用浏览器能运行的代码,来完成交互计算
  3. 使用 HTTP 协议调用服务端 API

Blazor 其实也是在做这三件事,但 Blazor 有两种做法:

方法一 : Blazor WebAssembly

  1. 把 C#编译成 WebAssembly,然后放在浏览器上去执行:去调用 DOM API 渲染视觉,以及完成交互计算
  2. 依然使用 HTTP 协议调用服务端 API

这种做法其实和 React/Angular/Vue 的做法是一样的,唯一的区别就是为了使用 C#,使用了 WebAssembly 来做中间层。如下图所示:

代价也是有的,就是 WebAssembly 虽然干其它事很牛逼,但调用 DOM API 时,只能间接的去使用 interop 方式去调用 JavaScript 的 DOM API。并且初次载入页面时,慢到令人窒息。

方法二 : Blazor Server

除了第一次访问,后续访问均摒弃 HTTP 协议,使用 WebSocket 在服务端和浏览器之间维持一个长连接,浏览器上的代码只做一件事:根据 WebSocket 消息来调用 DOM API。

  1. 当需要更新 DOM 时,服务端向浏览器发送二进制消息。浏览器按指令调用 DOM API 更新视觉就行
  2. 当有用户输入,需要进行交互计算的时候,浏览器不算,而是把用户输入以二进制发送给服务端,服务端来做交互计算,然后回传计算结果,浏览器只管更新 DOM
  3. 交互计算代码需要访问业务逻辑代码时,就不存在所谓的“调用 API”了,而是直接在服务端调用函数即可

如下图所示:

这种做法非常激进,你以为你点开的网页是网页,是 B/S 架构的,其实是一个需要维持长连接的 C/S 构架的应用:浏览器只是一个样式渲染器而已,用户本质上使用的是一个远程应用,所有计算,无论大小,都运行在服务端。

总结

  1. 边缘 Web 开发技术,不流行,缺点相当难以忽视
  2. 优点也相当难以忽视:使用 C#,而不是 JavaScript。配合.NET 服务端技术,一人全栈相当舒服。

    你要认识到,当我们说不使用 JavaScript时,不光是抛开了 JS 或 TS,更重要的是抛开了传统前端的所有工具链: node, webpack, babel 等等等等,这些东西对于一个专业的前端开发工程师来说不算什么,但对于一个只是想攒一个小应用,小网站,小博客的个人来说,这些玩意是巨大的心智负担。
  3. 但你依然无法逃脱 HTML 和 CSS
  4. 目前没有比较好的,有说服力,广为人知的商业互联网产品使用 Blazor 技术栈

1.2 Blazor 中的组件:Razor Components

Blazor 其它 UI 框架一样,也是分治于“组件”的思想,一个组件可以是一个大到页面,小到对话框、按钮、输入表单这样的 UI 元素。

上面介绍了 Blazor 有两种工作模式,Blazor 中的组件比较神奇的点在于:组件和具体的工作方式是无关的。配套的工具链会帮你去处理两种工作方式之间的差异,但对于框架开发者来说,组件就是组件,开发组件的时候不需要考虑是 Blazor Server 还是 Blazor WebAssembly。

从代码角度来看,React 中的组件是一个 JS 函数,而 Blazor 中的组件则是一个 C#类。在组件内部,要写上下面的东西:

  1. 组件内部要写上 UI 渲染逻辑
  2. 组件应当在必要的时候处理用户的输入,即处理“事件”
  3. 组件应当是可嵌套的,可复用的
  4. 组件可以被打成一个类库,或者打成一个 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的书写规范以及工作原理,但我们已经能大致猜出来它的工作流程了:在运行时,这个特殊的类会被实例化,它内部的两样东西:

  1. 它要控制页面如何渲染,或者换句话说,这个类内部最终还是要输出一种能被浏览器直接渲染的东西的,我们暂时可以认为这个类内部有一个方法可以输出 HTML+CSS 代码(但实际并不是这样
  2. 它本身可以拥有一些属性与方法来表达业务逻辑,这些属性与方法可以在代码中用@属性@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.csStartup.cs看的话,它与普通的 ASP 项目基本没什么区别,核心的点在Start.cs中的ConfigureServicesConfigure两个方法中:

再介绍一下其它目录:

  1. Program.cs, Startup.cs : 经典的 ASP .Net Core 项目入口
  2. appsettings.jsonappsettings.Development.json : 项目配置文件
  3. HelloBlazorServer.csproj : 项目编译脚本
  4. _Imports.razor : 相当于所有*.razor文件的公共头,点开看,里面全是@using指令
  5. App.razor : 这是整个项目的 UI 根组件,它内部其实只写了一个<Router>元素,逻辑是:如果路由匹配成功了,则去把匹配 UI 组件包在MainLayout组件里面进行渲染,否则将一个提示字符串包在MainLayout组件里渲染出来

  6. wwwroot 目录:里面包含了静态资源,与普通 的 ASP .Net Core 项目一样。需要注意的是,默认创建的项目中,这里面给你自带了bootstrap样式库和open-iconic图标库
  7. Properties 目录:与经典的 ASP 项目一样,里面一个launchSettings.json描述了一些配置项
  8. Data 目录:这里面包含两个类,一个是默认自带的WeatherForecast.cs,是一个 Model 类,另外一个是WeatherForecastService.cs类,里面写着真正的“业务逻辑”,并且这个SeatherForecastService还被在Startup.cs中注册成了一个全局单例的服务
  9. Shared目录:这里面包含了一些自带的 Blazor 组件,这些组件都是被广泛使用的公共组件,所以被放在这个目录中。你会注意到MainLayoutNavMenu组件除了本身的*.razor定义文件,还有单独的描述样式的css文件
  10. Pages目录:这里就包含了所有的页面 Blazor 组件,如果你要开发一个网站的话,这里应该是你最常工作的地方。这个目录中除了Counter, FetchDataIndex三个 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中,有两个显著的区别

  1. 没有了Data目录,也没有了WeatherForecast.csWeatherForecastService.cs
  2. FetchData.razor
    1. 就地定义了结构体WeatherForecast
    2. 数据是从一个静态的,托管在服务端上的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开发框架的更多相关文章

  1. python Web开发框架-Django (1)

    以前用web.py(另外一款轻量级web开发框架)做一个监控管理平台,没有做特别的记录就不好拾起来.最近做一个日志聚合系统,使用的是django,这次就记下来,方便查询. Django是一个高效的we ...

  2. 常见Python的Web开发框架

    在今天,Python里有很多开发框架用来帮助你轻松创建web应用.web开发框架存在的意义就在于可以快速便捷的构建应用,而不用去在意那些没必要的技术细节(协议.报文.数据结构). 到2020年为止,基 ...

  3. 一个web开发框架

    一个web开发框架 怎么才能成为一名架构师?需要具备哪些条件? 作为一名码农我迫切希望自己成为一个比较合格的web架构师,昨晚心血来潮小弟花了4个小时的时间整了个简易的web开发框架,由于第一次搭建框 ...

  4. 我的第一个python web开发框架(3)——怎么开始?

    小白与小美公司经过几次接触商谈,好不容易将外包签订了下来,准备开始大干一场.不过小白由于没有太多的项目经验,学过python懂得python的基本语法,在公司跟着大家做过简单功能,另外还会一些HTML ...

  5. 我的第一个python web开发框架(14)——后台管理系统登录功能

    接下来正式进入网站的功能开发.要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX. 在进行接口开发之前, ...

  6. 我的第一个python web开发框架(41)——总结

    我的第一个python web开发框架系列博文从17年6.7月份开始写(存了近十章稿留到9月份才开始发布),到今天结束,一年多时间,想想真不容易啊. 整个过程断断续续,中间有段时间由于工作繁忙停了好长 ...

  7. 分享一个基于 Node.js 的 Web 开发框架 - Nokitjs

    简介 Nokit 是一个简单易用的基于 Nodejs 的 Web 开发框架,默认提供了 MVC / NSP / RESTful 等支持,并提供对应项目模板.管理工具. 资源 GitHub https: ...

  8. 我的第一个web开发框架

    怎么才能成为一名架构师?需要具备哪些条件? 作为一名码农我迫切希望自己成为一个比较合格的web架构师,昨晚心血来潮小弟花了4个小时的时间整了个简易的web开发框架,本着开源的精神做个分享,希望和更多的 ...

  9. 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用

    由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...

随机推荐

  1. 实现redis哨兵,模拟master故障场景

    由于主从架构无法实现master和slave角色的自动切换,所以在发送master节点宕机时,redis主从复制无法实现自动的故障转移,即将slave 自动提升为新的master.因此,需要配置哨兵来 ...

  2. Maven获取resources的文件路径、读取resources的文件

    路径问题一切要看编译后的文件路径 比如,源文件路径是: 而编译后的文件路径为: 也就是说,resources文件夹下的文件在编译后,都是为根目录,这种情况下,比如我要读取resources 文件夹下的 ...

  3. 基于Oracle数据库登陆界面及功能实现 Java版

    首先要在Oracle数据库创建表文件,包括建立表头以及关键字(唯一标识符),此次程序所用的表名称为SW_USER_INFO,表头有UNAME.UKEY.USEX等,关键字为UCC,然后添加一条记录,用 ...

  4. Mysql的基本操作知识

    顺带,我会在后面把我整理的一整套CSS3,PHP,MYSQL的开发的笔记打包放到百度云,有需要可以直接去百度云下载,这样以后你们开发就可以直接翻笔记不用百度搜那么麻烦了.  笔记链接:http://p ...

  5. Solution -「NOI 模拟赛」出题人

    \(\mathcal{Description}\)   给定 \(\{a_n\}\),求一个 \(\{b_{n-1}\}\),使得 \(\forall x\in\{a_n\},\exists i,j\ ...

  6. Redis 中常见的集群部署方案

    Redis 的高可用集群 前言 几种常用的集群方案 主从集群模式 全量同步 增量同步 哨兵机制 什么是哨兵机制 如何保证选主的准确性 如何选主 选举主节点的规则 哨兵进行主节点切换 切片集群 Redi ...

  7. docker迁移工作目录

    yum安装的docker 工作目录在系统盘,迁移到数据盘 首先需要停止docker服务 systemctl stop docker.service 通过命令df -h 先去看下磁盘大概的情况,找一个大 ...

  8. Java高频面试题70道

    1.作用域public,private,protected,以及不写时的区别? 答:区别如下: 作用域  当前类 同一packag 子孙类 其他package public √ √ √ √ prote ...

  9. excel制作折线图太麻烦?试试这些折线图在线生成工具

    折线图是以折线的上升或下降来表示统计数量的增减变化的统计图,叫作折线统计图.用折线的起伏表示数据的增减变化情况,不仅可以表示数量的多少,而且可以反映数据的增减变化情况.并且折线图也是目前最方便的一种统 ...

  10. BI和报表的区别在哪?还傻傻分不清楚吗?

    1.面向人群不同 报表主要针对IT人员,或者专业的报表开发人员.用户需要具备一定的编程知识.制作一张报表通常需要先由业务人员提出需求,再由IT部门人员取数制作报表. BI主要面向业务人员和管理人员.B ...