冬雨财经 发表于 2025-3-24 08:57:28

C#逆袭前端:Blazor WebAssembly 全栈开辟揭秘

一、引言

在当今的 Web 开辟范畴,技术的快速迭代与创新令人目不暇接。传统的前端开辟主要依赖 JavaScript 语言,共同各种框架如 React、Vue 和 Angular 等,来构建交互性强、体验丰富的用户界面。这些技术栈在已往的十几年间极大地推动了 Web 应用的发展,从简单的网页展示进化到现在功能复杂、媲美桌面应用的 Web 应用步伐。
然而,随着.NET 技术的不停演进,特殊是.NET Core 的跨平台特性以及对 Web 开辟的深度支持,一种全新的前端开辟方式 ——Blazor WebAssembly 应运而生。它打破了传统前端开辟对 JavaScript 的依赖,允许开辟者使用 C# 语言进行前端开辟,为 Web 开辟带来了新的思绪和解决方案。
C# 作为一种强类型、面向对象的编程语言,拥有丰富的类库和强大的开辟工具支持,在后端开辟范畴已经取得了巨大的成功。Blazor WebAssembly 则将 C# 的能力拓展到了前端,让开辟者可以或许在全栈开辟中使用同一种语言,镌汰了因语言切换带来的学习成本和开辟成本。同时,它使用 WebAssembly 技术,将.NET 代码编译成二进制格式在欣赏器中运行,实现了高性能和接近原生的用户体验。
对于广大 C# 开辟者而言,Blazor WebAssembly 无疑是一个令人高兴的技术。它不但提供了一种全新的前端开辟方式,还让他们可以或许在熟悉的编程环境中完成前后端的开辟工作。在接下来的内容中,我们将深入探讨 Blazor WebAssembly 的技术原理、开辟流程以及实际应用场景,一起明白 C# 在前端开辟中的魅力 。
二、Blazor WebAssembly 是什么

二、Blazor WebAssembly 是什么

(一)界说与原理

