Blazor作为.NET Core平台上的全栈Web框架,其路由系统与传统ASP.NET MVC有着本质区别。Blazor应用的路由完全在客户端处理,这意味着页面切换时不会触发完整的HTTP请求,而是通过WebAssembly或SignalR实现无刷新导航。
Blazor的路由系统基于组件树构建,当浏览器地址栏URL变化时,路由器会执行以下流程:
关键对象Router组件位于App.razor中,负责整个应用的路由调度。其核心配置包括:
razor复制<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
Blazor支持两种路由定义方式:
约定式路由:在组件顶部使用@page指令
razor复制@page "/products"
@page "/products/{id:int}"
集中式配置:通过RouteTable动态注册
csharp复制var routes = new RouteBuilder();
routes.MapRoute("products", "/products");
Router.AddRoutes(routes.Build());
路由参数支持以下约束:
{id:int} 整数类型{active:bool} 布尔值{date:datetime} 日期时间提示:约定式路由更推荐用于大多数场景,保持路由定义与组件在同一个文件中更易维护
通过注入NavigationManager服务实现动态导航:
razor复制@inject NavigationManager Navigation
<button @onclick="NavigateToProducts">View Products</button>
@code {
private void NavigateToProducts()
{
Navigation.NavigateTo("/products");
// 带查询参数
Navigation.NavigateTo($"/products?page={currentPage}");
// 强制刷新(不推荐)
Navigation.NavigateTo("/products", forceLoad: true);
}
}
使用NavLink组件创建导航链接,可自动添加active样式:
razor复制<NavLink href="/products" Match="NavLinkMatch.Prefix">
Products
</NavLink>
<NavLink href="/products/1" class="nav-link" ActiveClass="active">
Product Detail
</NavLink>
Match参数控制匹配规则:
Prefix(默认):路径开头匹配All:完全匹配通过NavigationLock组件实现路由守卫:
razor复制<NavigationLock
ConfirmExternalNavigation="true"
OnBeforeInternalNavigation="OnNavigate"/>
@code {
private async Task OnNavigate(LocationChangingContext context)
{
if (hasUnsavedChanges)
{
var isConfirmed = await JSRuntime.InvokeAsync<bool>(
"confirm",
"You have unsaved changes. Continue?");
if (!isConfirmed)
{
context.PreventNavigation();
}
}
}
}
创建可嵌套的路由组件:
razor复制// Parent.razor
@page "/admin"
<Router AppAssembly="@typeof(Parent).Assembly"
AdditionalAssemblies="new[] { typeof(Child).Assembly }">
@* ... *@
</Router>
// Child.razor
@page "/admin/users"
<h3>User Management</h3>
按需加载程序集的路由:
csharp复制// 在Program.cs中配置
builder.Services.AddScoped<LazyAssemblyLoader>();
builder.Services.AddRouter(async (route, assemblies) =>
{
if (route.StartsWith("/admin"))
{
var assembly = await lazyLoader.LoadAssemblyAsync("AdminModule.dll");
assemblies.Add(assembly);
}
});
根据路由动态切换布局:
razor复制// App.razor
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
@{
var layout = routeData.PageType.GetCustomAttribute<LayoutAttribute>()?.LayoutType
?? typeof(MainLayout);
}
<RouteView RouteData="@routeData" DefaultLayout="@layout" />
</Found>
</Router>
// AdminPage.razor
@layout AdminLayout
@page "/admin"
在_Host.cshtml中预加载关键路由:
html复制<link rel="modulepreload" href="_framework/blazor.boot.json" />
<link rel="prefetch" href="/products" as="document" />
使用Blazor Debug代理查看路由表:
bash复制dotnet watch --debug
访问/_framework/debug可查看实时路由信息。
404错误:
@page指令参数绑定失败:
[SupplyParameterFromQuery]导航无响应:
NavigationManager是否注入NavigationLock阻止结合CSS实现平滑过渡:
css复制.route-view {
transition: opacity 0.3s ease;
}
.route-view.leaving {
opacity: 0;
}
razor复制<CascadingValue Value="this">
<RouteView RouteData="@routeData" DefaultLayout="@layout">
<ChildContent Context="component">
<div class="route-view @(isNavigating ? "leaving" : "")">
@component
</div>
</ChildContent>
</RouteView>
</CascadingValue>
创建可复用的面包屑组件:
razor复制<Breadcrumb>
<BreadcrumbItem Href="/">Home</BreadcrumbItem>
<BreadcrumbItem Href="/products">Products</BreadcrumbItem>
<BreadcrumbItem Current>Detail</BreadcrumbItem>
</Breadcrumb>
编写单元测试验证路由:
csharp复制[Fact]
public void ProductRoute_HasCorrectTemplate()
{
var component = new Product();
var routeAttribute = component.GetType()
.GetCustomAttributes<RouteAttribute>()
.First();
Assert.Equal("/products/{id:int}", routeAttribute.Template);
}
结合ASP.NET Core Identity实现权限控制:
razor复制@attribute [Authorize(Roles = "admin")]
@page "/admin/dashboard"
验证敏感路由参数:
csharp复制protected override void OnParametersSet()
{
if (!int.TryParse(Id, out _))
{
Navigation.NavigateTo("/error");
}
}
记录关键导航事件:
csharp复制private void HandleLocationChanged(object sender, LocationChangedEventArgs e)
{
logger.LogInformation("Navigation to {Location}", e.Location);
}
protected override void OnInitialized()
{
Navigation.LocationChanged += HandleLocationChanged;
}