8. 【Blazor全栈开发实战指南】--路由与导航

一、Blazor路由机制与Router组件

Blazor的路由系统完全在客户端运行,不依赖服务器来匹配URL并返回对应页面------这正是现代单页应用(SPA)的核心特征。当用户点击链接时,Blazor拦截导航事件,在不刷新整个页面的前提下卸载当前组件并挂载新组件,整个页面保持流畅连续。

路由的起点是App.razor,这是应用的根组件,也是路由配置的核心:

csharp 复制代码
@* Components/App.razor *@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <title>MyApp</title>
    @* HeadOutlet 允许各页面组件动态修改 <head> 内容(如改变标题、SEO标签) *@
    <HeadOutlet />
</head>
<body>
    @* Routes 组件封装了路由核心逻辑 *@
    <Routes />
</body>
</html>
csharp 复制代码
@* Components/Routes.razor *@

@* Router 组件是路由系统的引擎,AppAssembly 指定要扫描 @page 指令的程序集 *@
<Router AppAssembly="typeof(App).Assembly">
    <Found Context="routeData">
        @* 找到匹配路由时,用 RouteView 渲染对应组件,DefaultLayout 指定默认布局 *@
        <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
        @* FocusOnNavigate 在页面导航完成后自动聚焦指定选择器的元素,改善无障碍体验 *@
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
    <NotFound>
        @* 没有匹配路由时渲染此处内容 *@
        <PageTitle>页面未找到</PageTitle>
        <LayoutView Layout="typeof(Layout.MainLayout)">
            <h1>404 - 页面不存在</h1>
            <p>您访问的页面不存在,<a href="/">返回首页</a>。</p>
        </LayoutView>
    </NotFound>
</Router>

Router组件在应用启动时扫描AppAssembly所指程序集,找出所有带@page指令的组件并建立路由表。每当URL发生变化(无论是用户点击链接、按浏览器后退键,还是通过代码导航),Router都会重新匹配当前URL,并在FoundNotFound分支中渲染相应内容。

如果应用跨多个程序集(如独立的Razor类库),需要通过AdditionalAssemblies参数告知Router扫描其他程序集:

csharp 复制代码
<Router AppAssembly="typeof(App).Assembly"
        AdditionalAssemblies="new[] { typeof(SomeLibrary.SomeComponent).Assembly }">
    ...
</Router>

二、路由参数、约束与可选参数

@page指令中的路由模板支持与ASP.NET Core MVC完全相同的参数语法,通过花括号定义路由段,并可附加约束和可选标记。

基础路由参数 直接在花括号内写参数名,参数名必须与组件@code区中标记了[Parameter]的属性名(不区分大小写)完全匹配:

csharp 复制代码
@* /products/42 → Id = 42 *@
@page "/products/{Id:int}"

@code {
    [Parameter]
    public int Id { get; set; }
}

:int路由约束 ,表示该路由段必须能转换为int类型,非整数URL(如/products/abc)不会匹配此路由。常用约束还有:guid(GUID格式)、:bool(布尔值)、:datetime(日期时间)、:decimal(小数)等。如果不加约束,参数类型默认被视为字符串。

可选参数 在参数名末尾加?,对应的C#属性应声明为可空类型(int?string?):

csharp 复制代码
@* /articles/2024 或 /articles 都能匹配 *@
@page "/articles/{Year:int?}"

@code {
    [Parameter]
    public int? Year { get; set; }

    // 在 OnParametersSet 中处理可选参数的默认值
    protected override void OnParametersSet()
    {
        Year ??= DateTime.Now.Year;
    }
}

Catch-all参数*前缀捕获URL中任意数量的路径段,常用于构建树形结构的面包屑导航或文件浏览器:

csharp 复制代码
@* /files/documents/2024/report.pdf → FilePath = "documents/2024/report.pdf" *@
@page "/files/{*FilePath}"

@code {
    [Parameter]
    public string FilePath { get; set; } = string.Empty;
}

同一个组件也可以通过多个@page指令注册多个路由:

csharp 复制代码
@page "/products"
@page "/catalog"
@page "/shop"

三、NavigationManager:程序化导航

NavigationManager是Blazor提供的导航服务,允许在C#代码中读取当前URL、监听导航事件,以及在不点击任何链接的情况下以编程方式跳转页面。通过@inject注入后即可使用:

csharp 复制代码
@inject NavigationManager NavManager

@code {
    // 获取完整的当前URL,如 "https://localhost:5001/products/42"
    private string currentUrl => NavManager.Uri;

    // 获取基础URL,如 "https://localhost:5001/"
    private string baseUrl => NavManager.BaseUri;

    // 获取相对路径,如 "/products/42"
    private string relativePath => NavManager.ToBaseRelativePath(NavManager.Uri);

    private void GoToHome()
    {
        // 导航到指定URL(相对或绝对均可)
        NavManager.NavigateTo("/");
    }

    private void GoToProductWithReplace()
    {
        // forceLoad: true 强制整页刷新(通常不需要)
        // replace: true 替换浏览器历史记录中的当前条目(用户无法后退到此页面)
        NavManager.NavigateTo("/products/1", replace: true);
    }

    // 登录成功后重定向到之前尝试访问的页面
    private async Task HandleLogin()
    {
        var returnUrl = new Uri(NavManager.Uri).Query;
        // ... 执行登录逻辑 ...
        NavManager.NavigateTo(returnUrl.Length > 0 ? returnUrl : "/");
    }
}

LocationChanged事件在每次导航成功后触发,常用于分析统计或在导航时执行特定操作(如滚动到顶部):

csharp 复制代码
@inject NavigationManager NavManager
@implements IDisposable

