首页 程序笔记 Blazor的N种渲染模式原理和常见问题说明

Blazor的N种渲染模式原理和常见问题说明

我们从下面这幅图开始,下图显示了三种渲染模式,分别称之为静态SSR、交互式SSR(即之前的BlazorServer)、交互式CSR(即之前的BlazorWasm)。还有一种渲染模式BlazorHybrid,稍后说。

一、先浅层理解一个图例

静态SSR:经典的请求响应模式,服务器接收到客户端的请求后,找到路由组件,直接将Razor组件解析为HTML,发送给浏览器渲染。很多特性,和WebApi相似。

交互式SSR:首先,浏览器发起请求,服务器先返回JS脚本;接着,浏览器的JS脚本自动向服务器发起SignalR连接,这个过程图例没画出来。服务器和浏览器,通过SignalR(本质是WebSocket)建立双向的长连接。服务端管理着一棵与浏览器真实DOM树相对应的虚拟DOM树。服务端通过SignalR,监听着浏览器的UI交互事件。服务端接收到UI事件后,执行计算,(这里还会形成一棵虚拟DOM树,新旧两棵虚拟DOM树进行比较),将差量DOM,通过SignalR提交给浏览器的JS脚本,由JS脚本更新真实DOM。

交互式CSR:首先,浏览器发起请求,服务器先返回JS脚本;接着,浏览器的JS脚本向服务器请求Wasm负载(包括应用组件、依赖项和针对WebAssembly定制的.NET运行时等),下载到WebAssembly沙箱中。“漫长的下载”完成后,启动.NET运行时,通过Wasm和JS的交互操作,监听UI交互事件,并将差量DOM提交给JS脚本,由JS脚本更新真实DOM。Wasm负载可以AOT编译为Wasm二进制,这个很牛。

二、这套机制的诸多疑问

1、什么是状态数据:

状态数据:指为了实现差量更新,由运行时保存和管理的虚拟DOM、组件属性、依赖注入等数据对象。

静态SSR:每次请求和响应都是无状态的HTTP,服务端没有保留状态数据,和WebApi有很多相似性。

交互式SSR:使用WebSocket技术(SignalR)建立长连接,服务端为每个请求都保存着状态数据。

交互式CSR:用户浏览器的每个Tab页中,都保存着一份独立的状态数据,信息保存在WebAssembly沙箱环境中。

2、SignalR的问题:

在网络不稳定情况下,SignalR容易出现断连问题。

连接断开后,服务端会暂时保存状态数据,浏览器JS脚本尝试重连,但重连次数或时间超过阈值后,会放弃重连,服务端释放数据,浏览器提示连接失败。

每个连接都保存着一份独立的状态数据,服务器的压力会比较大。这让人想起了古老的聊天室。

3、JSInterop的问题:

不像Vue等前端框架,Blazor的事件监听和差量DOM更新,涉及到Wasm和JS两个运行时的切换,以及两个运行时之间的通信数据转换,其间无论优化的如何,都是有性能开销的。

Blazor能追回来的性能,就是差量DOM的计算上,如果计算复杂,Blazor的性能优势才能体现出来,但大多数情况下都比较简单。当然,这个问题对于BlazorWasm来说,并不是大问题,下面才是大问题。

4、交互式CSR的最大问题:

和Vue等前端框架不同,首次访问BlazorWasm网页时,不但要下载应用组件、依赖类库、CSS等文件,还要下载整个.NET运行时,启动.NET也是一项耗时工作(这个问题有好几个Issues),Wasm负载很大。

虽然现在BlazorWasm支持AOT,可以提升运行时性能,但编译后的下载包体积会更大。总之,交互式CSR的冷启动很耗时。

借助浏览器缓存,Wasm负载首次下载后可以缓存,从而大大减少初始化时间,但要解决应用升级重新发布后的更新问题。

5、什么是Auto模式:

交互式SSR和交互式CSR都有各自的优点和问题,而Auto模式将两者结合起来,取长补短。

首次请求时,先使用交互式SSR,能够快速呈现页面。然后,浏览器后台静默下载Wasm负载并启动.NET运行时,当Wasm的准备工作完成后,再次发生路由切换时,将自动切换到交互式CSR模式。

同一个浏览页面,如果一开始是交互式SSR,即使Wasm已经准备好,也不会发生自动切换。

6、什么是BlazorWeb混合渲染:

综合了静态SSR、交互式SSR、交互式CSR、Auto模式的混合渲染模式,同一个项目,不同页面、或者同一个页面内的不同组件,可以使用不同的渲染模式。充分利用不同渲染模式的优势,但也增加了复杂度。