Blazor WebAssembly 是微软推出的一个基于 WebAssembly 的当代 Web 应用步伐框架,它允许开辟者使用 C# 和.NET 技术构建客户端 Web 应用步伐,而无需使用 JavaScript。在传统的 Web 开辟中,前端主要依赖 JavaScript 来实现各种交互逻辑和功能。而 Blazor WebAssembly 打破了这一通例,它将 C# 代码编译为 WebAssembly 字节码,然后在欣赏器中运行。
WebAssembly 是一种新型的二进制格式,它可以在当代欣赏器中以接近原生的性能运行。简单来说,WebAssembly 就像是一个翻译器,它可以或许将高级编程语言(如 C#、C++、Rust 等)编写的代码转换为一种高效的、可以在欣赏器中直接执行的格式。对于 Blazor WebAssembly 而言,它借助 WebAssembly 的能力,让 C# 代码可以或许在欣赏器环境中运行,从而实现了用 C# 进行前端开辟的可能。
例如,我们可以编写一个简单的 C# 方法,在 Blazor WebAssembly 应用中实现一个计数器功能:
private int count = 0;
private void IncrementCount()
{
    count++;
}
在上述代码中,IncrementCount方法用于增加计数器的值。这段 C# 代码会被编译为 WebAssembly 字节码,在欣赏器中运行时,用户点击相应的按钮(通过绑定该方法到按钮的点击变乱),就能实现计数器的功能,而无需使用 JavaScript 来编写变乱处理逻辑。
(二)与传统前端开辟的区别


[*] 语言方面:传统前端开辟主要使用 JavaScript 语言,它是一种弱类型、动态的脚本语言。而 Blazor WebAssembly 使用 C# 语言,C# 是一种强类型、面向对象的编程语言,具有严酷的类型检查和丰富的语法特性。例如,在 JavaScript 中界说变量时,无需显式指定类型,如let num = 10;;而在 C# 中则须要明确指定类型,如int num = 10;。这种强类型特性可以在编译阶段发现更多的错误,提高代码的稳定性和可维护性。
[*] 开辟模式方面:传统前端开辟通常基于 JavaScript 框架(如 React、Vue 等),开辟者须要熟悉这些框架的特定语法和开辟模式。例如,React 使用 JSX 语法来描述 UI,Vue 则有自己的模板语法。而 Blazor WebAssembly 采用基于组件的开辟模式,使用 Razor 语法来构建用户界面。Razor 语法结合了 HTML 和 C# 代码,使得开辟者可以在同一个文件中编写 UI 和业务逻辑,例如:
@page "/"
<h1>Hello, Blazor!</h1>
<p>Count: @count</p>
<button @onclick="IncrementCount">Increment</button>

@code {
    private int count = 0;
    private void IncrementCount()
    {
      count++;
    }
}

[*]性能方面:WebAssembly 的性能优势使得 Blazor WebAssembly 应用在执行效率上有了很大提升。WebAssembly 代码颠末高度优化,可以或许在欣赏器中快速执行,接近原生应用的性能。相比之下,JavaScript 代码在执行前须要颠末解析和编译,这在一定水平上会影响性能。尤其是对于复杂的计算使命或大型应用,Blazor WebAssembly 的性能优势更加明显。
(三)优势亮点


[*] 使用熟悉的 C# 技术栈:对于 C# 开辟者来说,使用 Blazor WebAssembly 进行前端开辟意味着可以在前后端都使用同一门语言。这大大镌汰了学习成本,开辟者无需再耗费大量时间学习 JavaScript 和各种前端框架。他们可以使用已有的 C# 知识和经验,快速构建出功能强大的 Web 应用。例如,在开辟一个企业级管理体系时,后端使用ASP.NET Core 进行 API 开辟,前端使用 Blazor WebAssembly,开辟者可以在整个项目中同一使用 C# 语言,共享代码逻辑和类库,提高开辟效率。
[*] 高性能:由于 WebAssembly 的特性,Blazor WebAssembly 应用可以或许以接近原生的性能在欣赏器中运行。这使得应用在处理复杂的业务逻辑和大量数据时,可以或许保持流畅的运行速率,提供更好的用户体验。好比,在开辟一个数据可视化的 Web 应用时,须要实时处理和展示大量的实时数据,Blazor WebAssembly 可以高效地完成这些使命,确保数据的实时更新和图表的流畅渲染。
[*] 离线支持:Blazor WebAssembly 应用可以通过 Service Worker 实现离线支持,这意味着用户在没有网络连接的情况下也可以或许访问应用。这对于一些须要在移动装备上使用大概在网络不稳定环境下运行的应用来说非常重要。例如,一款移动办公应用,用户在乘坐地铁大概在偏远地区没有网络时,仍然可以使用应用查看和编辑本地的文档、数据等。
[*] 跨平台:Blazor WebAssembly 应用可以在各种当代欣赏器上运行,无论是桌面端的 Chrome、Firefox、Edge,照旧移动端的 Safari、Chrome for Android 等。这使得开辟者可以一次编写代码,在多个平台上部署,降低了开辟和维护成本。好比,开辟一款在线教育应用,门生可以通过差别的装备和欣赏器访问该应用,进行课程学习、作业提交等操作。
三、开辟环境搭建

(一)必备工具


[*]安装 Visual Studio:


[*]

[*]首先,前往Visual Studio 官方下载页面。

[*]

[*]根据你的操作体系(Windows、Mac 等)选择对应的版本进行下载。以 Windows 体系为例,点击下载按钮后,运行安装步伐。

[*]

[*]在安装步伐中,选择 “ASP.NET和 Web 开辟” 工作负载,确保勾选了 “.NET Core 跨平台开辟” 选项。这些选项将为你提供开辟 Blazor WebAssembly 项目所需的工具和模板。

[*]

[*]选择安装位置,点击 “安装” 按钮,等待安装完成。安装完成后,启动 Visual Studio,你可以在 “开始” 菜单中找到它。


[*]安装 Visual Studio Code:


[*]

[*]访问Visual Studio Code 官方网站。

[*]

[*]点击 “下载” 按钮,选择得当你操作体系的版本进行下载。

[*]

[*]下载完成后,运行安装步伐,按照提示完成安装。

[*]

[*]安装完成后,打开 Visual Studio Code。在扩展市肆中搜索并安装 “C#” 扩展,这将为你提供 C# 语言的开辟支持,包括语法高亮、智能代码补全等功能。


[*]安装最新版.NET SDK:


[*]

[*]前往.NET 官方下载页面。

[*]

[*]下载实用于你操作体系的最新版.NET SDK。例如,如果你使用的是 Windows 体系,下载对应的 Windows 安装包。

[*]

[*]运行下载的安装包,按照安装向导的提示完成安装。安装过程中,你可以选择安装位置和其他相关选项。

[*]

[*]安装完成后,打开命令提示符或终端,输入 “dotnet --version” 命令,如果安装成功,你将看到当前安装的.NET SDK 版本号。

(二)创建项目


[*]在命令行中创建项目:


[*]

[*]打开命令提示符或终端。

[*]

[*]输入以下命令创建一个新的 Blazor WebAssembly 项目:

dotnet new blazorwasm -o MyBlazorApp


[*] 上述命令中,“dotnet new” 是创建新项目的命令,“blazorwasm” 指定项目模板为 Blazor WebAssembly 应用,“-o” 参数指定项目输出目录为 “MyBlazorApp”,你可以将其更换为你想要的项目名称。
[*] 执行命令后,体系会在指定目录下创建一个新的 Blazor WebAssembly 项目,包括项目所需的文件和文件夹布局。
[*] 进入项目目录:
cd MyBlazorApp

[*]在 Visual Studio 中创建项目:


[*]

[*]打开 Visual Studio。

[*]

[*]在启动界面中,点击 “创建新项目”。

[*]

[*]在项目模板搜索框中输入 “Blazor WebAssembly”,然后选择 “Blazor WebAssembly App” 模板,点击 “下一步”。

[*]

[*]在 “配置新项目” 页面,输入项目名称,选择项目存放位置,然后点击 “创建”。

[*]

[*]在 “创建 Blazor WebAssembly 应用” 页面,你可以选择是否使用ASP.NET Core 托管(得当须要后端支持的场景),是否启用 PWA(渐进式 Web 应用)支持等选项,根据需求选择后点击 “创建”。

(三)项目布局剖析


[*] wwwroot 文件夹:这个文件夹用于存放静态文件,如 CSS 样式表、JavaScript 文件、图像文件、字体文件等。这些文件会直接被发布到最终的 Web 应用中,供欣赏器加载和使用。例如,如果你在项目中引入了一个自界说的 CSS 文件来设置页面样式,就须要将其放在 wwwroot 文件夹下,然后在 Razor 组件中通过标签引用它。
[*] Pages 文件夹:Pages 文件夹用于存放 Razor 页面文件(.razor 文件)。每个 Razor 页面都是一个组件,包罗了前端的 HTML 标志和 C# 代码逻辑。这些组件构成了应用的用户界面,例如,Index.razor通常是应用的首页组件,Counter.razor可能是一个实现计数器功能的组件。在 Razor 页面中,你可以使用@page指令来界说页面的路由,例如@page "/"表现该页面是应用的根页面。
[*] Shared 文件夹:Shared 文件夹用于存放共享组件和布局文件。共享组件是可以在多个页面中重复使用的组件,好比导航栏组件、页脚组件等。布局文件(如MainLayout.razor)用于界说整个应用的布局布局,其他页面可以通过继续该布局来获得同一的表面和布局。例如,MainLayout.razor中可能包罗了导航栏、侧边栏和内容地区的布局界说,其他页面只须要在顶部使用@layout MainLayout指令来应用该布局。
[*] Program.cs 文件:Program.cs 是应用的入口点,它负责启动应用并配置应用的服务和中央件。在这个文件中,你可以进行一些初始化操作,如注册依赖项、配置路由、设置应用的启动选项等。例如,通过builder.Services.AddScoped()可以注册一个 HttpClient 服务,用于在应用中进行 HTTP 请求。
[*] 其他文件:除了上述主要的文件和文件夹外,项目中还包罗一些其他文件,如.csproj文件,它是项目的配置文件,包罗了项目的基本信息、依赖项、编译选项等;_Imports.razor文件用于界说全局的导入指令,在其中导入的定名空间可以在全部的 Razor 组件中使用,无需在每个组件中单独导入。
四、核心概念与技术

(一)Razor 语法

Razor 语法是 Blazor 中用于构建用户界面的关键技术,它巧妙地将 HTML 与 C# 代码融合在一起,为开辟者提供了一种轻便而高效的编程体验。在 Blazor 项目中,Razor 文件(.razor)是主要的代码载体,开辟者可以在同一个文件中同时编写前端的 UI 布局和后端的业务逻辑,这大大提高了代码的可读性和可维护性。
在 Razor 语法中,使用@符号来标识 C# 代码块或表达式。例如,要在 HTML 中显示一个 C# 变量的值,可以这样写:
<p>当前计数: @count</p>
这里的@count就是一个 Razor 表达式,它会在运行时被更换为count变量的实际值。
Razor 还支持各种指令,这些指令用于控制页面的行为和布局。其中,@page指令用于界说页面的路由,例如:
@page "/home"
表现该页面的路由为/home,当用户访问这个 URL 时,对应的组件就会被渲染。
此外,@using指令用于导入定名空间,使得在当前组件中可以使用该定名空间下的类型。好比:
@using System.Net.Http
通过这条指令,我们就可以在组件中使用System.Net.Http定名空间下的HttpClient等类型,方便进行 HTTP 请求操作。
(二)组件化开辟

组件化开辟是 Blazor 的核心特性之一,它允许开辟者将复杂的用户界面拆分成一个个独立的、可复用的组件,每个组件都有自己的逻辑和样式,通过组合这些组件,可以构建出复杂的界面。
创建一个 Blazor 组件非常简单,只须要创建一个以.razor为后缀的文件,在文件中界说组件的 UI 和逻辑。例如,下面是一个简单的计数器组件:
@page "/counter"<h1>计数器</h1><p>当前计数: @count</p>
<button @onclick="IncrementCount">增加计数</button>
@code {    private int count = 0;    private void IncrementCount()    {      count++;    }} 在这个组件中,@page "/counter"界说了组件的路由,用户访问/counter时会看到这个组件。
、和标签界说了组件的 UI,@code块中包罗了组件的 C# 逻辑代码,IncrementCount方法用于增加计数器的值。



组件之间可以通过参数传递数据,实现数据的共享和交互。在父组件中使用子组件时,可以通过属性绑定的方式将数据传递给子组件。例如,假设有一个ChildComponent子组件,它有一个Message参数:
<ChildComponent Message="Hello, Blazor!" />
在子组件ChildComponent.razor中,可以这样接收参数:
@typeparam string Message

<p>@Message</p>
这里的@typeparam指令用于声明组件的参数类型。
每个组件都有自己的生命周期,包括初始化、渲染、更新和销毁等阶段。在这些阶段中,开辟者可以执行一些特定的操作。例如,OnInitializedAsync方法会在组件初始化后异步执行,通常用于加载数据等操作:
protected override async Task OnInitializedAsync()
{
    // 从API获取数据
    var data = await Http.GetFromJsonAsync<List<WeatherForecast>>("weatherforecast");
    forecasts = data;
}
(三)数据绑定与变乱处理

数据绑定是 Blazor 实现数据与 UI 同步的重要机制,它分为单向数据绑定和双向数据绑定。
单向数据绑定是指数据从模型流向视图,当模型数据发生厘革时,UI 会主动更新。在 Blazor 中,使用@符号实现单向数据绑定。例如:
<p>当前计数: @count</p>
当count变量的值发生厘革时,
标签中的文本会主动更新。
双向数据绑定则允许数据在模型和视图之间双向流动,用户在 UI 上的操作会实时反映到模型中,模型的厘革也会立刻更新到 UI 上。Blazor 使用@bind指令实现双向数据绑定,主要用于表单元素等场景。例如:
<input @bind="userName" />
<p>用户名: @userName</p>
在这个例子中,当用户在输入框中输入内容时,userName变量的值会实时更新;反之,当userName变量的值在代码中被修改时,输入框中的内容也会相应改变。
变乱处理是 Blazor 实现用户交互的关键,通过绑定变乱处理方法,当用户触发某个变乱(如点击按钮、输入内容等)时,相应的方法会被执行。例如,在前面的计数器组件中,按钮的点击变乱绑定了IncrementCount方法:
<button @onclick="IncrementCount">增加计数</button>
当用户点击按钮时,IncrementCount方法会被调用,实现计数器增加的功能。
(四)依赖注入

依赖注入是一种软件筹划模式,它允许将依赖对象(如服务、数据库连接等)通过外部提供的方式注入到组件中,而不是在组件内部直接创建,这样可以提高代码的可测试性、可维护性和可扩展性。
在 Blazor 中,使用.NET Core 内置的依赖注入容器来管理依赖关系。首先,须要在Program.cs文件中注册服务。例如,注册一个WeatherForecastService服务:
builder.Services.AddScoped<WeatherForecastService>();
这里使用AddScoped方法表现该服务在每个请求范围内是唯一的,即每次请求都会创建一个新的服务实例。
在组件中使用依赖注入时,可以通过@inject指令大概属性注入的方式获取服务实例。例如:
@page "/fetchdata"
@inject WeatherForecastService ForecastService

<h1>天气预报</h1>
@if (forecasts == null)
{
    <p><em>加载中...</em></p>
}
else
{
    <table class="table">
      <thead>
            <tr>
                <th>日期</th>
                <th>温度 (C)</th>
                <th>温度 (F)</th>
                <th>摘要</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 ForecastService.GetForecastAsync(DateTime.Now);
    }
}
在这个组件中,通过@inject WeatherForecastService ForecastService注入了WeatherForecastService服务,然后在OnInitializedAsync方法中使用该服务获取天气预报数据并显示在页面上。
(五)路由与导航

