文章元信息

  • 作者:刚子
  • 系列名称:.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 主要有两种托管模型,新手最容易搞混,刚子先帮你理一理:

  1. Blazor WebAssembly (客户端):应用被编译成 WebAssembly,直接在浏览器里运行。这就像 React 一样,部署在静态服务器上,不依赖服务器实时计算。
  2. 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 Server 实现了一个简单但完整的“全栈”功能:

  1. 理解了 Blazor Server 的实时通信原理。
  2. 学会了使用 @inject 注入服务。
  3. 实现了从后端 API 获取数据、删除数据和提交数据。
  4. 掌握了双向绑定简化表单开发。

现在的你,已经具备了独立开发一个小型管理系统的能力:后端用 Minimal API + EF Core,前端用 Blazor Server,全部用 C# 贯通。

下一篇预告

我们的应用已经能跑能跳了,但在互联网上裸奔是很危险的。谁都可以调用你的 /todos 接口吗?显然不行。 在下一篇教程中,我们将引入 身份验证与授权。刚子将带你深入了解 JWT (JSON Web Token),给你的 API 加上“门禁卡”,只有拿着正确 Token 的用户才能进出。

安全防线,即将构筑!