Blazor是微软为ASP.NET Core开发的前端框架,它能让开发者使用C#语言构建基于浏览器的交互式Web前端应用程序。使用C#开发前端的体验是非常特别的,这和传统的JavaScript/TypeScript前端技术栈有相似点但也有很大区别。本系列笔记我们将对Blazor的概念和使用方法进行学习,所有内容将基于最新的.NET8版本进行介绍。
注:学习该章节需要掌握ASP.NET Core WebAPI/MVC/SignalR章节中介绍的所有内容,ASP.NET Core中一些通用的部分包括依赖注入、配置框架、Razor模板语法等不会在这里重复介绍了,此外最好还要掌握现代前端开发相关知识,如果你不熟悉前端开发直接学习Blazor,一些概念可能难以理解。
Blazor有3种渲染模式,这和目前前端领域最火热的框架之一NextJS有些类似(但也并不完全等同),Blazor的3种渲染模式我们可以类比NextJS中的SSG、SSR和CSR渲染模式。
静态渲染模式:类似NextJS的SSG模式,Web内容完全由服务端静态生成,用户在页面上的操作不会直接和服务端C#代码发生交互。静态渲染模式的使用场景和Hexo之类的静态生成器比较类似,可用于那些不需要动态交互的内容呈现。与静态渲染模式相对的是交互式渲染模式,下面两种都属于交互式渲染。
BlazorServer模式:类似NextJS的SSR模式,Web内容是由服务端动态计算生成的,不过BlazorServer的组件状态是维护在服务端的,这又和JavaEE中的JSF有些类似,用户打开页面后会同时自动建立SignalR连接,组件状态的变化将自动通过SignalR反映到页面上,用户对页面组件的操作也会通过SignalR将页面组件状态保存在BlazorServer的服务端内存中。由于前端组件状态保存在远程服务器上,这种渲染模式带来了两个缺点,一是前后端交互频繁,在网速较低的情况下有明显延迟和卡顿感,体验较差,二是一旦网站访问量较高服务端内存开销将比较大。
WebAssembly模式:类似NextJS的CSR模式,该模式将创建纯前端工程,它可以完全脱离服务端在浏览器中运行。不过浏览器毕竟不能直接执行C#代码,因此这种模式会将工程以WebAssembly方式编译。WebAssembly模式的Blazor工程会在页面首次打开时加载一系列WASM运行时文件,因此首次打开页面时有一个较慢加载的过程,这也是该模式的一个缺点。
在.NET8中,我们其实还可以混合使用这些渲染模式实现更好的用户体验,这部分内容将在后续章节介绍。
了解了Blazor的3种渲染模式,其实我们也就大致知道了Blazor的优缺点。优点自然是能够使用强大的C#语言同时编写前端和后端工程,可以让开发者抛开大部分恼人的JavaScript代码,技术栈统一、开发效率高;Blazor框架设计的也是十分简单易用,而前端生态的React、Angular等则相对要“抽象”很多;当然,Blazor的缺点也很明显,前面我们介绍的BlazorServer模式和WebAssembly模式都有各自的“硬伤”,做不到JavaScript系列的现代前端框架那么完美。
此外,Blazor的生态也相对奇葩和小众,如果你要实现的功能比较简单还好(例如那些只有CRUD的2B项目),一旦要实现的功能较为复杂,可能还是绕不开JavaScript的API,这又额外需要Blazor和JavaScript进行互操作,引入了额外的麻烦,这也是使用Blazor不得不面对的困境。
根据微软官方文档,.NET8下的Blazor兼容最新版本的Google Chrome、Mozilla Firefox、Microsoft Edge和Apple Safari,不支持Internet Explorer。如果你还想保持网站的IE兼容性,则无法使用最新版本的Blazor。
在.NET8中,创建Blazor工程默认只有一个项目模板Blazor Web App
。
在创建的过程中有一个选项Interactive render mode
,选择None
表示静态渲染,Server
表示BlazorServer模式渲染,WebAssembly
表示WebAssembly模式渲染,Auto
则是BlazorServer模式和WebAssembly模式混合渲染。我们可以逐一尝试,看看默认生成的代码和浏览器中的表现具体有什么区别。
本系列笔记将先主要介绍BlazorServer模式的开发,WebAssembly模式和混合渲染模式将在后续章节补充介绍。
Visual Studio默认创建的Blazor工程目录结构如下。
|_ bin # 编译输出目录,不需要手动编辑
|_ obj # 编译的中间文件目录,不需要手动编辑
|_ Properties # 用于存放与项目运行和配置相关的文件
|_ launchSettings.json # 配置开发环境下的运行和调试行为,不会被发布到生产环境中
|_ Components # 前端页面和组件文件夹
|_ Layout # 页面基础框架文件夹,其中的组件在Routes.razor中被引入
|_ Pages # 页面文件夹,内部的组件被路由系统根据浏览器中的URL加载
|_ _Imports.razor # 名字固定,统一导入文件,包含应用程序中常用的命名空间和组件的导入
|_ App.razor # Blazor根组件,用于定义应用程序的路由和布局
|_ Routes.razor # 单独定义的路由组件,通常用于集中存放和管理路由配置
|_ wwwroot # 静态资源文件夹
|_ Program.cs # 工程入口文件
|_ appsettings.json # 工程配置文件
|_ appsettings.Development.json # 分环境的工程配置文件
|_ DemoBlazorServer.csproj # 工程描述文件
可以看到,BlazorServer工程也是一种ASP.NET Core工程,它们有相似的结构,只是Program.cs
中配置了不同的服务和中间件。Components
文件夹是Blazor独有的,其中使用定义了许多.razor
结尾的Blazor组件。
最后我们观察一下Program.cs
文件。
using Gacfox.DemoBlazorServer.Components;
var builder = WebApplication.CreateBuilder(args);
// 注入RazorComponents服务和交互式组件服务,允许BlazorServer应用使用交互式组件
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
}
// 启用静态文件服务
app.UseStaticFiles();
// 启用防CSRF攻击功能
app.UseAntiforgery();
// 映射Razor组件路由,设置根组件为App,并启用交互式服务器渲染模式
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
// 启动并运行应用程序
app.Run();
可以看到Program.cs
中我们注册了BlazorServer相关的服务,最后使用MapRazorComponents()
方法加载了Blazor根组件并启动交互式渲染模式,然后启动了应用程序。
我们这里编写一个简单的例子体验Blazor的使用,下面例子创建了一个页面,它是一个反复在红蓝之间切换的Hello, Blazor!
文本。
Components/Pages/Hello.razor
@page "/hello"
@rendermode InteractiveServer
<h3 style="@colorStyle">Hello, Blazor!</h3>
@code {
private string colorStyle = "color: red";
private Timer? _timer;
protected override void OnInitialized()
{
_timer = new Timer(ToggleColor, null, 0, 1000);
}
private void ToggleColor(object? state)
{
colorStyle = colorStyle == "color: red" ? "color: blue" : "color: red";
InvokeAsync(StateHasChanged);
}
public void Dispose()
{
_timer?.Dispose();
}
}
Blazor组件通常由三部分构成。第一部分是指令,@page
指定了页面路由,@rendermode
设置为InteractiveServer
指定该页面是一个交互模式的BlazorServer组件。第二部分是Razor模板,它在HTML内嵌入的Razor语法。第三部分是一个@code {}
块,其中包含了C#代码,colorStyle
是一个组件的变量,它绑定到了页面元素的style
属性上,用于控制颜色;_timer
是一个定时器,OnInitialized()
会在组件初始化时回调,代码中我们初始化了定时器并让其每隔1秒调用ToggleColor()
切换colorStyle
的值,这间接实现了页面元素颜色的修改;Dispose()
会在组件销毁时调用,我们在这里销毁了定时器。ToggleColor()
函数中还调用了InvokeAsync(StateHasChanged)
,StateHasChanged()
函数用于通知Blazor组件重新渲染,InvokeAsync()
是确保在异步操作中正确调度UI更新的必要方法,这能够避免线程安全问题。
BlazorSerever组件会在服务端运行,colorStyle
变量在服务端内存里被不断修改,这一修改会通过SignalR将状态告知浏览器,浏览器就会最终渲染出页面元素颜色不断跳动的效果。