交互式组件具有传染性,如果父组件使用了某种交互式渲染模式,则后代组件自动继承这种交互式渲染模式,且不能更改。这个规则将在.NET9中被打破,在交互式组件的后代中,可以强行启动静态SSR渲染,VeryGood!!!

静态SSR组件不具有传染性,在一个静态SSR组件(页面)中,可以使用多种交互模式的组件,比如A组件是交互式SSR,B组件是交互式CSR,C组件是Auto。

在一个静态SSR组件(页面)中,传递给交互式子组件的参数必须时JSON可序列化的,比如:

//Name参数是可序列化的
<MyComp @rendermode="InteractiveServer" Name="MC"/>

//RenderFragment类型参数Child content,是不可序列化的
<MyComp @rendermode="InteractiveServer">Child content</MyComp> //XXX报错

//像以上这种情况,可以外包一层来解决
//WrapMyComp.razor
<MyComp>Child content</MyComp>
//在WrapMyComp组件上使用交互式
<WrapMyComp @rendermode="InteractiveServer" />

7、什么是首次浏览:

当我们在地址栏中输入地址,或者手动刷新页面,或者使用a标签跳转地址,都会触发首次浏览。首次浏览,不是首“页”浏览,任何页面(路由地址)都可能触发首次浏览。

首次浏览发生后,使用Blazor框架内置的路由跳转,除静态SSR之外,不会再次触发首次浏览。

首次浏览发生时,对静态SSR没有影响,因为它的每次请求/响应,本质都是首次浏览;对于交互式SSR,将重新建立一个新的SingalR连接,状态数据丢失;对于交互式CSR,将重新下载Wasm负载并重新启动.NET运行时,状态数据丢失。

如果开通了预渲染功能(交互式SSR或者交互式CSR),只有首次浏览时会触发预渲染。但是,任意页面都有可能是首次浏览的页面,所以任何页面都要考虑预渲染的问题。

8、预渲染的问题:

首次浏览时,为加快页面的渲染速度和优化SEO(这是重点),Blazor为交互式SSR和交互式CSR,提供了预渲染机制。请求页面首先在服务端被解析为HTML,并迅速发送到浏览器渲染,这个过程类似静态SSR,爬虫可以爬到内容。然后再建立SignalR连接或者下载Wasm负载,完成后,会启动相应的运行时,并重新渲染一次。

预渲染提升了首次浏览的体验,优化了SEO,但也带来了一些难题。一是交互式组件的初始化和生命周期函数(除了AfterRender之外),会被执行两次,这不仅有性能开销,如果前后两次产生的数据不一致,还需要解决数据一致性问题;二是对于交互式CSR组件,它首先在服务端执行一次,然后在客户端重新执行一次,两次执行的环境不一样,如果组件中有访问强依赖于执行环境的数据,则需要解决数据一致性问题,有方案,但操作繁琐。

BlazorWeb项目中,预渲染是默认打开的。如果在生命周期函数中进行比较复杂操作的,建议针对这个组件,单独关闭预渲染。

9、依赖注入的生命周期问题:

静态SSR:依赖注入的表现特征,和WebApi保持一致,每次请求是一个Scoped(范围),也可以使用Transient(瞬时)和Singleton(单例)服务。

交互式SSR:如果是Singleton服务,所有SignalR连接,都是访问同一个对象,需要注意数据的安全性;每个SignalR连接,是一个Scoped服务,所以要创建购物车这种共享状态数据,适合使用Scoped;交互式SSR,也可以使用Transient服务,每次使用都是一个新的实例。

交互式CSR:首先,区别于静态SSR和交互式SSR,依赖注入对象存在于客户端浏览器中;其次,由于不存在请求响应模式,所以交互式CSR的Scoped服务,和Singleton服务基本一致;Transient服务,和SSR表现一致。

由于同一组件可能在服务端执行,也可能在客户端执行,比如使用Auto模式的组件,还或者使用预渲染的组件。如果在组件中,使用了某个依赖注入服务,就需要在服务端和客户端分别注册,且还需要考虑这两个服务是不同的对象,使用方式是否一样(比如HttpClient,服务端有HttpClientFactory,而客户端默认没有提供的),以及是否会造成数据不一致的问题。

10、状态数据的共享和持久化问题:

状态数据的共享,目前有联级值(CascadingValue)和依赖注入两个方案。如果使用Auto模式,一定要和状态数据的持久化相结合,因为当交互式SSR向交互式CSR切换时,无论是联级值、还是依赖注入,数据都会丢失。如果使用联级值的自定义组件方案,应该在自定义组件的初始化或者AfterRender后,读取持久化中的数据,并在组件销毁时保存数据。如果是使用依赖注入方案,是不是应该在根组件的生命周期里进行状态数据的持久化保存和读取?这个方案暂时还没试过。

