本章包括 \- 为下一次应用选择 Blazor 的理由 - 为什么组件是构建用户界面的更好方法? - Blazor 的托管模式 我们生活在一个激动人心的时代,.NET 开发人员的生活从未如此美好。我们可以为任何操作系统(Windows、Linux、iOS、Android 或 macOS)创建应用程序,当然,我们还可以使用 [ASP.NET](http://ASP.NET) MVC、Razor Pages 和 Web API 构建令人惊叹的网络应用程序,多年来,这些技术使我们能够创建稳健、可扩展和可靠的系统。
然而,这块拼图一直缺少一块。所有 [ASP.NET](http://ASP.NET) 网络解决方案都有一个共同点,那就是它们都基于服务器。 我们一直无法利用 C# 和 .NET 的强大功能来编写客户端网络应用程序;这一直是 JavaScript 的天下,但现在不是了。
在本章中,我将向你介绍一个名为 Blazor 的革命性客户端框架。Blazor基于网络标准,允许我们使用C#和.NET编写内容丰富、引人入胜的用户界面。我们将探讨 Blazor 如何使您的开发过程更加高效,提高您的生产力水平,尤其是当您在服务器上也使用 .NET 时。我们将介绍托管模型,这是开始使用 Blazor 时需要了解的一个重要概念。接下来,我们将开始探索组件以及使用组件构建用户界面的好处。最后,我们将讨论您下一个项目应该考虑 Blazor 的原因。
可以说,近来启动一个新项目最难的部分就是选择技术栈--可供选择的技术实在太多了。 在前端领域尤其如此。我们必须选择一个框架(Angular、React、Vue.js),选择一种语言(TypeScript、CoffeeScript、Dart),选择一种构建工具(webpack、Parcel、Browserify)。如果一个团队是这个生态系统的新员,要想找出哪种技术组合能帮助项目取得成功,似乎是一项几乎不可能完成的任务;对于有经验的团队来说,这甚至是一件难事!
让我们来谈谈在下一个项目中选择Blazor的一些主要原因,以及Blazor如何帮助您避免我刚才提到的一些问题。
与许多现代前端框架一样,Blazor 使用组件概念来构建用户界面。一切都是组件--页面、页面的一部分、布局。在Blazor中有多种类型的组件,以及设计组件的多种方法,这些都将在以后的章节中探讨。不过,学会从组件的角度思考问题对于编写 Blazor 应用程序至关重要。
把一个组件看作是一个积木。你可以将这些积木组合在一起,形成你的应用程序。这些构件可大可小,由你决定;但是,将整个用户界面作为一个单独的组件来构建并不是一个好主意。当组件被用来划分用户界面的逻辑区域时,才真正显示出它的优势。让我们来看一个用户界面组件化的例子(图 1.1)。
图 1.1 按组件划分的布局示例
界面的每个区域都是一个组件,每个组件都有一定的责任。你可能还会注意到,这里形成了一个层次结构。布局组件位于树的顶端;菜单、页眉、主页和页脚都是布局组件的子组件。这些子组件可以而且很可能会有自己的子组件。例如,页眉组件可以包含一个徽标组件和一个搜索组件(图 1.2)。
图 1.2 将组件嵌套成组件树的示例
许多用户界面中都有重复元素。使用组件的一大优势是,您可以在组件中定义元素,然后在元素重复出现时重复使用该组件。这可以大大减少应用程序中重复代码的数量。它还能大大提高应用程序的可维护性--如果元素的设计发生变化,只需在一个地方进行更新即可。
为了迎合更高级的应用场景,组件可以定义自己的应用程序接口(API),允许数据和事件传入和传出。想象一下业务线应用程序。可以肯定的是,在该应用程序中,有许多地方都以表格格式显示数据。一种方法是将每个表格创建为自己的组件,但这意味着我们最终会有很多组件在表格中显示数据。更好的方法是定义一个组件,将数据集作为参数,然后将其显示在表格中。现在,我们有了一个用于在表格中显示数据的组件,可以在整个应用程序中重复使用。我们还可以为该组件添加排序或分页等功能。这样,应用程序中的所有表都可以自动使用这一功能,因为它们都在重复使用同一个组件。
虽然组件通常都是独立的,但也有可能通过相互协作来创建更复杂的用户界面。例如,以我们刚才谈到的数据表为例,它可以是一个单独的组件,但有可能非常庞大。另一种方法是将其分为几个较小的组件,每个组件都执行特定的工作。我们可以有一个表头组件、一个表体组件,甚至一个表格单元格组件。这些组件各自执行特定的工作,但它们仍然是整个表格组件的一部分。
既然我们已经对组件有了一个大致的了解,那就让我们来看看 Blazor 中组件的一个示例。为此,我们将从 Blazor 项目模板中抓取一个组件。图 1.3 显示了 Blazor 标准项目模板 Counter.razor 中的一个组件示例。
图 1.3 Blazor 中组件的各个部分
这个特殊的组件被称为可路由组件,因为它的顶部声明了一个页面指令。可路由组件本质上是应用程序中的一个页面。当用户导航到应用程序中的/counter路由时,Blazor路由器将加载该组件。它通过一个按钮显示一个简单的计数器,当用户点击按钮时,计数器的数值会递增一个,并向用户显示新的数值。
虽然现在理解代码并不重要,但我们可以理解组件的结构。图 1.3 分成三个部分,每个部分都有一定的职责。
在本书接下来的内容中,我们将更详细地介绍组件,所以现在就不多说了。不过,这已经让你了解了 Blazor 中组件的外观和组成方式。
Blazor 是一个功能齐全的框架,可利用 C# 和 .NET 的强大功能构建现代客户端应用程序。这样,开发人员就能构建几乎适用于任何平台(包括网络、移动和桌面)的引人入胜的应用程序。
Blazor 是 Angular、Vue.js 和 React 等 JavaScript 框架和库的替代品。如果您有过使用这些技术的经验,那么您可能会发现一些熟悉的概念。最显著的影响是使用组件构建用户界面的能力,这是所有这些技术共享的概念,我们将在本章稍后部分详细探讨。
因为Blazor是建立在网络标准之上的,[所以它不需要终端用户在机器上安装.NET](http://xn--fhqf16de7uo1ad5qlb098ghct12exk1bcro3ylb77akkco50g.NET),也不需要任何浏览器插件或扩展。事实上,有了 Blazor WebAssembly 应用程序,我们甚至不需要在服务器上运行 .NET;这种 Blazor 可以作为简单的静态文件托管。
基于 .NET 构建意味着我们可以访问 NuGet 上充满活力的软件包生态系统。我们拥有一流的工具,包括 Visual Studio、VS Code 和 JetBrains Rider。此外,由于.NET是跨平台的,我们可以在任何我们喜欢的平台上开发我们的Blazor应用程序,无论是Windows、macOS还是Linux。
虽然本书将重点介绍用于网络应用程序开发的Blazor,但我想强调的是,Blazor的编程模型也可用于构建跨平台的桌面应用程序。[随着.NET](http://xn--u2yv18d.NET) 6的推出,Blazor Hybrid也被引入。它建立在新的.NET多平台应用程序用户界面(又称MAUI)框架之上,工作方式与Electron应用程序类似。Blazor应用程序的内容通过BlazorWebView控件呈现。这为这些应用程序的结构提供了很多选择。开发人员可以使用 Blazor 和网络技术来构建整个用户界面,但 Chrome 浏览器除外,它是应用程序的最外层容器,包括标题栏。开发人员也可以只使用 Blazor 编写界面中的特定部分,并将其与本地控件放在一起。
不仅如此。有一个名为 Mobile Blazor Bindings ([http://mng.bz/OGOO](http://mng.bz/OGOO)) 的实验项目已经运行了很长时间。这是 [ASP.NET](http://ASP.NET) Core 团队和 .NET MAUI 团队的合作项目,旨在研究使用 Blazor 编程模型构建本地移动应用程序的潜力和需求!这确实让 Blazor 成为一项值得学习的技术,因为一旦了解了它,开发人员就可以为几乎所有平台或设备构建用户界面。
希望您已经看到,Blazor 是一项令人兴奋的技术,具有很大的潜力。但是,在我们继续深入研究之前,有一个关键概念必须了解,那就是托管模式。接下来,让我们来解决这个问题。
刚开始使用 Blazor 时,您会立即遇到托管模式。从本质上讲,托管模式就是运行Blazor应用程序的地方。目前,Blazor有两种网络专用托管模型--Blazor WebAssembly和Blazor Server。无论您为应用程序选择哪种托管模式,组件模式都是相同的,这意味着组件的编写方式相同,并可在任一托管模式之间互换(图 1.4)。
图 1.4 Blazor 将托管模型与应用程序/组件模型分开。这意味着为一种托管模式编写的组件可用于另一种托管模式。
图 1.4 显示了 Blazor 架构的抽象示意图,包括应用程序和组件模型与各种托管模型之间的分离。Blazor 的一个有趣之处在于,随着时间的推移,其他托管模式也有可能出现。这使得 Blazor 可以在更多地方运行,并用于创建更多类型的用户界面。
Blazor WebAssembly允许您的应用程序完全在客户端浏览器中运行,因此它可以直接替代JavaScript SPA(单页面应用程序)框架。为了帮助您了解这种托管模式的工作原理,我们将介绍初始化 Blazor WebAssembly 应用程序的过程(图 1.5)。
图 1.5 Blazor WebAssembly 应用程序启动,显示客户端浏览器与网络服务器之间的交互情况
当浏览器向网络服务器发出请求时,整个过程就开始了。网络服务器将返回一组加载应用程序所需的文件。这些文件包括应用程序的主机页面(通常称为 index.html)、应用程序所需的任何静态资产(如图像)、CSS 和 JavaScript,以及一个称为blazor.webassembly.js 的特殊 JavaScript 文件。
在 Blazor WebAssembly 托管模式中,Blazor 框架的一部分位于 JavaScript 中,并包含在 blazor.webassembly.js 文件中。这部分框架主要做三件事:
说到这里,您可能会好奇为什么我们要编写 JavaScript 文件。Blazor的一大卖点就是可以使用C#而不是JavaScript编写用户界面逻辑,对吗?是的,没错。但就目前而言,WebAssembly 有一个很大的限制:它不能改变 DOM 或直接调用 Web API。WebAssembly 的下一阶段计划并正在开发这些功能,但在它们出现之前,JavaScript 是执行这些任务的唯一方法。
将来有可能不再需要这个文件。这将取决于 WebAssembly 新增功能和浏览器采用这些功能的速度。但现在,它还是框架的重要组成部分。
既然我们已经弄清了这一点,那就继续启动 Blazor 应用程序吧。我想指出的是,从服务器返回的文件都是静态文件;它们不需要任何服务器端编译或操作。这意味着它们可以托管在任何提供静态托管的服务上。服务器上不需要 .NET 运行时。这是首次向 .NET 开发人员开放 GitHub 页面等免费托管选项(仅适用于独立的 Blazor WebAssembly 应用程序)。
浏览器从网络服务器接收到所有初始文件后,就可以处理这些文件并构建 DOM。接下来,浏览器将执行 blazor.webassembly.js。它会执行许多操作,但在启动 Blazor WebAssembly 应用程序时,它会下载一个名为 blazor.boot.json 的文件。该文件包含运行应用程序所需的所有框架和应用程序文件的清单。下载完成后,它将用于下载运行应用程序所需的其余文件。
这些文件大多是普通的 .NET 程序集,没有什么特别之处,可以在任何兼容的 .NET 运行时上运行。但也有另一种下载文件,名为 dotnet.wasm。这个文件是一个完整的 .NET 运行时,已经编译成了 WebAssembly。
WebAssembly WebAssembly 是一种类似汇编语言的低级语言,可在现代网络浏览器中运行,性能接近原生语言。虽然可以直接编写 WebAssembly,但它更常用作 C/C++ 和 Rust 等高级语言的编译目标。WebAssembly 设计为与 JavaScript 同时运行,允许 JavaScript 调用 WebAssembly,反之亦然。WebAssembly 与 JavaScript 应用程序一样,在相同的安全沙箱中运行。有关 WebAssembly 的详细信息,请访问 [https://webassembly.org](https://webassembly.org)。
默认情况下,只有 .NET 运行时编译为 WebAssembly--框架和应用程序文件是标准的 .NET 程序集。不过,.NET 6 引入了 AOT(超前)模式,允许开发人员将应用程序编译为 WebAssembly。这样做的好处是大大提高了 CPU 密集型代码的性能。使用 AOT,编译为 WebAssembly 的 CPU 密集型代码的性能将比默认使用的解释型方法高出许多倍。不过,这也有代价,那就是代码的大小。AOT 编译的代码比标准程序集大两倍左右,这意味着应用程序的总下载量要大得多。
下载完 blazor.boot.json 文件并下载了其中列出的文件后,就到了运行应用程序的时候了。初始化 WebAssembly .NET 运行时,然后加载 Blazor 框架,最后加载应用程序本身。至此,我们就有了一个运行中的 Blazor 应用程序,它完全存在于客户端的浏览器中。除了请求额外数据(如适用)外,它不再依赖服务器。
我们现在了解了 Blazor WebAssembly 应用程序是如何启动的。但用户界面更新是如何计算的呢?就像初始化过程一样,我们将按照一个场景来理解这个过程是如何发生的,以及 Blazor 是如何做的(图 1.6)。
图 1.6 Blazor WebAssembly 中从点击链接到应用用户界面更新的客户端导航过程
在我们的方案中,我们有一个 Blazor WebAssembly 应用程序,其中有两个页面只包含一个页眉: 分别是主页和计数器。用户在应用程序的主页上,点击链接进入计数器页面。在从主页导航到计数器页面时,我们将跟踪 Blazor 更新用户界面的过程。
当用户点击计数器链接时,导航事件会被 Blazor 的 JavaScript 运行时(blazor.webassembly.js)拦截。然后,该事件被传递给运行在 WebAssembly 运行时 (dotnet.wasm) 上的 Blazor 框架,并由 Blazor 的路由器组件进行处理。
路由器会检查路由表,寻找与用户试图导航的路由相匹配的可路由组件。在我们的例子中,它会找到与 Counter 组件匹配的组件,创建该组件的新实例,并执行相关的生命周期方法。
完成后,Blazor 将计算出更新 DOM 以匹配计数器组件所需的最小更改次数。完成后,这些更改将传回 Blazor JavaScript 运行时,然后将这些更改应用到物理 DOM。此时,用户界面将更新,用户将进入计数器页面。
所有这一切都发生在用户浏览器的客户端。在这个过程中,任何时候都不需要服务器。可以说,在现实世界的应用程序中,你很可能会在这个过程的某个阶段调用服务器。这通常发生在执行被导航组件的生命周期方法时,以便为组件加载一些初始数据。但这取决于具体的应用程序。
既然我们已经对 Blazor WebAssembly 托管模式的工作原理有了一定的了解,下面我们就来谈谈选择这种模式的好处和利弊。先说好处:
当然,没有什么是万能的,让我们来了解一下这种模式的一些权衡:
总而言之,如果您想替换Angular、React或Vue.js等JavaScript SPA框架,那么Blazor WebAssembly是您应该选择的托管模式。虽然需要考虑一些取舍,但选择这种模式也有一些实质性的好处。
既然我们已经了解了Blazor WebAssembly是如何工作的,那么让我们把目光转向服务器托管模式,看看它有什么不同。Blazor Server是Blazor第一个支持生产的托管模式,比WebAssembly版本早8个月左右发布。与之前的托管模式一样,我们将通过初始化一个 Blazor Server 应用程序来帮助你理解其工作原理(图 1.7)。
图 1.7 Blazor 服务器应用程序的启动过程
这一过程始于浏览器加载网站的请求。当该请求到达网络服务器时,可能会发生两种情况:启动应用程序,或者如果应用程序已经在运行,则建立一个新的会话。为什么应用程序已经在运行?Blazor WebAssembly的行为更像桌面应用程序,每个用户都有自己的实例,而Blazor服务器则不同,它运行的是所有用户都能连接的应用程序实例。因此,应用程序可能已经在运行,新请求只是建立一个新会话。每个用户都有自己的应用程序实例,该实例在他们的机器上本地运行。Blazor 服务器则不同,服务器上只运行一个应用程序实例,但可以支持多个客户端。因此,应用程序可能已经在运行,新请求只会建立一个新会话。
然后,应用程序对请求进行处理,并将初始有效载荷发送回浏览器。这包括 CSS、JavaScript 文件和图片等静态资产。此外还有初始 HTML,但这是经过编译的,而不是我们在 Blazor WebAssembly 中看到的静态 HTML。这是因为Blazor服务器应用程序的托管页面是Razor页面,而不是WebAssembly模型中的静态HTML页面。这样做的好处是允许Blazor服务器应用程序开箱即用服务器端预渲染。事实上,在创建此类 Blazor 应用程序时,该功能已默认启用。
一旦初始有效载荷返回浏览器,文件得到处理并创建了 DOM,然后一个名为 blazor.server.js 的文件就会被执行。该运行时的任务是建立一个 SignalR 连接,返回服务器上运行的 Blazor 应用程序。至此,应用程序就完全启动并准备好与用户交互了。
您可能已经发现了这一点,但整个过程与 Blazor WebAssembly 的工作方式并无不同,只是在 SignalR 连接上稍作了延伸。Blazor Server 和 Angular、Vue.js 或 Blazor WebAssembly 一样,都是 SPA。它只是碰巧在服务器上而不是客户端运行其逻辑并计算 UI 更新。事实上,我敢打赌,如果你看到两个完全相同的应用程序,一个是用 Blazor Server 编写的,一个是用 Blazor WebAssembly 编写的,作为用户,你肯定分辨不出它们之间的区别。
在讨论这种模式的优势和权衡之前,我想先简单提一下性能。由于这种托管模式会产生大量的网络聊天,您可能会想,这种托管模式的扩展性是否会特别好。
2019 年,[ASP.NET](http://ASP.NET) Core 团队进行了一些测试,以确定 Blazor Server 应用程序的性能水平。他们在 Azure 中设置了一个应用程序,并在不同功率的虚拟机上进行了测试,检查应用程序可支持的活跃用户数量。以下是测试结果:
如您所见,Blazor 服务器在性能方面毫不逊色。团队发现,影响可支持客户端数量的主要因素是内存。这是有道理的,因为服务器需要跟踪连接到它的所有客户端--客户端越多,需要存储在内存中的信息就越多。
测试的另一个主要发现是网络延迟对应用程序的影响。由于所有交互都要发回服务器进行处理,因此延迟会对可用性产生很大影响。如果服务器距离客户端 250 毫秒(ms),那么每次交互至少需要 500 毫秒才能处理完毕,因为交互必须先传送到服务器(250 毫秒),然后处理,最后再传送回来(250 毫秒)。
测试发现,当延迟超过 200 毫秒时,用户界面就会开始感觉迟钝,响应速度降低。一般来说,您总是希望您的用户与服务器位于同一大洲。如果您想拥有一个全球可用的 Blazor 服务器应用程序,那么您就需要让应用程序均匀地分布在世界各地,并将所有客户端与服务器之间的时延控制在 200 毫秒以内。
和以前一样,让我们来看看选择 Blazor 服务器应用程序的好处和利弊。
Blazor 服务器有很多优点,但如何取舍呢?
总之,如果您正在寻找一个快速加载的应用程序,并且您的用户拥有快速稳定的网络连接,那么 Blazor 服务器就是一个不错的选择。选择这种托管模式,您还可以获得代码安全保障。
在结束本章之前,我想让大家了解另外两种托管模式--Blazor混合托管和Blazor移动绑定。我不会详细介绍这两种模式,因为它们不是本书的重点,但知道它们的存在可以说明使用Blazor可以构建的范围。
Blazor Hybrid基于.NET MAUI框架的技术,允许开发人员使用Blazor编写跨平台桌面应用程序。与 Blazor WebAssembly 和 Blazor Server 一样,组件使用 C#、HTML 和 CSS 编写,并使用名为 BlazorWebView 的控件呈现。下面的列表显示了一个在 Blazor Hybrid 应用程序中运行的组件示例。
清单 1.1 在 Blazor Hybrid 上运行的组件
Current count: @currentCount
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
Blazor Hybrid 的一大优势是,它可以运行同样可以在 Blazor WebAssembly 或 Blazor Server 中运行的组件。清单 1.1 中显示的代码可以在这三种托管模式下执行,无需做任何修改。
Mobile Blazor Bindings 是一种试验性托管模式,它采用了一种不同的组件编写方法。该托管模式的组件必须使用本地控件编写。以下列表包含与列表 1.1 相同的组件,但针对移动 Blazor Bindings 托管模式进行了重写。
清单 1.2 在 Mobile Blazor Bindings上运行的组件
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
正如您所看到的,这两个代码示例的编程模型是相同的。代码块中的逻辑没有变化;毕竟这只是 C#。唯一不同的是标记,网络技术被换成了本地移动控件。这意味着我们不能在基于网络的托管模型和本地托管模型之间交换组件。不过,一旦我们掌握了 Blazor 的编程模型,就可以轻松地利用这些知识创建其他类型的用户界面。
本章介绍了 Blazor 框架。我们介绍了 Blazor 的许多强大功能,还介绍了许多现在可能还不太明白的概念。别担心,在接下来的章节中,我们将详细探讨所有这些内容。现在,这里是我们所涉及内容的总结: