






















大家好,我是码农刚子。最近好几天没更了,刷课时、考试、兼职,搞得我眼睛的睁不开了,今天终于有时间更新一篇。也不知道有没有朋友在期待?
在传统开发中,全栈往往意味着你要精通 C# 还要精通 JavaScript、HTML、CSS,甚至还要学 React 或 Vue 的一大堆生命周期和状态管理库。这对很多专注于业务逻辑的后端同学来说,简直是噩梦。
微软也意识到了这个痛点,于是 Blazor 诞生了。
Blazor 的核心理念很简单:用 C# 替代 JavaScript。你可以在 Razor 页面里写 C# 代码,处理点击事件,甚至直接操作 DOM(虽然不推荐)。这让 .NET 开发者拥有了无缝的全栈开发能力。
Blazor 主要有两种托管模型,新手最容易搞混,刚子先帮你理一理:
刚子敲黑板: 本教程选择 Blazor Server 作为入门。为什么?
我们重新创建一个项目,为了方便演示,这次建立一个独立的 Blazor Server 项目。
在终端执行:
dotnet new blazorserver -n MyTodoUI
cd MyTodoUI
dotnet run
可视化界面创建项目: 打开vs2022→点击创建新项目→选择Blazor Web 应用,点击下一步→依次输入项目名称、解决方案及项目文件路径,点击下一步→选择.NET 8 ,点击创建即可

启动后,浏览器访问 https://localhost:xxxx,你会看到一个漂亮的官方模板页面。

注意:这里创建应用时要选择 Blazor Web 应用 而不是 Blazor Server 应用。从.NET 8开始,经典的“Blazor Server”模板已被全新的“Blazor Web App”模板所取代,这一点在微软的官方文档和技术社区中都有提及。新的模板统一了Blazor Server和Blazor WebAssembly的开发体验,并引入了更灵活的“交互渲染模式”(Interactive Render Modes),因此VS不再直接提供名为“Blazor Server”的.NET 8项目选项。创建Blazor Server 应用不支持.NET 8。
打开项目,重点关注以下几个文件夹:

MainLayout.razor(布局模板)和 NavMenu.razor(导航菜单)。.razor 页面组件。比如 Home.razor 就是首页。using 命名空间,这里引入的命名空间会自动应用到所有 Razor 组件。Blazor 的开发模式是组件化的。一个页面通常由多个小组件拼凑而成。
打开 Pages/Home.razor,把内容清空,写入以下代码:
@page "/" <!-- 路由:访问根路径时显示这个组件 -->
@rendermode InteractiveServer
<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 会自动帮你更新界面。
光有个计数器没意思,我们要把之前教程里做好的 Todo API 接入进来。
为了模拟真实的前后端分离场景,我们假设后端 API 运行在 http://localhost:5000。
快速新建一个极简的 ASP.NET Core Web API:
不需要手动写控制器,直接用 最小 API 方式,几行代码就搞定。步骤如下:
右键解决方案 → 添加 → 新建项目
选择 ASP.NET Core Web API
项目名称:TodoApi
框架:.NET 8
不勾选“使用控制器” (这样会生成最小 API 模板)
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// 内存数据存储
var todos = new List<Todo>
{
new Todo { Id = 1, Title = "学习 Blazor", IsDone = false },
new Todo { Id = 2, Title = "测试 API", IsDone = true }
};
// GET /todos
app.MapGet("/todos", () => todos);
// DELETE /todos/{id}
app.MapDelete("/todos/{id}", (int id) =>
{
var todo = todos.FirstOrDefault(t => t.Id == id);
if (todo == null) return Results.NotFound();
todos.Remove(todo);
return Results.NoContent();
});
// POST /todos
app.MapPost("/todos", (Todo todo) =>
{
todo.Id = todos.Max(t => t.Id) + 1;
todos.Add(todo);
return Results.Created($"/todos/{todo.Id}", todo);
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Title { get; set; }
public bool IsDone { get; set; }
}
注意路由是 /todos(复数),没有 api 前缀。后面的前端调用也需要同步修改。
在 Blazor Web App 中,我们通过 HttpClient 发起网络请求。首先在 Program.cs 中注册服务:
// Program.cs
// ... 其他代码 ...
// 注册 HttpClient,BaseAddress 设为后端 API 地址
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri("http://localhost:5000") // 这里替换为你实际的后端地址
});
// ...
为了接收 JSON 数据,我们需要定义一个简单的 DTO 类。在项目中新建 Models/TodoItemDto.cs:
public class TodoItemDto
{
public int Id { get; set; }
public string? Title { get; set; }
public bool IsDone { get; set; }
}
新建一个页面 Pages/Todo.razor:
@page "/todo"
@rendermode InteractiveServer
@using MyTodoUI.Models
@inject HttpClient Http
<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>>("todos");
}
catch (Exception ex)
{
Console.WriteLine($"获取数据失败: {ex.Message}");
}
}
private async Task DeleteTodo(int id)
{
// 调用 DELETE /todos/{id} 接口
await Http.DeleteAsync($"todos/{id}");
// 刷新列表
todos = await Http.GetFromJsonAsync<List<TodoItemDto>>("todos");
}
}

