探索.NET 8中的ASP.NET Core Blazor:全堆栈Web应用的新特性
太阳作者太阳
原创内容采用 CC-4.0 协议发布,转载请注明出处
.netBlazorASP.NET CoreWeb

这两天微软正在发布 .Net 8 ,我也在持续关注着,这段时间各种事情忙着,从 .Net Preview 7 只后就没怎么关注后续的 .Net 发布进程了。 直到这两天 Blazor 全堆栈 Web 让我产生了兴趣, 在 .Net 6 的时候我就尝试过使用 Blazor WebAssembly 写过一个后台应用当时用的还是 Ant Design Blazor 的 UI组件, 如果是编写后台应用不需要考虑 SEO 的话 Blazor WebAssembly 也算勉强挤进了 Angular,Vue,React 等单页前端框架的行列,只是有一个问题在 .Net 6 中 Blazor WebAssembly 是一个单独的项目,前端加载依赖一个页面或者一个路径直接整个加载进去,无法和 Asp.Net Core Razor 页面“集成” 或者说在开发时能够感受到两套代码那种完全割裂的感觉。似乎在 ASP.NET Core 8 Blazor 中解决了这个问题。

ASP.NET Core 8 Blazor 中呈现模式

参考 ASP.NET Core Blazor 呈现模式 文档中介绍了三种呈现方式

  • Static 静态呈现 其实就是Asp.Net Core Razor 页面
  • Interactive Server 服务器交互式呈现 使用 SignalR
  • Interactive WebAssembly 在浏览器中运行C# 编译的 WebAssembly 进行渲染
  • Interactive Auto 下载 Blazor 捆绑包后,首先使用 Blazor Server 进行交互式客户端渲染,然后在后续访问时使用 WebAssembly,加快对于交互式应用加快首屏加载的速度。

使用过一段时间的 Blazor, 在 .Net 6 中我始终没有感觉到 Interactive Server 服务器呈现模式的使用价值,真心想问客户端不支持WebAssembly是真实存在的吗在企业内部应用的场景下?,似乎在那个时候 Asp.Net Core 几种渲染模式也是混乱的,页面都有 cshtml,razor 两种格式。并且是单独的两个项目加载只是通过链接的方式跳转过去。

在 ASP.NET Core 8 Blazor 中这发生了很明显的变化,在更新了最新版本的 Visual Studio 后我创建了一个 Visual Studio Blazor Web App 示例项目如图 Visual Studio Blazor Web App

以及创建后的示例项目结构如图所示 Visual Studio Blazor Web App Code

惊奇的发现 BlazorApp.Client(Blazor WebAssembly呈现模式)需要定义的 App.razor,Layout.razor ... 等等都消失了,取而代之的是 BlazorApp 项目中定义由 BlazorApp 引用 BlazorApp.Client,在 Program.cs 中加载成了如下代码。

using BlazorApp.Client.Pages;
using BlazorApp.Components;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
  app.UseWebAssemblyDebugging();
}
else
{
  app.UseExceptionHandler("/Error", createScopeForErrors: true);
}

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(Counter).Assembly);

app.Run();

以上代码中 builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddInteractiveWebAssemblyComponents() 加了Razor组件服务、交互式服务器组件和WebAssembly组件服务。 app.MapRazorComponents&lt;App&gt;().AddInteractiveServerRenderMode().AddInteractiveWebAssemblyRenderMode().AddAdditionalAssemblies(typeof(Counter).Assembly) 将Razor组件映射到应用中,并配置了服务器渲染模式、WebAssembly渲染模式,其中 AddAdditionalAssemblies(typeof(Counter).Assembly) 用于向Blazor应用中添加额外的程序集, 也就是将 BlazorApp.Client 项目中的 Counter 组件加载到 BlazorApp 项目的中知道了Counter组件的存在,并能够在需要时使用它。

静态呈现模式

运行项目第一个页面就是静态渲染界面返回的内容体是静态呈现的内容, 在返回的 html 文件结构中虽然知道这是 Asp.Net Core Razor 页面的一种实现方式,但对于 Blazor 不使用服务器渲染首屏打开只需要 532kb(包含了调试代码以及bootstrap)是非常让人兴奋的(使用过 Blazor WebAssembly 深有体会)。 Visual Studio Blazor Web App Code Hello, world Page