状态数据的持久化,目前也有两个方案,一是使用浏览器的Storage,使用这种方式,一定要在AfterRender生命周期函数中进行持久化操作,因为只有它里面,调用JS或操作DOM才是安全的,并建议使用“nuget搜索Blazored”提供的API。我更推荐是用第二种方案,使用服务端的Sqlite进行持久化保存,这恰恰体现了BlazorWeb的优势。这个方案,虽然相对于浏览器的Storage会更重些,但它还是处于轻量这一档,同时它更加稳定,也不需要考虑不同渲染模式的影响。具体的实现思路:创建增删改查的服务接口>服务端实现接口,并将实现类暴露为WebApi>客户端的实现类,并通过访问WebApi来进行增删改查>服务端和客户端分别用自己的实现类来注册服务。

11、HTTP请求问题:

由于HttpClient存在Socket套接字和DNS的问题(甭管啥问题,反之有问题),AspNetCore服务端项目,推荐使用HttpClientFactory,且默认已安装。BlazorWeb的服务端项目,可以直接使用。

BlazorWasm客户端,不存在以上问题,如果是独立项目,可以直接使用HttpClient。BlazorWasm项目,默认没有安装相应类库,无法直接使用HttpClientFactory。

BlazorWeb项目中,由于同一组件可能在服务端执行,还可能在客户端执行,如果组件中需要发送HTTP请求,建议统一使用HttpClientFactory,此时BlazorWasm客户端项目需要安装Microsoft.Extensions.Http。

12、BlazorHybrid如何运行?

看图例,BlazorHybrid渲染模式的运行原理并没有什么不同,把服务器应用或者浏览器的WebAssembly换成MAUI应用、再把浏览器换成本机自带的WebView就可以。

MAUI应用托管着Blazor组件、依赖资源和虚拟DOM,然后通过MAUI应用和WebView的连接通道进行事件响应和差量更新。这个连接通道的具体实现,可以追踪一下BlazorWebView控件的源码。

Blazor托管在MAUI本机应用中,没有WebAssembly这样的沙箱,所以,在Razor组件中,可以调用所有“MAUI本机应用可以调用的API”,就像BlazorServer环境中的组件可以调用AspNetCore的API一样。

3

站心网

我们从下面这幅图开始,下图显示了三种渲染模式,分别称之为静态SSR、交互式SSR(即之前的BlazorServer)、交..

为您推荐

软件产品开发中常见的10个问题及处理方法

常见的10个问题#产品开发中常见的10个问题思维导图需求相关#1. 需求不明确#在日常工作中,需求来源于用户、老板、客户、竞品分析、业务部门、产品经理等,这些人或部门会提出需求,因为他们不是产品经理,提出的需求..

Java中String类常见的方法

以下介绍字符串常见的几个方法。介绍String类在 Java 中,String类是一个代表字符串的类,具有以下特性:不可变性:String对象一旦被创建就是不可变的,即它们的值在创建后不能被更改。任何对String对象的修改操作实..

EntityFrame(EF) SQLite常见问题和解决方案

在使用 Entity Framework (EF) SQLite 时可能遇到的一些问题,下面是一些常见的问题和对应的解决方案。1. 无法找到适配器或数据库提供程序运行 EF 时提示类似以下错误:No database provider has been configured fo..

ASP.NET MVC最常用的设计模式代码示例

ASP.NET MVC 是一个基于分层架构的框架,其核心架构本身已经实现了 MVC 模式(Model-View-Controller)。除了 MVC 模式,开发者在使用 ASP.NET MVC 开发应用时,通常会结合其他设计模式以提高代码的可维护性、可扩展..

.NET开源ORM FreeSql常见问题和解决方法

FreeSql 是一个功能强大的 .NET 开源 ORM(对象关系映射)工具,支持多种数据库类型并提供丰富的功能特性。以下是使用 FreeSql 过程中常见的问题及其解决方法:1. 数据库连接问题问题描述:无法连接数据库,报错提示..

Blazor 与传统 ASP.NET MVC 的对比

Blazor 和传统 ASP.NET MVC 是两种不同的Web开发框架,分别针对现代前端开发需求和传统的服务端渲染需求。以下从多个维度对两者进行对比:1. 架构和工作方式Blazor基于组件的开发方式,采用现代化前端框架的思想,代..

.NET9 Blazor有哪些更新?

