文章元信息
- 作者:刚子
- 系列名称:.NET 8 现代Web开发实战指南
- 原文链接:https://www.codeobservatory.cn/post/dotnet-8-web-development-part6-blazor-full-stack-no-javascript-anxiety
- 关键词:Blazor Server, C# 写前端, 组件化开发, 数据绑定, 全栈开发, SignalR
- 摘要:本文将带你进入 Blazor 的世界,一种允许开发者使用 C# 构建交互式 Web UI 的技术。通过实战构建“待办事项管理”前端页面,深入浅出地讲解组件化思想、数据绑定机制以及如何通过 HTTP 调用后端 API,助你打破前后端技术壁垒,实现真正的全栈开发。
一、前言:后端开发的“前端痛”
在传统开发中,全栈往往意味着你要精通 C# 还要精通 JavaScript、HTML、CSS,甚至还要学 React 或 Vue 的一大堆生命周期和状态管理库。这对很多专注于业务逻辑的后端同学来说,简直是噩梦。
微软也意识到了这个痛点,于是 Blazor 诞生了。
Blazor 的核心理念很简单:用 C# 替代 JavaScript。你可以在 Razor 页面里写 C# 代码,处理点击事件,甚至直接操作 DOM(虽然不推荐)。这让 .NET 开发者拥有了无缝的全栈开发能力。
二、Blazor Server:实时交互的黑魔法
Blazor 主要有两种托管模型,新手最容易搞混,刚子先帮你理一理:
- Blazor WebAssembly (客户端):应用被编译成 WebAssembly,直接在浏览器里运行。这就像 React 一样,部署在静态服务器上,不依赖服务器实时计算。
- Blazor Server (服务端):应用在服务器上运行,通过 SignalR(一种 WebSocket 技术)与浏览器实时通信。
刚子敲黑板: 本教程选择 Blazor Server 作为入门。为什么?
- 调试简单:你可以直接在 Visual Studio 里打断点,断点会停在后端的 C# 代码上,就像调试普通后台代码一样。
- 加载快:浏览器不需要下载整个 .NET 运行时,首屏加载速度极快。
- 适合后台管理系统:非常适合企业内部使用的后台管理界面开发。
三、动手实战:创建第一个 Blazor 项目
我们重新创建一个项目,为了方便演示,这次建立一个独立的 Blazor Server 项目。
3.1 创建项目
在终端执行:
dotnet new blazorserver -n MyTodoUI
cd MyTodoUI
dotnet run
启动后,浏览器访问 https://localhost:xxxx,你会看到一个漂亮的官方模板页面。
【配图建议 1】 位置:项目启动成功后。 内容:Blazor Server 默认模板首页截图,展示侧边栏导航和主要内容区。 Alt文本:Blazor Server 应用程序默认模板首页展示图。
3.2 项目结构解析
打开项目,重点关注以下几个文件夹:
- Pages/:存放
.razor页面组件。比如Index.razor就是首页。 - Shared/:存放公共布局组件,如
MainLayout.razor(布局模板)和NavMenu.razor(导航菜单)。 - _Imports.razor:类似于
using命名空间,这里引入的命名空间会自动应用到所有 Razor 组件。
四、组件化开发:一切皆组件
Blazor 的开发模式是组件化的。一个页面通常由多个小组件拼凑而成。
4.1 创建一个简单的计数器组件
打开 Pages/Index.razor,把内容清空,写入以下代码:
@page "/" <!-- 路由:访问根路径时显示这个组件 -->
<h1>欢迎来到刚子的计数器</h1>
<p>当前计数: <strong>@currentCount</strong></p>
<button class="btn btn-primary" @onclick="IncrementCount">点击我 +1</button>
@code {
// 这里写 C# 逻辑
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
代码解析:
@page "/":定义路由,告诉 Blazor 这是首页。@currentCount:使用@符号将 C# 变量输出到 HTML 中。@onclick="IncrementCount":将 HTML 的点击事件绑定到 C# 的方法上。
运行项目,点击按钮,你会发现数字变了!而且完全不需要写 JavaScript,也不用操作 DOM(document.getElementById...),Blazor 会自动帮你更新界面。
五、实战进阶:连接后端 API
光有个计数器没意思,我们要把之前教程里做好的 Todo API 接入进来。
为了模拟真实的前后端分离场景,我们假设后端 API 运行在 http://localhost:5000。
5.1 注入 HttpClient
在 Blazor Server 中,我们通过 HttpClient 发起网络请求。首先在 Program.cs 中注册服务:
// Program.cs
using System.Net.Http;
// ... 其他代码 ...
// 注册 HttpClient,BaseAddress 设为后端 API 地址
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri("http://localhost:5000") // 这里替换为你实际的后端地址
});
// ...
5.2 定义数据模型
为了接收 JSON 数据,我们需要定义一个简单的 DTO 类。在项目中新建 Models/TodoItemDto.cs:
public class TodoItemDto
{
public int Id { get; set; }
public string? Title { get; set; }
public bool IsDone { get; set; }
}
5.3 获取并展示数据
新建一个页面 Pages/Todo.razor:
@page "/todo"
@inject HttpClient Http <!-- 注入 HttpClient -->
@implements IDisposable
<h1>待办事项列表</h1>
@if (todos == null)
{
<p><em>正在加载数据...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
@foreach (var item in todos)
{
<tr>
<td>@item.Id</td>
<td>@item.Title</td>
<td>@(item.IsDone ? "已完成" : "进行中")</td>
<td>
<button class="btn btn-sm btn-danger" @onclick="() => DeleteTodo(item.Id)">删除</button>
</td>
</tr>
}
</tbody>
</table>
}
@code {
private List<TodoItemDto>? todos;
// 组件初始化时触发,适合加载数据
protected override async Task OnInitializedAsync()
{
try
{
// 调用 GET /todos 接口
todos = await Http.GetFromJsonAsync<List<TodoItemDto>>("api/todos");
}
catch (Exception ex)
{
Console.WriteLine($"获取数据失败: {ex.Message}");
}
}
private async Task DeleteTodo(int id)
{
// 调用 DELETE /todos/{id} 接口
await Http.DeleteAsync($"api/todos/{id}");
// 刷新列表
todos = await Http.GetFromJsonAsync<List<TodoItemDto>>("api/todos");
}
}
【配图建议 2】 位置:代码展示之后。 内容:浏览器运行截图,展示一个标准的 HTML 表格,里面列出了从后端 API 获取的 Todo 数据,每行都有一个“删除”按钮。 Alt文本:Blazor 页面展示从后端 API 获取的待办事项列表数据截图。
刚子小贴士:
注意到 @inject HttpClient Http 了吗?这依然是依赖注入。Blazor 的组件本质上也是一个类,我们可以直接在视图中注入服务,非常方便。
OnInitializedAsync 是 Blazor 的生命周期方法,相当于 React 的 useEffect 或 Vue 的 mounted,在这里发起网络请求是最标准的做法。
六、双向绑定:让表单变简单
Web 开发离不开表单。Blazor 提供了强大的双向绑定功能。
6.1 添加新待办
我们在 Todo.razor 底部添加一个输入框:
<hr />
<h3>添加新任务</h3>
<EditForm Model="@newTodo" OnValidSubmit="@AddTodo">
<div class="form-group">
<InputText @bind-Value="newTodo.Title" class="form-control" placeholder="输入任务内容" />
</div>
<button type="submit" class="btn btn-success mt-2">提交</button>
</EditForm>
@code {
// ... 之前的代码 ...
private TodoItemDto newTodo = new TodoItemDto();
private async Task AddTodo()
{
// 调用 POST /todos 接口
var response = await Http.PostAsJsonAsync("api/todos", newTodo);
if (response.IsSuccessStatusCode)
{
newTodo = new TodoItemDto(); // 清空输入框
todos = await Http.GetFromJsonAsync<List<TodoItemDto>>("api/todos"); // 刷新列表
}
}
}
核心解析:
@bind-Value="newTodo.Title":这是双向绑定的魔法。当你在输入框打字时,newTodo.Title会自动更新;当你修改newTodo时,输入框也会自动变。EditForm:Blazor 提供的表单组件,可以集成我们之前讲的 DataAnnotations 验证。
刚子敲黑板:
Blazor 的表单处理比原生 HTML 强大得多。你甚至可以在 InputText 上加 <DataAnnotationsValidator />,直接复用我们在第五篇写的验证规则,前端连验证逻辑都不用写了!
七、延伸阅读:刚子的 Blazor 深度系列
本篇实战教程旨在帮你快速打通前后端。如果你在实战过程中,想要更深入地了解 Blazor 的基础概念、路由机制、布局系统或 JS 交互,强烈推荐阅读我之前整理的系列专栏,那里有更细致的图文讲解:
- Blazor 入门指南(一):简介与环境搭建
- Blazor 入门指南(二):组件与数据绑定
- Blazor 入门指南(三):布局与路由
- Blazor 入门指南(四):JavaScript 互操作
- Blazor 入门指南(五):表单与验证
- Blazor 入门指南(六):依赖注入
- Blazor 入门指南(七):安全与认证
通过这些文章,你将建立起完整的 Blazor 知识体系。
八、总结与下篇预告
在这篇教程中,我们通过 Blazor Server 实现了一个简单但完整的“全栈”功能:
- 理解了 Blazor Server 的实时通信原理。
- 学会了使用
@inject注入服务。 - 实现了从后端 API 获取数据、删除数据和提交数据。
- 掌握了双向绑定简化表单开发。
现在的你,已经具备了独立开发一个小型管理系统的能力:后端用 Minimal API + EF Core,前端用 Blazor Server,全部用 C# 贯通。
下一篇预告:
我们的应用已经能跑能跳了,但在互联网上裸奔是很危险的。谁都可以调用你的 /todos 接口吗?显然不行。
在下一篇教程中,我们将引入 身份验证与授权。刚子将带你深入了解 JWT (JSON Web Token),给你的 API 加上“门禁卡”,只有拿着正确 Token 的用户才能进出。
安全防线,即将构筑!