交互式 WebAssembly 呈现模式

而在 counter 页面中虽然依然加载了9.8Mb的Wasm代码(调试模式),但!html 居然返回了完整的初始的Html代码,包含了菜单,页面标题 &lt;title&gt;Counter&lt;/title&gt; 以及 &lt;p role="status"&gt;Current count: 0&lt;/p&gt; 初始加载的标签如图。 Visual Studio Blazor Web App Code Counter Interactive WebAssembly Page

我觉得这就是这次 Blazor 全堆栈 Web 应用实现最让人兴奋的地方,它使用了 Asp.Net Core Razor 渲染了第一次打开没有涉及交互的所有html代码,而且在代码中这是基本无缝对接的,BlazorApp.Client项目里面的 Counter 页面直接使用了 BlazorApp 定义的 App.razor,Layout.razor 在静态渲染和 WebAssembly 之间的切换在代码上是统一的,不再是基本上所有SPA单页应用那样所有页面刷新都只返回同一个index.html。当静态页面和 WebAssembly 同时使用的时候是否单页应用的SEO优化问题就可以解决,在需要交互的地方使用 WebAssembly 首屏加载静态渲染? 这是我的一个想法。

交互式服务器呈现模式

还是在 counter 页面中修改 BlazorApp.Client 项目下 Pages/Counter.razor 文件 @rendermode InteractiveWebAssembly 修改为 @rendermode InteractiveServer 然后重新运行项目,打开 counter 页面,会发现 9.8Mb的Wasm代码的加载过程消失了取而代之的是 ws://localhost:5117/_blazor?id=xxx 的一个 Websocket 连接如图 Visual Studio Blazor Web App Code Counter Interactive Server Page

这次更新最明显的感觉是 InteractiveServer 和 InteractiveWebAssembly 不再是两个项目了,而是仅仅是一行代码的区别。虽然个人觉得服务器呈现模式的使用场景并不是很多,但如此简单的切换方式或许对于一些特殊场景式非常有用的。

流式渲染

对于 weather 页面的加载最开始看 chrome 开发者工具中返回的 html 代码我是很疑惑的如图: Visual Studio Blazor Web App Code Weather StreamRendering Page

@page "/weather"
@attribute [StreamRendering]
...
@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(500);
        ...
    }
    ..
}

在代码中页面初始化等待了500毫秒,实际在浏览器中这个页面的加载就确实花了500到600毫秒,而且在页面中多了blazor-ssr这个自定义标签,页面依旧显示了Loading... 字符并且在html代码中也有它。我第一个瞬间的想法是既然都在一次返回中有了渲染出来的数据为何会单独加一个标签,而且在点击切换页面的时候我并没有感觉到有500毫秒的加载卡顿的感觉。

直到我发现了这篇文档 ASP.NET Core Razor 组件呈现之流式渲染 也就是交互式服务器呈现模式的拓展,文档中描述流式渲染最开始使用占位符内容快速渲染整个页面,也就是加载 blazor-ssr 之前的html内容,而blazor-ssr标签是在500毫秒之后修补到文档对象模型中的。从 chrome 开发者工具中也可以看出, 在下载这个页面时,等待服务器响应时间只花了11毫秒,剩余的500多毫秒用来下载内容,而我的项目是本地运行的理论上并不存在这么长的下载时间。 Visual Studio Blazor Web App Code Weather StreamRendering Page Net Time

初步窥探这应该是用了最近流行的类似chatgpt输出回答内容时所使用的http加载技术,请求可以一边返回一边处理,具体实现我暂时并未去了解。

总结

借着这两天 .NET Conf 2023 发布 .Net 之际,点net又又又发布了迄今为止最快的.Net版本(真的好绕🤣)我也是简单了解了一下比较关心的Web开发方向的一些升级,对于 Blazor 从.Net 6 版本给我的感觉并不是那么完善,当时 Blazor 的 服务器呈现模式 和 Wasm 呈现模式使用体验是非常割裂的,完完全全的就是两个项目虽然可以共用一些代码... 但... 直到 .Net 8 终于把它们融合到,一起似乎它有了一个简单的名字 Blazor。