路由是 Blazor 应用中实现页面导航和页面切换的关键机制,它允许根据差别的 URL 地址加载相应的组件。在 Blazor 中,使用@page指令来界说组件的路由。例如:
@page "/home"
<h1>首页</h1> 表现该组件的路由为/home,当用户访问/home时,这个组件会被渲染。
实现页面导航可以使用NavLink组件,它会根据当前的 URL 主动添加或移除active类,以指示当前激活的链接。例如:
<NavLink href="/home">首页</NavLink>
<NavLink href="/counter">计数器</NavLink>
<NavLink href="/fetchdata">获取数据</NavLink>
点击这些链接时,会根据href属性的值进行页面导航,加载相应的组件。
在处理路由参数时,Blazor 支持在路由中界说参数。例如,界说一个带有参数的路由:
@page "/product/{productId}"
这里的{productId}就是一个路由参数,在组件中可以通过特性来接收这个参数:
@page "/product/{productId}"
@using System.Net.Http
@inject HttpClient Http<h1>产品详情</h1>@if (product == null){    <p><em>加载中...</em></p>}else{    <p>产品ID: @product.Id</p>    <p>产品名称: @product.Name</p>    <p>产品价格: @product.Price</p>}@code {        public int productId { get; set; }    private Product product;    protected override async Task OnInitializedAsync()    {      product = await Http.GetFromJsonAsync<Product>($"products/{productId}");    }} 在这个例子中,productId参数会在组件初始化时被赋值,然后通过 HTTP 请求获取对应的产品数据并显示在页面上。
五、实战案例:构建一个简单的 Blazor WebAssembly 应用