@code {
    protected override void OnInitialized()
    {
        NavManager.LocationChanged += OnLocationChanged;
    }

    private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
    {
        // e.Location 是新的完整URL
        // e.IsNavigationIntercepted 表示是否是通过拦截锚点点击触发的导航
        Console.WriteLine($"导航到: {e.Location}");
    }

    public void Dispose()
    {
        NavManager.LocationChanged -= OnLocationChanged;
    }
}

NavLink组件是Blazor对HTML <a>标签的增强版,当其href与当前URL匹配时会自动添加ActiveClass属性指定的CSS类(默认为active),非常适合构建高亮当前页面的导航菜单:

csharp 复制代码
@* NavLink 会在匹配当前路由时自动添加 "active" class *@
<NavLink href="/" Match="NavLinkMatch.All">首页</NavLink>
<NavLink href="/products" Match="NavLinkMatch.Prefix">产品中心</NavLink>

NavLinkMatch.All要求URL完全匹配才高亮;NavLinkMatch.Prefix只要URL以href开头就高亮,适合有子页面的导航项(如/products能匹配/products/42)。

四、路由守卫与授权控制

真实应用中,某些页面必须登录后才能访问。Blazor通过AuthorizeRouteView[Authorize]特性提供声明式的路由级授权控制。

首先将Routes.razor中的RouteView替换为AuthorizeRouteView,并提供NotAuthorized内容,指定未认证时的降级UI:

csharp 复制代码
@* Components/Routes.razor(带授权版本)*@
@using Microsoft.AspNetCore.Components.Authorization

<Router AppAssembly="typeof(App).Assembly">
    <Found Context="routeData">
        @* AuthorizeRouteView 在渲染页面前检查授权状态 *@
        <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
            <NotAuthorized>
                @* 未认证时,重定向到登录页,并将当前URL作为returnUrl参数传过去 *@
                <RedirectToLogin />
            </NotAuthorized>
            <Authorizing>
                @* 正在验证认证状态时显示(网络慢时可见) *@
                <p>正在验证身份...</p>
            </Authorizing>
        </AuthorizeRouteView>
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
    <NotFound>
        <LayoutView Layout="typeof(Layout.MainLayout)">
            <h1>页面不存在</h1>
        </LayoutView>
    </NotFound>
</Router>

RedirectToLogin是一个简单的自定义组件,它在渲染时立即执行重定向:

csharp 复制代码
@* Components/RedirectToLogin.razor *@
@inject NavigationManager NavManager

@code {
    protected override void OnInitialized()
    {
        // 将当前URL编码后作为 returnUrl 参数,以便登录后能回到原页面
        var returnUrl = Uri.EscapeDataString(NavManager.ToBaseRelativePath(NavManager.Uri));
        NavManager.NavigateTo($"/login?returnUrl={returnUrl}");
    }
}

在需要保护的页面组件上添加[Authorize]特性。如果用@attribute指令写在.razor文件中,等效于在类上标记特性:

csharp 复制代码
@page "/admin/users"
@attribute [Authorize(Roles = "Admin")]

<h1>用户管理</h1>
@* 只有Admin角色的已登录用户才能看到此页面 *@

还可以在同一个页面内按细粒度控制UI元素的可见性,AuthorizeView组件提供了AuthorizedNotAuthorizedAuthorizing三个插槽:

razor 复制代码
<AuthorizeView Roles="Admin,Manager">
    <Authorized>
        @* 已认证且具有 Admin 或 Manager 角色才显示 *@
        <button class="btn btn-danger">删除记录</button>
    </Authorized>
    <NotAuthorized>
        @* 权限不足时显示友好提示 *@
        <span class="text-muted">(无权操作)</span>
    </NotAuthorized>
</AuthorizeView>

认证状态本身由AuthenticationStateProvider提供,该服务的具体实现与认证方案(JWT、Cookie、Identity等)密切相关,我们将在第四章的认证授权部分做详细讲解。

五、总结

本章完整梳理了Blazor路由系统的运作机制:Router组件在运行时扫描程序集建立路由表,@page指令配合类型约束声明路由匹配规则,NavigationManager提供程序化导航与URL读取的能力,NavLink自动处理活跃状态高亮,AuthorizeRouteView[Authorize]特性共同构建声明式的路由级授权体系。

至此,第二章的组件进阶内容已全部覆盖。在实际项目中,你一定会遇到某些功能必须借助JavaScript才能实现------浏览器原生API(如Clipboard、Notification、WebSocket)、第三方JavaScript库的图表和编辑器,或者对DOM的像素级操控。下一章,我们将深入Blazor与JavaScript互操作,学习如何从C#代码安全、高效地调用JavaScript函数,以及如何将JavaScript的回调结果传回C#组件。

相关推荐
liqianpin12 小时前
maven导入spring框架
数据库·spring·maven
wanhengidc2 小时前
服务器硬盘都有哪些功能
大数据·运维·服务器·数据库·科技
A10169330712 小时前
QT数据库(三):QSqlQuery使用
数据库·qt·oracle
码云数智-大飞2 小时前
分布式锁的三种实现方案:Redis、ZooKeeper与数据库的深度对比与选型指南
数据库·redis·分布式
“抚琴”的人2 小时前
SqlSugar 文档
开发语言·数据库·c#·sqlsugar
a***71632 小时前
IDEA连接SQL server数据库(保姆级详细且必坑,包括防火墙、 SQL Server 网络配置等问题解决)
网络·数据库·intellij-idea
木易 士心2 小时前
告别手写SQL?Cursor智能生成实战指南与避坑技巧
数据库·sql·ai编程
倔强的石头1062 小时前
KWDB 硬核实战:30ms 写入千条轨迹,用 SQL 打造物流车队“天眼”系统
数据库·sql·kwdb
啊哈哈121382 小时前
计算机三级备考(七)——高级数据库查询
服务器·数据库