.NET 9 在 Blazor 中引入了多项更新,增强了开发体验和应用性能。以下是主要更新内容:1. 新的 Blazor 混合应用模板.NET 9 引入了一个支持 .NET MAUI 和 Blazor Web 客户端的混合应用模板。开发者可以利用该模板在共..

GitHub上开源许可证License含义说明

在 GitHub 上,开源许可证规定了其他人如何使用、修改和分发代码。以下是几种常见的开源许可证及其含义:MIT 许可证:MIT 许可证是非常宽松的许可证,允许任何人自由使用、复制、修改和分发代码。唯一的要求是保留原..

SQL Server用UUID做主键性能问题和解决方案

在 SQL Server 中使用 UUID(全称:Universally Unique Identifier) 作为主键确实可能带来一些性能问题,特别是在大型数据库和高写入负载的场景下。以下是一些关键的性能挑战及其原因:1. 无序插入导致索引碎片化UU..

用Blazor开发App应用可行吗?

使用 Blazor 开发 App 应用是可行的,并且已成为跨平台应用开发的一种强大选择,特别适合在 Web、移动端(iOS、Android) 和 桌面端 上创建应用。Blazor 是一个支持使用 C# 和 .NET 的框架,开发者可以编写一套代码..

前端开发有必要学习Blazor吗?

前端开发者是否需要学习 Blazor 取决于你的技术栈、职业目标和项目需求。Blazor 是一个由 .NET 支持的框架,允许开发者使用 C# 进行前端开发,这对专注于 JavaScript 的传统前端开发者而言可能具有不同的吸引力和适..

.NET开发中常见的异常报错原因和解决方法?

在 .NET 开发中,常见的报错通常涉及代码编写、配置和运行时环境。以下是一些常见的报错类型及其解决建议:1. NullReferenceException原因:尝试访问空对象的成员。解决:在访问对象之前检查是否为 null,可以使用 C..

设计模式之高质量代码

0,什么是高质量代码我觉得回答这个问题,应该从两个方面考虑。从业务角度考虑。首先,在公司开发一款软件,应该是业务在驱动。所以,从这个角度来说,代码第一个应该满足的是业务需求,如果连最基本的业务需求都满..

.NET 9 即将推出的功能Task.WhenEach

.NET 爱好者!我刚刚偶然发现了一个非常酷的新 PR,它被合并到 .NET 运行时存储库中,我想分享一个例子。希望您能为新的 .NET 版本大肆宣传!在即将到来的 .NET 9 版本中,我们预计会有一个名为 .它在这里让您的异步..

针对 Go 语言开发的 SQL 驱动模拟库

数据库交互是几乎所有应用程序不可或缺的一部分,开发者们常常需要对数据库进行各种操作,包括插入、更新、删除和查询等。然而,在开发过程中直接对真实数据库进行操作不仅耗时耗力,还可能带来数据一致性和安全性的..

DockerUI 中文可视化Docker管理工具使用示例

DockerUI 是由国内开发者打造的一款优秀的 Docker 可视化管理工具。该工具拥有简洁直观的UI界面,可以轻松进行Docker主机管理、集群管理,以及Docker任务的编排等操作。DockerUI不仅展示了资源利用率、系统信息和更..

前端CSS常见的三种设计模式

CSS设计模式主要包括OOCSS、SMACSS和BEMCSS等。以下是对这些模式的具体介绍:OOCSS:面向对象的CSS,旨在编写高可复用、低耦合和高扩展的CSS代码。它将抽象(结构)和实现(样式)分离,抽离公共代码,以提高代码的..

WinToUSB | 把Windows塞进U盘里即插即用

不论是在外出差,还是在家临时办公,现在很多设备携带起来都不是那么方便,在这种情况下,有一个轻巧而高效的操作系统环境就显得格外关键。今天,要给大家介绍一款超级实用的便携式系统启动盘,凭借其独特功能和卓越..

.NET Framework被淘汰了吗?

.NET Framework并未完全被淘汰,但它的某些版本确实已经停止支持。微软在2023年11月10日停止了对.NET Framework 4.8之前的版本的支持。这一决策意味着使用这些旧版本的应用程序将不再获得安全更新和其他维护,从而可..

强大的 .NET Mock 框架 单元测试模拟库Moq使用教程

单元测试是确保代码质量和可靠性的重要手段。当我们的代码依赖于外部系统、数据库或第三方服务时,编写有效的单元测试可能会变得复杂且耗时。为了简化这一过程,模拟(Mocking)技术应运而生。在 .NET 生态系统中,M..

发表回复

返回顶部