(一)需求分析

本次我们要构建一个简单的使命管理应用,它主要包罗以下功能:

[*] 展示使命列表:可以或许从数据源获取使命数据,并以列表形式展示在页面上,每个使命须要显示使命名称、创建时间、截止时间和使命状态等信息。
[*] 添加使命:提供一个表单,用户可以在其中输入使命名称、截止时间等信息,点击提交按钮后,新使命可以或许被添加到使命列表中,并保存到数据源。
[*] 删除使命:在使命列表的每一项旁边提供删除按钮,用户点击删除按钮后,对应的使命可以或许从使命列表和数据源中移除。
[*] 页面导航:应用包罗多个页面,如使命列表页面、添加使命页面等,用户可以通过导航栏在差别页面之间进行切换。
(二)筹划与实现


[*]创建项目:打开命令提示符或终端,使用以下命令创建一个新的 Blazor WebAssembly 项目:
dotnet new blazorwasm -o TaskManagerApp
然后进入项目目录:
cd TaskManagerApp

[*]创建数据模型:在项目中创建一个Task.cs文件,界说使命的数据模型:
public class Task
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime DueTime { get; set; }
    public bool IsCompleted { get; set; }
}

[*]创建数据访问服务:创建一个TaskService.cs文件,用于处理与使命数据相关的操作,如获取使命列表、添加使命、删除使命等。这里我们先使用内存数据来模仿数据存储,后续可以根据实际需求更换为数据库访问。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class TaskService
{
    private List<Task> tasks = new List<Task>();

    public TaskService()
    {
      // 初始化一些示例数据
      tasks.Add(new Task
      {
            Id = 1,
            Name = "学习Blazor WebAssembly",
            CreatedTime = DateTime.Now,
            DueTime = DateTime.Now.AddDays(3),
            IsCompleted = false
      });
      tasks.Add(new Task
      {
            Id = 2,
            Name = "完成项目文档",
            CreatedTime = DateTime.Now,
            DueTime = DateTime.Now.AddDays(1),
            IsCompleted = false
      });
    }

    public async Task<List<Task>> GetTasksAsync()
    {
      return tasks;
    }

    public async Task AddTaskAsync(Task newTask)
    {
      newTask.Id = tasks.Count + 1;
      newTask.CreatedTime = DateTime.Now;
      tasks.Add(newTask);
    }

    public async Task DeleteTaskAsync(int id)
    {
      var taskToDelete = tasks.FirstOrDefault(t => t.Id == id);
      if (taskToDelete!= null)
      {
            tasks.Remove(taskToDelete);
      }
    }
}

