-
C#中利用Blazor WebAssembly——单页应用开发
第四部分:跨平台应用开发
2. Blazor WebAssembly——单页应用开发
实例介绍
之前做企业管理系统时,前后端用不同技术栈:前端Vue+JavaScript,后端C#+.NET,调试要切换两个IDE,跨语言调试麻烦,前后端代码复用率几乎为0。换成Blazor WebAssembly后,全栈用C#开发,前端组件直接vb.net教程C#教程python教程SQL教程access 2010教程调用后端服务,代码复用率提升50%,调试在Visual Studio里一站式完成,开发效率翻了一倍。这节就带你从零实现Blazor WebAssembly单页应用,包括组件化开发、数据绑定、API集成、状态管理、PWA支持,覆盖单页应用的核心需求。
需求分析
Blazor WebAssembly单页应用要解决“全栈C#开发、组件复用、数据同步、性能优化、跨平台支持、离线运行”的问题,具体需求如下:
1.全栈C#开发:前端用Razor组件(HTML+C#),后端用ASP.NET Core,统一技术栈;
2.组件化架构:将UI拆分为独立组件(如表格、表单、弹窗),复用率高;
3.数据绑定:支持单向、双向、事件绑定,UI与数据自动同步;
4.API集成:前端组件直接调用后端API,无需编写JavaScript;
5.状态管理:管理全局状态(如用户信息、主题设置),支持跨组件共享;
6.路由导航:单页应用路由,支持参数传递、嵌套路由;
7.性能优化:组件懒加载、预渲染、资源压缩,提升首屏加载速度;
8.PWA支持:离线运行、桌面安装、推送通知,像原生APP一样使用;
9.实时通信:集成SignalR,实现实时数据更新(如聊天、通知);
10.可扩展性:支持第三方组件库(如MudBlazor、Blazored),快速构建复杂UI。
代码实现
前置条件:.NET 8 SDK;Visual Studio 2022(17.8+),安装“ASP.NET和Web开发”工作负载;需安装以下NuGet包:
Microsoft.AspNetCore.Components.WebAssembly:Blazor WebAssembly核心包
Microsoft.AspNetCore.Components.WebAssembly.Authentication:身份验证(可选)
Blazored.LocalStorage:本地存储(可选)
MudBlazor:UI组件库(可选)
场景1:项目创建与基础结构搭建
从零创建Blazor WebAssembly项目,了解项目结构和核心文件。
步骤1:创建Blazor WebAssembly项目
1.打开Visual Studio → 新建项目 → 搜索“Blazor WebAssembly” → 选择“Blazor WebAssembly应用”模板;
2.命名项目为BlazorWasmSpa → 选择.NET 8框架;
3.选择“独立”模式(前端单独部署)或“托管”模式(前后端同项目) → 点击“创建”;
步骤2:项目结构说明
Pages:存放页面组件(如Index.razor、Counter.razor),每个组件用@page指定路由;
Shared:存放共享组件(如MainLayout.razor布局、NavMenu.razor导航栏);
wwwroot:静态资源(CSS、JS、图片、index.html入口文件);
Program.cs:应用启动配置,注册服务、添加组件;
_Imports.razor:全局导入命名空间(如@using Microsoft.AspNetCore.Components.Web);
步骤3:运行项目
1.点击“调试”按钮,项目编译后会在浏览器中打开http://localhost:5000;
2.查看默认页面:首页、计数器、天气预测,体验Blazor的组件化开发;
场景2:组件化开发(计数器组件)
实现一个可复用的计数器组件,展示Blazor的核心特性:声明式UI、事件绑定、状态管理。
步骤1:创建计数器组件(Pages/Counter.razor)
razor
@page "/counter"
@rendermode InteractiveWebAssembly <!-- 启用交互式渲染 -->
<h3>计数器</h3>
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">当前计数: @CurrentCount</h5>
<p class="card-text">点击按钮增加计数</p>
<button class="btn btn-primary" @onclick="IncrementCount">点击我</button>
<button class="btn btn-secondary ms-2" @onclick="ResetCount">重置</button>
</div>
</div>
@code {
// 组件状态:当前计数
private int CurrentCount { get; set; } = 0;
// 事件处理:增加计数
private void IncrementCount()
{
CurrentCount++;
// 状态变化时,UI自动更新
}
// 事件处理:重置计数
private void ResetCount()
{
CurrentCount = 0;
}
}
@page "/counter":指定组件的路由,访问/counter时显示该组件;
@rendermode InteractiveWebAssembly:启用交互式渲染,支持事件绑定、状态更新;
@onclick="IncrementCount":事件绑定,点击按钮时执行IncrementCount方法;
@code块:编写C#逻辑,包括状态变量、事件处理方法;
步骤2:组件复用(Shared/CountDisplay.razor)
创建一个可复用的计数显示组件:
razor
<h5 class="text-primary">计数:@Count</h5>
@code {
// 组件参数:从父组件接收计数
[Parameter]
public int Count { get; set; } = 0;
// 组件参数变化时触发
protected override void OnParametersSet()
{
Console.WriteLine($"计数更新为:{Count}");
}
}
[Parameter]:标记属性为组件参数,父组件可以通过Count="5"传递值;
OnParametersSet:组件参数变化时调用,可用于执行初始化逻辑;
步骤3:在父组件中使用子组件(Pages/Counter.razor)
razor
@page "/counter"
@rendermode InteractiveWebAssembly
<h3>计数器</h3>
<!-- 使用子组件,传递Count参数 -->
<CountDisplay Count="@CurrentCount" />
<div class="card" style="width: 18rem;">
<!-- 按钮和计数显示 -->
</div>
@code {
private int CurrentCount { get; set; } = 0;
// ...其他方法
}
场景3:数据绑定与表单处理
实现表单数据双向绑定,处理用户输入,展示Blazor的数据绑定机制。
步骤1:创建用户表单组件(Pages/UserForm.razor)
razor
@page "/user-form"
@rendermode InteractiveWebAssembly
<h3>用户信息表单</h3>
<div class="container mt-4">
<form @onsubmit="HandleSubmit">
<div class="mb-3">
<label class="form-label">用户名</label>
<input class="form-control" @bind="User.Name" placeholder="请输入用户名" />
</div>
<div class="mb-3">
<label class="form-label">邮箱</label>
<input type="email" class="form-control" @bind="User.Email" placeholder="请输入邮箱" />
</div>
<div class="mb-3">
<label class="form-label">年龄</label>
<input type="number" class="form-control" @bind="User.Age" min="18" max="100" />
</div>
<div class="mb-3">
<label class="form-check-label">
<input type="checkbox" class="form-check-input" @bind="User.IsActive" />
启用用户
</label>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
<!-- 显示提交结果 -->
@if (SubmittedUser != null)
{
<div class="mt-4 alert alert-success">
<h5>提交成功!</h5>
<p>用户名:@SubmittedUser.Name</p>
<p>邮箱:@SubmittedUser.Email</p>
<p>年龄:@SubmittedUser.Age</p>
<p>状态:@(SubmittedUser.IsActive ? "启用" : "禁用")</p>
</div>
}
</div>
@code {
// 用户数据模型
private User User { get; set; } = new();
private User? SubmittedUser { get; set; }
// 表单提交处理
private void HandleSubmit()
{
SubmittedUser = new User
{
Name = User.Name,
Email = User.Email,
Age = User.Age,
IsActive = User.IsActive
};
// 重置表单
User = new User();
}
// 用户模型类
public class User
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public int Age { get; set; } = 18;
public bool IsActive { get; set; } = true;
}
}
@bind="User.Name":双向数据绑定,输入框内容变化时,User.Name自动更新,反之亦然;
@onsubmit="HandleSubmit":表单提交事件绑定,点击提交按钮时执行HandleSubmit方法;
数据验证:通过HTML属性(如type="email"、min="18")实现基础验证,复杂验证可使用DataAnnotations;
场景4:API集成与数据展示
前端组件调用后端API,获取并展示数据,实现前后端交互。
步骤1:创建后端API(ASP.NET Core Web API)
1.新建ASP.NET Core Web API项目,命名为BlazorWasmSpa.Api;
2.创建天气预测控制器(Controllers/WeatherForecastController.cs):
csharp
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
6.启动API项目,访问http://localhost:5001/api/weatherforecast,确认API返回数据;
步骤2:前端调用API(Pages/Weather.razor)
razor
@page "/weather"
@rendermode InteractiveWebAssembly
@inject HttpClient Http <!-- 注入HttpClient服务 -->
<h3>天气预测</h3>
@if (forecasts == null)
{
<p><em>加载中...</em></p>
}
else
{
<table class="table table-striped">
<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;
// 组件初始化时调用API
protected override async Task OnInitializedAsync()
{
try
{
// 调用后端API获取数据
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("http://localhost:5001/api/weatherforecast");
}
catch (Exception ex)
{
Console.WriteLine($"调用API失败:{ex.Message}");
}
}
// 天气预测模型类
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
@inject HttpClient Http:注入HttpClient服务,用于调用API;
OnInitializedAsync:组件初始化时调用,异步获取API数据;
GetFromJsonAsync:将API返回的JSON数据反序列化为C#对象;
场景5:状态管理与本地存储
使用Blazored.LocalStorage管理全局状态(如用户信息、主题设置),实现跨组件共享数据。
步骤1:安装Blazored.LocalStorage
bash
dotnet add package Blazored.LocalStorage
步骤2:注册服务(Program.cs)
csharp
using Blazored.LocalStorage;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// 注册HttpClient
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
// 注册本地存储服务
builder.Services.AddBlazoredLocalStorage();
await builder.Build().RunAsync();
步骤3:使用本地存储(Pages/ThemeSettings.razor)
razor
@page "/theme-settings"
@rendermode InteractiveWebAssembly
@inject ILocalStorageService LocalStorage <!-- 注入本地存储服务 -->
<h3>主题设置</h3>
<div class="container mt-4">
<div class="mb-3">
<label class="form-label">选择主题</label>
<select class="form-select" @bind="SelectedTheme" @onchange="SaveTheme">
<option value="light">浅色主题</option>
<option value="dark">深色主题</option>
</select>
</div>
<p class="text-success">当前主题:@SelectedTheme</p>
</div>
@code {
private string SelectedTheme { get; set; } = "light";
// 组件初始化时加载主题
protected override async Task OnInitializedAsync()
{
// 从本地存储获取主题设置
var savedTheme = await LocalStorage.GetItemAsync<string>("theme");
if (!string.IsNullOrWhiteSpace(savedTheme))
{
SelectedTheme = savedTheme;
// 应用主题到页面
ApplyTheme(SelectedTheme);
}
}
// 保存主题设置到本地存储
private async Task SaveTheme()
{
await LocalStorage.SetItemAsync<string>("theme", SelectedTheme);
ApplyTheme(SelectedTheme);
}
// 应用主题到页面
private void ApplyTheme(string theme)
{
var body = document.DocumentElement;
if (theme == "dark")
{
body.ClassList.Add("dark-theme");
}
else
{
body.ClassList.Remove("dark-theme");
}
}
// 引入DOM操作
private readonly IJSRuntime _jsRuntime;
private IJSObjectReference? _document;
public ThemeSettings(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_document = await _jsRuntime.InvokeAsync<IJSObjectReference>("eval", "document");
}
}
}
@inject ILocalStorageService LocalStorage:注入本地存储服务;
GetItemAsync/SetItemAsync:从本地存储获取/保存数据,数据持久化到浏览器;
IJSRuntime:调用JavaScript操作DOM,应用主题到页面;
场景6:PWA支持(离线运行)
将Blazor WebAssembly应用转换为PWA(渐进式Web应用),支持离线运行、桌面安装、推送通知。
步骤1:启用PWA支持
1.右键Blazor WebAssembly项目 → 选择“属性”;
2.切换到“生成”选项卡 → 勾选“启用渐进式Web应用程序”;
3.项目会自动生成wwwroot/manifest.json(应用清单)和wwwroot/service-worker.js(服务工作者);
步骤2:PWA核心文件说明
manifest.json:定义应用名称、图标、启动URL、显示模式等,浏览器根据此文件生成桌面图标;
service-worker.js:缓存静态资源(如CSS、JS、图片),实现离线运行;
步骤3:测试离线运行
1.发布项目到生产环境(dotnet publish -c Release);
2.用HTTP服务器(如serve -s bin/Release/net8.0/publish/wwwroot)启动应用;
3.关闭网络,刷新页面,应用仍能正常运行(静态资源从缓存加载);
逐行讲解
场景2:组件核心代码
1.@rendermode InteractiveWebAssembly:
启用交互式渲染,组件支持事件绑定、状态更新;
若不指定,组件为静态渲染,无法响应用户交互;
2.组件参数:
[Parameter]标记的属性,父组件可以通过属性传递值;
OnParametersSet方法在参数变化时调用,可用于执行初始化逻辑;
场景4:API集成核心代码
1.HttpClient注入:
Blazor WebAssembly默认注册HttpClient服务,用于调用API;
可在Program.cs中配置BaseAddress,简化API调用路径;
2.异步数据获取:
使用GetFromJsonAsync异步获取API数据,避免阻塞UI线程;
捕获异常,处理API调用失败的情况;
场景5:状态管理核心代码
1.本地存储服务:
Blazored.LocalStorage封装了浏览器的localStorage API,提供类型安全的操作;
数据持久化到浏览器,即使关闭浏览器再打开,数据仍保留;
2.DOM操作:
通过IJSRuntime调用JavaScript,操作DOM元素(如添加主题类);
OnAfterRenderAsync方法在组件渲染完成后调用,确保DOM元素已存在;
基础知识拓展
- Blazor WebAssembly vs Blazor Server
| 特性 | Blazor WebAssembly | Blazor Server |
|---|---|---|
| 运行位置 | 浏览器(WebAssembly) | 服务器(.NET Core) |
| 网络依赖 | 首次加载后可离线运行(PWA) | 实时依赖网络,断开连接则无法使用 |
| 性能 | 首屏加载慢(下载.NET运行时和应用代码),运行时快 | 首屏加载快,运行时依赖网络延迟 |
| 可扩展性 | 支持PWA、离线运行、本地存储 | 适合低带宽环境,服务器压力大 |
| 调试体验 | 浏览器中调试C#代码 | 服务器端调试,实时同步到客户端 |
-
组件生命周期方法
Blazor组件从创建到销毁的完整生命周期,常用方法:
OnInitialized/OnInitializedAsync:组件初始化时调用,仅执行一次;
OnParametersSet/OnParametersSetAsync:组件参数变化时调用;
OnAfterRender/OnAfterRenderAsync:组件渲染完成后调用,可操作DOM;
Dispose/DisposeAsync:组件销毁时调用,释放资源; -
数据绑定类型
Blazor提供多种数据绑定方式:
| 绑定类型 | 语法示例 | 适用场景 |
|---|---|---|
| 单向绑定 | @CurrentCount | 显示数据,UI不影响代码 |
| 双向绑定 | @bind="User.Name" | 表单输入,UI与代码自动同步 |
| 事件绑定 | @onclick="HandleClick" | 按钮点击、表单提交等用户交互 |
| 属性绑定 | class="@(IsActive ? "active" : "")" | 动态设置HTML属性 |
-
性能优化策略
1.组件懒加载:使用@lazy指令,组件在需要时才加载,减少首屏体积;
2.预渲染:服务器端预渲染首屏,提升首屏加载速度;
3.资源压缩:启用Brotli/Gzip压缩,减小应用体积;
4.缓存策略:合理配置HTTP缓存头,缓存静态资源;
5.避免不必要的重渲染:使用ShouldRender方法控制组件是否重渲染; -
生态系统与第三方组件库
MudBlazor:基于Material Design的UI组件库,提供表格、表单、弹窗等组件;
Blazored:提供本地存储、模态框、Toast等常用组件;
Syncfusion Blazor:商业组件库,提供丰富的UI组件和图表;
Telerik UI for Blazor:商业组件库,支持网格、图表、调度器等;
总结
Blazor WebAssembly单页应用的核心是全栈C#开发、组件化架构、数据自动同步、跨平台支持,让前端开发告别JavaScript,用熟悉的C#构建现代Web应用。关键要点:
1.组件化开发:将UI拆分为独立组件,复用率高,维护成本低;
2.数据绑定:支持单向、双向、事件绑定,UI与数据自动同步;
3.API集成:前端组件直接调用后端API,前后端交互简单;
4.状态管理:使用本地存储、全局状态服务,管理跨组件共享数据;
5.PWA支持:离线运行、桌面安装、推送通知,像原生APP一样使用;
6.性能优化:组件懒加载、预渲染、资源压缩,提升首屏加载速度;
比如这个Blazor WebAssembly应用,全栈用C#开发,前端组件直接调用后端服务,代码复用率提升50%,调试在Visual Studio里一站式完成,开发效率翻了一倍。掌握Blazor WebAssembly,你就能用C#开发所有类型的Web应用,从简单的单页应用到复杂的企业管理系统!
来源:https://www.xin3721.com/ArticlecSharp/c49482.html