刚子小贴士: 注意到 @inject HttpClient Http 了吗?这依然是依赖注入。Blazor 的组件本质上也是一个类,我们可以直接在视图中注入服务,非常方便。
另外 Blazor Web App,务必在组件上添加 @rendermode InteractiveServer 。
OnInitializedAsync 是 Blazor 的生命周期方法,相当于 React 的 useEffect 或 Vue 的 mounted,在这里发起网络请求是最标准的做法。
Web 开发离不开表单。Blazor 提供了强大的双向绑定功能。
我们在 Todo.razor 底部添加一个输入框:
<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("todos", newTodo);
if (response.IsSuccessStatusCode)
{
newTodo = new TodoItemDto(); // 清空输入框
todos = await Http.GetFromJsonAsync<List<TodoItemDto>>("todos"); // 刷新列表
}
}
}
核心解析:
@bind="newTodo.Title":这是双向绑定的魔法。当你在输入框打字时,newTodo.Title 会自动更新;当你修改 newTodo 时,输入框也会自动变。EditForm:Blazor 提供的表单组件,可以集成我们之前讲的 DataAnnotations 验证。刚子敲黑板: Blazor 的表单处理比原生 HTML 强大得多。你甚至可以在 InputText 上加 <DataAnnotationsValidator />,直接复用我们在第五篇写的验证规则,前端连验证逻辑都不用写了!
本篇实战教程旨在帮你快速打通前后端。如果你在实战过程中,想要更深入地了解 Blazor 的基础概念、路由机制、布局系统或 JS 交互,强烈推荐阅读我之前整理的系列专栏,那里有更细致的图文讲解:
通过这些文章,你将建立起完整的 Blazor 知识体系。
在这篇教程中,我们通过 Blazor Server 实现了一个简单但完整的“全栈”功能:
@inject 注入服务。现在的你,已经具备了独立开发一个小型管理系统的能力:后端用 Minimal API + EF Core,前端用 Blazor Server,全部用 C# 贯通。
下一篇预告:
我们的应用已经能跑能跳了,但在互联网上裸奔是很危险的。谁都可以调用你的 /todos 接口吗?显然不行。 在下一篇教程中,我们将引入 身份验证与授权。刚子将带你深入了解 JWT (JSON Web Token),给你的 API 加上“门禁卡”,只有拿着正确 Token 的用户才能进出。
安全防线,即将构筑!
如果本篇教程你对有用,欢迎点赞、收藏、转发给你身边的朋友,一起学习。
原文链接:.NET 8 Web开发入门(六):Blazor 全栈开发——告别 JavaScript 焦虑 - 码农刚子的开发笔记
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。