[*]创建组件:


[*]

[*]使命列表组件(TaskList.razor):用于展示使命列表,并提供删除使命的功能。

@page "/tasks"
@using TaskManagerApp
@inject TaskService TaskService

<h1>任务列表</h1>

<ul>
    @foreach (var task in tasks)
    {
      <li>
            @task.Name - 创建时间: @task.CreatedTime - 截止时间: @task.DueTime - 状态: @(task.IsCompleted? "已完成" : "未完成")
            <button @onclick="@(() => DeleteTask(task.Id))">删除</button>
      </li>
    }
</ul>

<NavLink href="/addtask">添加任务</NavLink>

@code {
    private List<Task> tasks;

    protected override async Task OnInitializedAsync()
    {
      tasks = await TaskService.GetTasksAsync();
    }

    private async Task DeleteTask(int id)
    {
      await TaskService.DeleteTaskAsync(id);
      tasks = await TaskService.GetTasksAsync();
      StateHasChanged();
    }
}


[*]添加使命组件(AddTask.razor):提供一个表单,用于用户添加新使命。
@page "/addtask"
@using TaskManagerApp
@inject TaskService TaskService
@inject NavigationManager NavigationManager

<h1>添加任务</h1>

<EditForm Model="newTask" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="mb-3">
      <label for="taskName" class="form-label">任务名称</label>
      <InputText id="taskName" @bind-Value="newTask.Name" class="form-control" />
    </div>

    <div class="mb-3">
      <label for="dueTime" class="form-label">截止时间</label>
      <InputDate id="dueTime" @bind-Value="newTask.DueTime" class="form-control" />
    </div>

    <button type="submit" class="btn btn-primary">提交</button>
</EditForm>

@code {
    private Task newTask = new Task();

    private async Task HandleValidSubmit()
    {
      await TaskService.AddTaskAsync(newTask);
      NavigationManager.NavigateTo("/tasks");
    }
}

[*]配置路由:在App.razor文件中,配置应用的路由:
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
      <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
      <LayoutView Layout="@typeof(MainLayout)">
            <p>抱歉,未找到该页面。</p>
      </LayoutView>
    </NotFound>
</Router>

[*]注册服务:在Program.cs文件中,注册TaskService服务:
builder.Services.AddScoped<TaskService>();

[*]添加导航栏:在Shared文件夹下的NavMenu.razor文件中,添加导航链接:
<NavLink href="/tasks" Match="NavLinkMatch.All">任务列表</NavLink>
<NavLink href="/addtask" Match="NavLinkMatch.All">添加任务</NavLink>
(三)效果展示与题目解决


[*] 效果展示:运行应用后,在欣赏器中访问https://localhost:5001(端口号可能因实际情况而异),可以看到使命列表页面,显示了初始化的使命数据,而且可以点击 “删除” 按钮删除使命,点击 “添加使命” 链接跳转到添加使命页面。在添加使命页面,填写使命信息并提交后,会主动跳转到使命列表页面,新添加的使命也会显示在列表中。
[*] 题目解决:


[*]

[*]题目:在开辟过程中,发现点击删除按钮后,使命列表没有实时更新。

[*]

[*]缘故原由:Blazor 在数据厘革后,须要手动通知组件进行重新渲染。

[*]

[*]解决方案:在删除使命的方法中,调用StateHasChanged()方法,通知组件重新渲染,如上述TaskList.razor组件中的DeleteTask方法所示。

[*]

[*]题目:在添加使命时,输入的截止时间格式不正确会导致验证失败,但没有明确的错误提示。

[*]

[*]缘故原由:默认的验证提示不敷直观。

[*]

[*]解决方案:在AddTask.razor组件中,使用DataAnnotationsValidator和ValidationSummary组件,为表单添加具体的验证提示,当输入格式不正确时,会在页面上显示相应的错误信息。

六、性能优化与安全考量

(一)性能优化策略


[*]代码分割与懒加载:随着 Blazor WebAssembly 应用的功能不停增加,代码体积也会渐渐增大,这可能会导致应用的初次加载时间变长。代码分割技术可以将应用的代码分割成多个小块,只有在须要时才加载相应的代码块,从而提高应用的初始加载速率。在 Blazor 中,可以使用LazyComponentLoader组件来实现组件的懒加载。例如:
<LazyComponentLoader Name="MyComponent" />
这里的MyComponent会在须要显示时才被加载,而不是在应用启动时就全部加载。

[*]缓存机制:公道使用缓存可以镌汰重复的数据请求和计算,提高应用的性能。对于一些不经常厘革的数据,如配置信息、静态数据等,可以在客户端进行缓存。可以使用MemoryCache或DistributedCache来实现缓存功能。在TaskService中,可以添加缓存逻辑来缓存使命列表数据:
private readonly IMemoryCache _memoryCache;

public TaskService(IMemoryCache memoryCache)
{
    _memoryCache = memoryCache;
}

public async Task<List<Task>> GetTasksAsync()
{
    if (!_memoryCache.TryGetValue("Tasks", out List<Task> tasks))
    {
      // 从数据源获取任务数据
      tasks = await GetTasksFromDataSource();
      // 将任务数据缓存起来
      _memoryCache.Set("Tasks", tasks, TimeSpan.FromMinutes(10));
    }
    return tasks;
}

[*]性能监控与评估:使用工具如 Chrome DevTools 来监控应用的性能,包括加载时间、资源使用情况、CPU 和内存占用等。在 Chrome 欣赏器中,打开 DevTools,切换到 “Performance” 标签页,点击录制按钮,然后在应用中进行各种操作,停止录制后,就可以查看具体的性能分析报告,找出性能瓶颈地点。
(二)安全题目与防护


[*] XSS 攻击(跨站脚本攻击)防护:XSS 攻击是一种常见的 Web 安全漏洞,攻击者通过在网页中注入恶意脚本,从而获取用户的敏感信息。在 Blazor 中,默认会对输出进行 HTML 编码,防止 XSS 攻击。例如,在显示用户输入的内容时,Blazor 会主动对特殊字符进行编码,避免脚本注入。但在使用@Html.Raw等方法输出原始 HTML 内容时,须要特殊警惕,确保内容是可信的。如果要显示用户输入的富文本内容,可以使用一些安全的富文本编辑器,如 TinyMCE,它会对输入的内容进行过滤和清理,防止恶意脚本注入。
[*] CSRF 攻击(跨站请求伪造攻击)防护:CSRF 攻击是攻击者使用用户已登录的身份,在用户不知情的情况下执行恶意操作。Blazor 应用可以通过在请求中添加 CSRF 令牌来防止这种攻击。在_Layout.cshtml文件中,可以添加 CSRF 令牌:
<input type="hidden" name="__RequestVerificationToken" value="@(await Html.AntiForgeryTokenAsync())" />
服务器在接收到请求时,会验证令牌的有效性,如果令牌无效,则拒绝请求。
\3. 输入验证:对用户输入的数据进行严酷的验证是防止安全漏洞的重要措施。在 Blazor 中,可以使用数据注解(如、等)来进行输入验证。在Task模型中,可以添加数据注解:
public class Task
{
    public int Id { get; set; }
   
   
    public string Name { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime DueTime { get; set; }
    public bool IsCompleted { get; set; }
}
在AddTask.razor组件中,使用EditForm和DataAnnotationsValidator组件来进行表单验证,确保用户输入的数据符合要求。
七、与后端服务集成

(一)调用 API

在 Blazor WebAssembly 应用中,与后端服务进行集成是实现丰富功能的关键步调。调用后端的 RESTful API 是获取和提交数据的常见方式,而HttpClient则是 Blazor 中用于进行 HTTP 请求的重要工具。
首先,在Program.cs文件中注册HttpClient服务:
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
上述代码将HttpClient注册为作用域服务,并设置了请求的基地址。在实际应用中,你可以根据后端 API 的地址进行相应的修改。
接下来,在组件中注入HttpClient并发起请求。例如,在一个获取产品列表的组件中:
@page "/products"
@inject HttpClient Http

<h1>产品列表</h1>

@if (products == null)
{
    <p><em>加载中...</em></p>
}
else
{
    <ul>
      @foreach (var product in products)
      {
            <li>@product.Name - @product.Price</li>
      }
    </ul>
}

@code {
    private Product[] products;

    protected override async Task OnInitializedAsync()
    {
      products = await Http.GetFromJsonAsync<Product[]>("api/products");
    }
}
在这个例子中,Http.GetFromJsonAsync方法用于发送 GET 请求到api/products端点,并将相应数据反序列化为Product数组。该方法会主动处理 JSON 数据的解析,非常方便。
如果须要发送 POST 请求来创建新的产品,可以这样实现:
private async Task CreateProduct(Product newProduct)
{
    var response = await Http.PostAsJsonAsync("api/products", newProduct);
    if (response.IsSuccessStatusCode)
    {
      // 处理成功响应
      var createdProduct = await response.Content.ReadFromJsonAsync<Product>();
      // 更新产品列表
      products = products.Append(createdProduct).ToArray();
      StateHasChanged();
    }
    else
    {
      // 处理错误响应
      Console.WriteLine($"请求失败,状态码: {response.StatusCode}");
    }
}
在上述代码中,Http.PostAsJsonAsync方法用于发送 POST 请求,将新的产品数据以 JSON 格式发送到api/products端点。根据相应的状态码来判断请求是否成功,如果成功则读取相应数据并更新产品列表。
(二)数据交互格式

在前后端交互中,数据交互格式的选择至关重要。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,由于其轻便、易读、易于解析和生成的特点,在 Web 开辟中被广泛应用,Blazor 也不例外。
JSON 数据以键值对的形式构造,例如:
{
    "name": "Blazor Book",
    "price": 49.99,
    "isAvailable": true
}
在 Blazor 中,处理 JSON 数据非常方便。前面提到的HttpClient的GetFromJsonAsync和PostAsJsonAsync等方法,就是专门用于处理 JSON 数据的。这些方法基于System.Text.Json定名空间下的功能,可以或许主动将 JSON 数据与 C# 对象进行相互转换。
除了使用HttpClient的便捷方法外,还可以手动使用System.Text.Json定名空间下的类来处理 JSON 数据。例如,将一个 C# 对象序列化为 JSON 字符串:
using System.Text.Json;

var product = new Product { Name = "New Product", Price = 29.99, IsAvailable = true };
string json = JsonSerializer.Serialize(product);
上述代码使用JsonSerializer.Serialize方法将Product对象转换为 JSON 字符串。
反之,将 JSON 字符串反序列化为 C# 对象:
string json = "{\"name\":\"New Product\",\"price\":29.99,\"isAvailable\":true}";
Product product = JsonSerializer.Deserialize<Product>(json);
这里使用JsonSerializer.Deserialize方法将 JSON 字符串转换为Product对象。在反序列化时,要确保 C# 对象的属性名称与 JSON 中的键名同等,大概通过JsonPropertyName特性进行映射。
(三)状态管理与同步

在前后端交互过程中,状态管理和数据同步是确保应用步伐正常运行和用户体验的重要环节。由于 Blazor 应用是基于组件的,差别组件之间可能须要共享和同步数据,同时在与后端交互时,也须要包管数据的同等性。
一种简单的状态管理方式是通过依赖注入实现。例如,创建一个状态管理服务ProductStateService:
public class ProductStateService
{
    private Product[] products;

    public Product[] Products
    {
      get => products;
      set
      {
            products = value;
            StateChanged?.Invoke();
      }
    }

    public event Action StateChanged;
}
在这个服务中,Products属性用于存储产品数据,当该属性的值发生厘革时,会触发StateChanged变乱。
在Program.cs文件中注册该服务:
builder.Services.AddSingleton<ProductStateService>();
在组件中注入并使用该服务:
@page "/products"
@inject ProductStateService ProductState
@inject HttpClient Http

<h1>产品列表</h1>

@if (ProductState.Products == null)
{
    <p><em>加载中...</em></p>
}
else
{
    <ul>
      @foreach (var product in ProductState.Products)
      {
            <li>@product.Name - @product.Price</li>
      }
    </ul>
}

@code {
    protected override async Task OnInitializedAsync()
    {
      ProductState.Products = await Http.GetFromJsonAsync<Product[]>("api/products");
    }
}
当后端数据发生厘革时,可以通过调用ProductStateService的方法来更新状态,并触发StateChanged变乱,从而通知相关组件进行重新渲染,实现数据同步。
对于更复杂的状态管理场景,可以使用第三方状态管理库,如 Fluxor。Fluxor 基于 Redux 的架构头脑,提供了一种单向数据流的状态管理模式,使得状态的厘革更加可猜测和易于维护。使用 Fluxor 时,须要界说状态、动作和归约函数,通过分发动作来更新状态。例如:

[*] 安装Fluxor.Blazor.Web包。
[*] 在Program.cs中添加:
var currentAssembly = typeof(Program).Assembly;
builder.Services.AddFluxor(options => options.ScanAssemblies(currentAssembly));

[*]界说状态类:
using Fluxor;

namespace BlazorApp.Store;


public class ProductState
{
    public Product[] Products { get; private set; }

    private ProductState() { }

    public ProductState(Product[] products)
    {
      Products = products;
    }
}

[*]界说动作类:
namespace BlazorApp.Store;

public class LoadProductsAction { }

[*]界说归约函数:
using Fluxor;

namespace BlazorApp.Store;

public static class ProductReducers
{
   
    public static ProductState ReduceLoadProductsAction(ProductState state, LoadProductsAction action, HttpClient http)
    {
      var products = http.GetFromJsonAsync<Product[]>("api/products").Result;
      return new ProductState(products);
    }
}

[*]在组件中使用:
@page "/products"
@inject IDispatcher Dispatcher
@inject IState<ProductState> ProductState

<h1>产品列表</h1>

@if (ProductState.Value.Products == null)
{
    <p><em>加载中...</em></p>
}
else
{
    <ul>
      @foreach (var product in ProductState.Value.Products)
      {
            <li>@product.Name - @product.Price</li>
      }
    </ul>
}

@code {
    protected override void OnInitialized()
    {
      Dispatcher.Dispatch(new LoadProductsAction());
    }
}
通过这种方式,Fluxor 帮助我们更好地管理复杂的状态,确保在前后端交互中数据的同等性和可维护性。
八、总结与展望

Blazor WebAssembly 作为一种创新的 Web 开辟技术,为开辟者带来了诸多便利与优势。它打破了传统前端开辟对 JavaScript 的单一依赖,允许开辟者使用 C# 这一强大的编程语言进行前端开辟。通过将 C# 代码编译为 WebAssembly 字节码在欣赏器中运行,Blazor WebAssembly 实现了高性能的前端应用,为用户提供了流畅的交互体验。
在开辟流程上,我们首先须要搭建好开辟环境,包括安装必备的工具如 Visual Studio 或 Visual Studio Code,以及最新版的.NET SDK。创建项目后,深入了解项目布局,如 wwwroot 文件夹用于存放静态文件,Pages 文件夹存放 Razor 页面组件,Shared 文件夹用于共享组件和布局文件等,这有助于我们更好地构造和管理代码。
核心概念与技术方面,Razor 语法让 HTML 与 C# 代码完美融合,实现了轻便高效的 UI 开辟;组件化开辟模式将复杂的界面拆分成可复用的组件,提高了代码的可维护性和可扩展性;数据绑定与变乱处理机制实现了数据与 UI 的同步以及用户与应用的交互;依赖注入则提高了代码的可测试性和可维护性;路由与导航功能实现了页面之间的切换和参数传递。
通过构建简单的使命管理应用这一实战案例,我们亲身体验了 Blazor WebAssembly 从需求分析、筹划实现到效果展示与题目解决的全过程,进一步加深了对其开辟流程和技术应用的理解。在性能优化与安全考量上,我们探讨了代码分割与懒加载、缓存机制等性能优化策略,以及 XSS 攻击防护、CSRF 攻击防护和输入验证等安全措施。
在与后端服务集成时,使用 HttpClient 调用 API 实现了前后端的数据交互,JSON 作为主要的数据交互格式,轻便高效。同时,通过依赖注入或第三方库如 Fluxor 进行状态管理与同步,确保了数据的同等性和应用的稳定性。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: C#逆袭前端:Blazor WebAssembly 全栈开辟揭秘