ASP.NET Core Blazor 路由和导航

注意

此版本不是本文的最新版本。 若要切换到最新版本,请使用目录顶部的 ASP.NET Core 版本选择器。

版本选择器

如果选择器在较窄的浏览器窗口中不可见,请扩大窗口或选择垂直省略号 (⋮) >“目录”。

目录选择器

本文介绍如何管理请求路由以及如何使用 NavLink 组件在 Blazor 应用中创建导航链接。

重要

本文中的代码示例展示了对 Navigation 调用的方法,这是在类和组件中注入的 NavigationManager

路由模板

通过 Router 组件可在 Blazor 应用中路由到 Razor 组件。 Router 组件在 Blazor 应用的 App 组件中使用。

App.razor:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>

编译带有 @page 指令的 Razor 组件 (.razor) 时,将为生成的组件类提供一个 RouteAttribute 来指定组件的路由模板。

当应用启动时,将扫描指定为 路由器的 AppAssembly 的程序集,来收集具有 RouteAttribute 的应用组件的路由信息。

在运行时,RouteView 组件:

  • Router 接收 RouteData 以及所有路由参数。
  • 使用指定的组件的布局来呈现该组件,包括任何后续嵌套布局。

对于没有使用 @layout 指令指定布局的组件,可选择使用布局类指定一个 DefaultLayout 参数。 框架的 Blazor 项目模板会指定 MainLayout 组件 (Shared/MainLayout.razor) 作为应用的默认布局。 有关布局的详细信息,请参阅 ASP.NET Core Blazor 布局

组件支持使用多个 @page 指令的多个路由模板。 以下示例组件会对 /blazor-route/different-blazor-route 的请求进行加载。

Pages/BlazorRoute.razor:

@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>
@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>
@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>
@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>

重要

若要正确解析 URL,应用必须包含 <base> 标记(<head> 内容的位置),并在 href 属性中指定应用基路径。 有关详细信息,请参阅托管和部署 ASP.NET Core Blazor

Router 不与查询字符串值交互。 若要使用查询字符串,请参阅查询字符串部分。

作为使用 @page 指令将路由模板指定为字符串文本的替代方法,可以使用 @attribute 指令指定基于常量的路由模板。

在以下示例中,组件中的 @page 指令替换为 @attribute 指令和 Constants.CounterRoute 中基于常量的路由模板,该模板在应用中的其他位置设置为“/counter”:

- @page "/counter"
+ @attribute [Route(Constants.CounterRoute)]

将元素聚焦到导航上

在页面之间进行导航后,使用 FocusOnNavigate 组件基于 CSS 选择器将 UI 焦点设置到元素。 可以看到 FocusOnNavigate 组件正由从 Blazor 项目模板生成的应用的 App 组件使用。

App.razor中:

<FocusOnNavigate RouteData="@routeData" Selector="h1" />

Router 组件导航到新页面时,FocusOnNavigate 组件将焦点设置到页面的顶层标题 (<h1>)。 这是一种常见策略,可确保在使用屏幕阅读器时公布页面导航。

在找不到内容时提供自定义内容

如果找不到所请求路由的内容,则 Router 组件允许应用指定自定义内容。

App 组件中,在 Router 组件的 NotFound 模板中设置自定义内容。

App.razor:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <h1>Sorry</h1>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <h1>Sorry</h1>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <h1>Sorry</h1>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>

注意

随着 ASP.NET Core 5.0.1 的发布及任何附加 5.x 版本的推出,Router 组件包含 PreferExactMatches 参数(设置为 @true)。 有关详细信息,请参阅从 ASP.NET Core 3.1 迁移到 5.0

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <h1>Sorry</h1>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>

任意项都可用作 <NotFound> 标记的内容,例如其他交互式组件。 若要将默认布局应用于 NotFound 内容,请参阅 ASP.NET Core Blazor 布局

从多个程序集路由到组件

使用 AdditionalAssemblies 参数为 Router 组件指定搜索可路由组件时要考虑的其他程序集。 除了指定至 AppAssembly 的程序集外,还会扫描其他程序集。 在以下示例中,Component1 是在引用的组件类库中定义的可路由组件。 以下 AdditionalAssemblies 示例为 Component1 提供路由支持。

App.razor:

<Router
    AppAssembly="@typeof(App).Assembly"
    AdditionalAssemblies="new[] { typeof(Component1).Assembly }">
    @* ... Router component elements ... *@
</Router>

注意

随着 ASP.NET Core 5.0.1 的发布及任何附加 5.x 版本的推出,Router 组件包含 PreferExactMatches 参数(设置为 @true)。 有关详细信息,请参阅从 ASP.NET Core 3.1 迁移到 5.0

路由参数

路由器使用路由参数以相同的名称填充相应的组件参数。 路由参数名不区分大小写。 在下面的示例中,text 参数将路由段的值赋给组件的 Text 属性。 对 /route-parameter-1/amazing发出请求时,<h1> 标记内容呈现为 Blazor is amazing!

Pages/RouteParameter1.razor:

@page "/route-parameter-1/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }
}
@page "/route-parameter-1/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }
}
@page "/route-parameter-1/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string Text { get; set; }
}
@page "/route-parameter-1/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string Text { get; set; }
}

支持可选参数。 在下面的示例中,text 可选参数将 route 段的值赋给组件的 Text 属性。 如果该段不存在,则将 Text 的值设置为 fantastic

不支持可选参数。 在下述示例中,应用了两个 @page 指令。 第一个指令允许导航到没有参数的组件。 第二个指令将 {text} 路由参数分配给组件的 Text 属性。

Pages/RouteParameter2.razor:

@page "/route-parameter-2/{text?}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}
@page "/route-parameter-2/{text?}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}
@page "/route-parameter-2/{text?}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}
@page "/route-parameter-2"
@page "/route-parameter-2/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

使用 OnParametersSet 而不是 OnInitialized{Async},以允许应用使用不同的可选参数值导航到同一组件。 根据上述示例,当用户应该能够从 /route-parameter-2 导航到 /route-parameter-2/amazing 或从 /route-parameter-2/amazing 导航到 /route-parameter-2 时使用 OnParametersSet

protected override void OnParametersSet()
{
    Text = Text ?? "fantastic";
}

注意

路由参数不适用于查询字符串值。 若要使用查询字符串,请参阅查询字符串部分。

路由约束

路由约束强制在路由段和组件之间进行类型匹配。

在以下示例中,到 User 组件的路由仅在以下情况下匹配:

  • 请求 URL 中存在 Id 路由段。
  • Id 段是一个整数 (int) 类型。

Pages/User.razor:

@page "/user/{Id:int}"

<h1>User Id: @Id</h1>

@code {
    [Parameter]
    public int Id { get; set; }
}
@page "/user/{Id:int}"

<h1>User Id: @Id</h1>

@code {
    [Parameter]
    public int Id { get; set; }
}
@page "/user/{Id:int}"

<h1>User Id: @Id</h1>

@code {
    [Parameter]
    public int Id { get; set; }
}
@page "/user/{Id:int}"

<h1>User Id: @Id</h1>

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

注意

路由约束不适用于查询字符串值。 若要使用查询字符串,请参阅查询字符串部分。

下表中显示的路由约束可用。 有关与固定区域性匹配的路由约束,请参阅表下方的警告了解详细信息。

约束 示例 匹配项示例 固定条件
区域性
匹配
bool {active:bool} true, FALSE
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm
decimal {price:decimal} 49.99, -1,000.01
double {weight:double} 1.234, -1,001.01e8
float {weight:float} 1.234, -1,001.01e8
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638}
int {id:int} 123456789, -123456789
long {ticks:long} 123456789, -123456789

警告

验证 URL 的路由约束并将转换为始终使用固定区域性的 CLR 类型(例如 intDateTime)。 这些约束假定 URL 不可本地化。

路由约束也适用于可选参数。 在下面的示例中,Id 是必需的,但 Option 是一个可选的布尔路由参数。

Pages/User.razor:

@page "/user/{Id:int}/{Option:bool?}"

<p>
    Id: @Id
</p>

<p>
    Option: @Option
</p>

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

    [Parameter]
    public bool Option { get; set; }
}

使用包含点的 URL 进行路由

对于托管的 Blazor WebAssembly 和 Blazor Server应用,服务器端默认路由模板假定如果请求 URL 的最后一段包含一个点 (.),则请求一个文件。 例如,相对 URL /example/some.thing 由路由器解释为名为 some.thing 的文件的请求。 在没有额外配置的情况下,如果 some.thing 是指通过 @page 指令路由到一个组件,且 some.thing 是一个路由参数值,那么应用将返回“404 - 未找到”响应。 若要使用具有包含点的一个或多个参数的路由,则应用必须使用自定义模板配置该路由。

请考虑下面的 Example 组件,它可以从 URL 的最后一段接收路由参数。

Pages/Example.razor:

@page "/example/{param?}"

<p>
    Param: @Param
</p>

@code {
    [Parameter]
    public string? Param { get; set; }
}
@page "/example/{param?}"

<p>
    Param: @Param
</p>

@code {
    [Parameter]
    public string? Param { get; set; }
}
@page "/example/{param?}"

<p>
    Param: @Param
</p>

@code {
    [Parameter]
    public string Param { get; set; }
}
@page "/example"
@page "/example/{param}"

<p>
    Param: @Param
</p>

@code {
    [Parameter]
    public string Param { get; set; }
}

若要允许托管的 Blazor WebAssembly 解决方案Server 应用在 param 路由参数中使用一个点来路由请求,请添加一个回退文件路由模板,在该模板的 Program.cs 中包含该可选参数:

app.MapFallbackToFile("/example/{param?}", "index.html");

若要配置 Blazor Server应用,使其在 param 路由参数中使用一个点来路由请求,请添加一个回退页面路由模板,该模板具有Program.cs 中的可选参数:

app.MapFallbackToPage("/example/{param?}", "/_Host");

若要允许托管的 Blazor WebAssembly 解决方案Server 应用在 param 路由参数中使用一个点来路由请求,请添加一个回退文件路由模板,在该模板的 Startup.Configure 中包含该可选参数。

Startup.cs:

endpoints.MapFallbackToFile("/example/{param?}", "index.html");

若要配置 Blazor Server应用,使其在 param 路由参数中使用一个点来路由请求,请添加一个回退页面路由模板,该模板具有Startup.Configure 中的可选参数。

Startup.cs:

endpoints.MapFallbackToPage("/example/{param?}", "/_Host");

有关详细信息,请参阅 ASP.NET Core 中的路由

catch-all 路由参数

组件支持可跨多个文件夹边界捕获路径的 catch-all 路由参数。

Catch-all 路由参数是:

  • 以与路由段名称匹配的方式命名。 命名不区分大小写。
  • string 类型。 框架不提供自动强制转换。
  • 位于 URL 的末尾。

Pages/CatchAll.razor:

@page "/catch-all/{*pageRoute}"

@code {
    [Parameter]
    public string? PageRoute { get; set; }
}
@page "/catch-all/{*pageRoute}"

@code {
    [Parameter]
    public string? PageRoute { get; set; }
}
@page "/catch-all/{*pageRoute}"

@code {
    [Parameter]
    public string PageRoute { get; set; }
}

对于具有 /catch-all/{*pageRoute} 路由模板的 URL /catch-all/this/is/a/testPageRoute 的值设置为 this/is/a/test

对捕获路径的斜杠和段进行解码。 对于 /catch-all/{*pageRoute} 的路由模板,URL /catch-all/this/is/a%2Ftest%2A 会生成 this/is/a/test*

URI 和导航状态帮助程序

在 C# 代码中使用 NavigationManager 来来管理 URI 和导航。 NavigationManager 提供下表所示的事件和方法。

成员 描述
Uri 获取当前绝对 URI。
BaseUri 获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。 通常,BaseUri 对应于文档的 <base> 元素(<head> 内容的位置)上的 href 属性。
NavigateTo 导航到指定 URI。 如果 forceLoadtrue,则:
  • 客户端路由会被绕过。
  • 无论 URI 是否通常由客户端路由器处理,浏览器都必须从服务器加载新页面。
如果 replacetrue,则替换浏览器历史记录中的当前 URI,而不是将新的 URI 推送到历史记录堆栈中。
LocationChanged 导航位置更改时触发的事件。 有关详细信息,请参阅位置更改部分。
ToAbsoluteUri 将相对 URI 转换为绝对 URI。
ToBaseRelativePath 给定基 URI(例如,之前由 BaseUri 返回的 URI),将绝对 URI 转换为相对于基 URI 前缀的 URI。
RegisterLocationChangingHandler 注册一个处理程序来处理传入的导航事件。 调用 NavigateTo 始终调用处理程序。
GetUriWithQueryParameter 返回通过更新 NavigationManager.Uri 来构造的 URI(添加、更新或删除单个参数)。 有关详细信息,请参阅查询字符串部分。
成员 描述
Uri 获取当前绝对 URI。
BaseUri 获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。 通常,BaseUri 对应于文档的 <base> 元素(<head> 内容的位置)上的 href 属性。
NavigateTo 导航到指定 URI。 如果 forceLoadtrue,则:
  • 客户端路由会被绕过。
  • 无论 URI 是否通常由客户端路由器处理,浏览器都必须从服务器加载新页面。
如果 replacetrue,则替换浏览器历史记录中的当前 URI,而不是将新的 URI 推送到历史记录堆栈中。
LocationChanged 导航位置更改时触发的事件。 有关详细信息,请参阅位置更改部分。
ToAbsoluteUri 将相对 URI 转换为绝对 URI。
ToBaseRelativePath 给定基 URI(例如,之前由 BaseUri 返回的 URI),将绝对 URI 转换为相对于基 URI 前缀的 URI。
GetUriWithQueryParameter 返回通过更新 NavigationManager.Uri 来构造的 URI(添加、更新或删除单个参数)。 有关详细信息,请参阅查询字符串部分。
成员 描述
Uri 获取当前绝对 URI。
BaseUri 获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。 通常,BaseUri 对应于 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 中文档的 <base> 元素上的 href 属性。
NavigateTo 导航到指定 URI。 如果 forceLoadtrue,则:
  • 客户端路由会被绕过。
  • 无论 URI 是否通常由客户端路由器处理,浏览器都必须从服务器加载新页面。
LocationChanged 导航位置更改时触发的事件。
ToAbsoluteUri 将相对 URI 转换为绝对 URI。
ToBaseRelativePath 给定基 URI(例如,之前由 BaseUri 返回的 URI),将绝对 URI 转换为相对于基 URI 前缀的 URI。

位置更改

对于 LocationChanged 事件,LocationChangedEventArgs 提供了下述导航事件信息:

以下组件:

  • 使用 NavigateTo 选择按钮后,导航到应用的 Counter 组件 (Pages/Counter.razor)。
  • 通过订阅 NavigationManager.LocationChanged 来处理位置更改事件。
    • 在框架调用 Dispose 时,解除挂接 HandleLocationChanged 方法。 解除挂接该方法可允许组件进行垃圾回收。

    • 选择该按钮时,记录器实现会记录以下信息:

      BlazorSample.Pages.Navigate: Information: URL of new location: https://localhost:{PORT}/counter

Pages/Navigate.razor:

@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code example</h1>

<button class="btn btn-primary" @onclick="NavigateToCounterComponent">
    Navigate to the Counter component
</button>

@code {
    private void NavigateToCounterComponent()
    {
        Navigation.NavigateTo("counter");
    }

    protected override void OnInitialized()
    {
        Navigation.LocationChanged += HandleLocationChanged;
    }

    private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
    {
        Logger.LogInformation("URL of new location: {Location}", e.Location);
    }

    public void Dispose()
    {
        Navigation.LocationChanged -= HandleLocationChanged;
    }
}
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code example</h1>

<button class="btn btn-primary" @onclick="NavigateToCounterComponent">
    Navigate to the Counter component
</button>

@code {
    private void NavigateToCounterComponent()
    {
        Navigation.NavigateTo("counter");
    }

    protected override void OnInitialized()
    {
        Navigation.LocationChanged += HandleLocationChanged;
    }

    private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
    {
        Logger.LogInformation("URL of new location: {Location}", e.Location);
    }

    public void Dispose()
    {
        Navigation.LocationChanged -= HandleLocationChanged;
    }
}
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code example</h1>

<button class="btn btn-primary" @onclick="NavigateToCounterComponent">
    Navigate to the Counter component
</button>

@code {
    private void NavigateToCounterComponent()
    {
        Navigation.NavigateTo("counter");
    }

    protected override void OnInitialized()
    {
        Navigation.LocationChanged += HandleLocationChanged;
    }

    private void HandleLocationChanged(object sender, LocationChangedEventArgs e)
    {
        Logger.LogInformation("URL of new location: {Location}", e.Location);
    }

    public void Dispose()
    {
        Navigation.LocationChanged -= HandleLocationChanged;
    }
}
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code example</h1>

<button class="btn btn-primary" @onclick="NavigateToCounterComponent">
    Navigate to the Counter component
</button>

@code {
    private void NavigateToCounterComponent()
    {
        Navigation.NavigateTo("counter");
    }

    protected override void OnInitialized()
    {
        Navigation.LocationChanged += HandleLocationChanged;
    }

    private void HandleLocationChanged(object sender, LocationChangedEventArgs e)
    {
        Logger.LogInformation("URL of new location: {Location}", e.Location);
    }

    public void Dispose()
    {
        Navigation.LocationChanged -= HandleLocationChanged;
    }
}

有关组件处置的详细信息,请参阅 ASP.NET Core Razor 组件生命周期

NavigationManager 使用浏览器的历史记录 API 来维护与应用程序所做的每个位置更改相关的导航历史记录状态。 维护历史记录状态在外部重定向场景中特别有用,例如在使用外部标识提供者对用户进行身份验证时。 有关详细信息,请参阅导航选项部分。

NavigationOptions 传递给 NavigateTo 以控制以下行为:

  • ForceLoad:绕过客户端路由并强制浏览器从服务器加载新页面,无论 URI 是否由客户端路由器处理。 默认值为 false
  • ReplaceHistoryEntry:替换历史记录堆栈中的当前条目。 如果为 false,则将新条目附加到历史记录堆栈。 默认值为 false
  • HistoryEntryState:获取或设置要附加到历史记录条目的状态。
Navigation.NavigateTo("/path", new NavigationOptions
{
    HistoryEntryState = "Navigation state"
});

有关在处理位置更改时获取与目标历史记录条目关联的状态的详细信息,请参阅处理/防止位置更改部分。

查询字符串

[SupplyParameterFromQuery] 属性[Parameter] 属性结合使用,指定可路由组件的组件参数可以来自查询字符串。

注意

组件参数只能在具有 @page 指令的可路由组件中接收查询参数值。

只有可路由组件直接接收查询参数,以避免破坏自上而下的信息流,并通过框架和应用使参数处理顺序清晰明确。 此设计可避免采用特定参数处理顺序编写的应用代码中的细微 bug。 你可以自由地定义自定义级联参数或直接分配给常规组件参数,以便将查询参数值传递给不可路由的组件。

查询字符串提供的组件参数支持以下类型:

  • bool, DateTime, decimal, double, float, Guid, int, long, string.
  • 上述类型的可为空变体。
  • 上述类型的数组,无论它们是可为空还是不可为空。

正确的区域性固定格式设置适用于给定类型 (CultureInfo.InvariantCulture)。

指定 [SupplyParameterFromQuery] 属性的 Name 属性以使用不同于组件参数名称的查询参数名称。 在以下示例中,组件参数的 C# 名称是 {COMPONENT PARAMETER NAME}。 为 {QUERY PARAMETER NAME} 占位符指定了不同的查询参数名称:

[Parameter]
[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]
public string? {COMPONENT PARAMETER NAME} { get; set; }

在使用 /search?filter=scifi%20stars&page=3&star=LeVar%20Burton&star=Gary%20Oldman URL 的以下示例中:

  • Filter 属性解析为 scifi stars
  • Page 属性解析为 3
  • Stars 数组是从名为 star (Name = "star") 的查询参数填充的,并解析为 LeVar BurtonGary Oldman

Pages/Search.razor:

@page "/search"

<h1>Search Example</h1>

<p>Filter: @Filter</p>

<p>Page: @Page</p>

@if (Stars is not null)
{
    <p>Assignees:</p>

    <ul>
        @foreach (var name in Stars)
        {
            <li>@name</li>
        }
    </ul>
}

@code {
    [Parameter]
    [SupplyParameterFromQuery]
    public string? Filter { get; set; }

    [Parameter]
    [SupplyParameterFromQuery]
    public int? Page { get; set; }

    [Parameter]
    [SupplyParameterFromQuery(Name = "star")]
    public string[]? Stars { get; set; }
}

使用 NavigationManager.GetUriWithQueryParameter 在当前 URL 上添加、更改或删除一个或多个查询参数:

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameter("{NAME}", {VALUE})

在上面的示例中:

  • {NAME} 占位符指定查询参数名称。 {VALUE} 占位符将值指定为支持的类型。 本节后面的内容列出了支持的类型。
  • 返回的字符串相当于包含单个参数的当前 URL:
    • 如果查询参数名称在当前 URL 中不存在,则进行添加。
    • 如果查询参数存在于当前 URL 中,则更新为提供的值。
    • 如果提供的值的类型可为空且值为 null,则进行删除。
  • 正确的区域性固定格式设置适用于给定类型 (CultureInfo.InvariantCulture)。
  • 查询参数名称和值是 URL 编码的。
  • 如果存在类型的多个实例,则所有具有匹配查询参数名称的值都会被替换。

调用 NavigationManager.GetUriWithQueryParameters 以创建从 Uri 构造的 URI(添加、更新或删除多个参数)。 对于每个值,框架使用 value?.GetType() 来确定每个查询参数的运行时类型,并选择正确的区域性固定格式设置。 对于不支持的类型,框架将引发错误。

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters({PARAMETERS})

{PARAMETERS} 占位符是一个 IReadOnlyDictionary<string, object>

GetUriWithQueryParameters 传递 URI 字符串,以从提供的 URI 生成新的 URI(添加、更新或删除多个参数)。 对于每个值,框架使用 value?.GetType() 来确定每个查询参数的运行时类型,并选择正确的区域性固定格式设置。 对于不支持的类型,框架将引发错误。 本节后面的内容列出了支持的类型。

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters("{URI}", {PARAMETERS})
  • {URI} 占位符是带或不带查询字符串的 URI。
  • {PARAMETERS} 占位符是一个 IReadOnlyDictionary<string, object>

支持的类型与路由约束的支持类型相同:

  • bool
  • DateTime
  • decimal
  • double
  • float
  • Guid
  • int
  • long
  • string

支持的类型包括:

  • 上述类型的可为空变体。
  • 上述类型的数组,无论它们是可为空还是不可为空。

如果参数存在,则替换查询参数值

Navigation.GetUriWithQueryParameter("full name", "Morena Baccarin")
当前 URL 生成的 URL
scheme://host/?full%20name=David%20Krumholtz&age=42 scheme://host/?full%20name=Morena%20Baccarin&age=42
scheme://host/?fUlL%20nAmE=David%20Krumholtz&AgE=42 scheme://host/?full%20name=Morena%20Baccarin&AgE=42
scheme://host/?full%20name=Jewel%20Staite&age=42&full%20name=Summer%20Glau scheme://host/?full%20name=Morena%20Baccarin&age=42&full%20name=Morena%20Baccarin
scheme://host/?full%20name=&age=42 scheme://host/?full%20name=Morena%20Baccarin&age=42
scheme://host/?full%20name= scheme://host/?full%20name=Morena%20Baccarin

如果参数不存在,则追加查询参数和值

Navigation.GetUriWithQueryParameter("name", "Morena Baccarin")
当前 URL 生成的 URL
scheme://host/?age=42 scheme://host/?age=42&name=Morena%20Baccarin
scheme://host/ scheme://host/?name=Morena%20Baccarin
scheme://host/? scheme://host/?name=Morena%20Baccarin

如果参数值为 null,则删除查询参数

Navigation.GetUriWithQueryParameter("full name", (string)null)
当前 URL 生成的 URL
scheme://host/?full%20name=David%20Krumholtz&age=42 scheme://host/?age=42
scheme://host/?full%20name=Sally%20Smith&age=42&full%20name=Summer%20Glau scheme://host/?age=42
scheme://host/?full%20name=Sally%20Smith&age=42&FuLl%20NaMe=Summer%20Glau scheme://host/?age=42
scheme://host/?full%20name=&age=42 scheme://host/?age=42
scheme://host/?full%20name= scheme://host/

添加、更新和删除查询参数

如下示例中:

  • 删除了 name(如果存在)。
  • age 添加了值 25 (int)(如果不存在)。 对 age 更新了值 25(如果存在)。
  • 添加了 eye color 或为其更新了值 green
Navigation.GetUriWithQueryParameters(
    new Dictionary<string, object?>
    {
        ["name"] = null,
        ["age"] = (int?)25,
        ["eye color"] = "green"
    })
当前 URL 生成的 URL
scheme://host/?name=David%20Krumholtz&age=42 scheme://host/?age=25&eye%20color=green
scheme://host/?NaMe=David%20Krumholtz&AgE=42 scheme://host/?age=25&eye%20color=green
scheme://host/?name=David%20Krumholtz&age=42&keepme=true scheme://host/?age=25&keepme=true&eye%20color=green
scheme://host/?age=42&eye%20color=87 scheme://host/?age=25&eye%20color=green
scheme://host/? scheme://host/?age=25&eye%20color=green
scheme://host/ scheme://host/?age=25&eye%20color=green

支持可枚举值

如下示例中:

  • 添加了 full name 或将其更新为 Morena Baccarin(单个值)。
  • 添加了 ping 参数或将其替换为 351687240
Navigation.GetUriWithQueryParameters(
    new Dictionary<string, object?>
    {
        ["full name"] = "Morena Baccarin",
        ["ping"] = new int?[] { 35, 16, null, 87, 240 }
    })
当前 URL 生成的 URL
scheme://host/?full%20name=David%20Krumholtz&ping=8&ping=300 scheme://host/?full%20name=Morena%20Baccarin&ping=35&ping=16&ping=87&ping=240
scheme://host/?ping=8&full%20name=David%20Krumholtz&ping=300 scheme://host/?ping=35&full%20name=Morena%20Baccarin&ping=16&ping=87&ping=240
scheme://host/?ping=8&ping=300&ping=50&ping=68&ping=42 scheme://host/?ping=35&ping=16&ping=87&ping=240&full%20name=Morena%20Baccarin

若要使用添加或修改的查询字符串进行导航,请将生成的 URL 传递给 NavigateTo

下面的示例调用:

Navigation.NavigateTo(
    Navigation.GetUriWithQueryParameter("name", "Morena Baccarin"));

可从 NavigationManager.Uri 属性中获取请求的查询字符串:

@inject NavigationManager Navigation

...

var query = new Uri(Navigation.Uri).Query;

若要分析查询字符串的参数,一种方法是结合使用 URLSearchParamsJavaScript (JS) 互操作

export createQueryString = (string queryString) => new URLSearchParams(queryString);

有关使用 JavaScript 模块进行 JavaScript 隔离的详细信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

对命名元素的哈希处理路由

使用以下方法,通过对命名元素的哈希处理 (#) 引用,导航到该元素。 对组件中的元素路由,以及对外部组件中的元素路由,均使用根相对路径。 前导正斜杠 (/) 是可选的。

以下每种方法的示例均演示了如何导航到 Counter 组件中 idtargetElement 的元素:

  • href 的 Anchor 元素 (<a>):

    <a href="/counter#targetElement">
    
  • hrefNavLink 组件:

    <NavLink href="/counter#targetElement">
    
  • NavigationManager.NavigateTo 传递相对 URL:

    Navigation.NavigateTo("/counter#targetElement");
    

以下示例演示了对组件内命名 H2 标题和外部组件的哈希处理路由。

Index (Pages/Index.razor) 和 Counter (Pages/Counter.razor) 组件中,将以下标记置于现有组件标记的底部,以用作导航目标。 <div> 创建人工垂直空间来演示浏览器滚动行为:

<div class="border border-info rounded bg-info" style="height:500px"></div>

<h2 id="targetElement">Target H2 heading</h2>
<p>Content!</p>

向应用添加以下 HashedRouting 组件。

Pages/HashedRouting.razor:

@page "/hashed-routing"
@inject NavigationManager Navigation

<PageTitle>Hashed routing</PageTitle>

<h1>Hashed routing to named elements</h1>

<ul>
    <li>
        <a href="/hashed-routing#targetElement">
            Anchor in this component
        </a>
    </li>
    <li>
        <a href="/#targetElement">
            Anchor to the <code>Index</code> component
        </a>
    </li>
    <li>
        <a href="/counter#targetElement">
            Anchor to the <code>Counter</code> component
        </a>
    </li>
    <li>
        <NavLink href="/hashed-routing#targetElement">
            Use a `NavLink` component in this component
        </NavLink>
    </li>
    <li>
        <button @onclick="NavigateToElement">
            Navigate with <code>NavigationManager</code> to the 
            <code>Counter</code> component
        </button>
    </li>
</ul>

<div class="border border-info rounded bg-info" style="height:500px"></div>

<h2 id="targetElement">Target H2 heading</h2>
<p>Content!</p>

@code {
    private void NavigateToElement()
    {
        Navigation.NavigateTo("/counter#targetElement");
    }
}

用户与 <Navigating> 内容的交互

Router 组件可向用户指示页面正在转换。

App 组件 (App.razor) 的顶部,为 Microsoft.AspNetCore.Components.Routing 命名空间添加 @using 指令:

@using Microsoft.AspNetCore.Components.Routing

向组件添加一个 <Navigating> 标记,其中包含在页面转换事件期间显示的标记。 有关详细信息,请参阅 Navigating(API 文档)。

App 组件 (App.razor) 的路由器元素内容 (<Router>...</Router>) 中:

<Navigating>
    <p>Loading the requested page&hellip;</p>
</Navigating>

有关使用 Navigating 属性的示例,请参阅 ASP.NET Core Blazor WebAssembly 中的延迟加载程序集

使用 OnNavigateAsync 处理异步导航事件

Router 组件支持 OnNavigateAsync 功能。 当用户执行以下操作时,将调用 OnNavigateAsync 处理程序:

  • 通过直接在自己的浏览器中导航到某个路由,首次访问该路由。
  • 使用链接或 NavigationManager.NavigateTo 调用导航到一个新路由。

App 组件 (App.razor) 中:

<Router AppAssembly="@typeof(App).Assembly" 
    OnNavigateAsync="@OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext args)
    {
        ...
    }
}
<Router AppAssembly="@typeof(Program).Assembly" 
    OnNavigateAsync="@OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext args)
    {
        ...
    }
}

注意

随着 ASP.NET Core 5.0.1 的发布及任何附加 5.x 版本的推出,Router 组件包含 PreferExactMatches 参数(设置为 @true)。 有关详细信息,请参阅从 ASP.NET Core 3.1 迁移到 5.0

有关使用 OnNavigateAsync 的示例,请参阅 ASP.NET Core Blazor WebAssembly 中的延迟加载程序集

在 Blazor Server 应用或托管的 Blazor WebAssembly 应用中的服务器上预呈现内容时,OnNavigateAsync 会执行两次:

  • 第一次是在请求的终结点组件最初静态呈现时。
  • 第二次是浏览器呈现终结点组件时。

为了防止 OnNavigateAsync 中的开发人员代码执行两次,App 组件可以存储 NavigationContext,以供 OnAfterRender{Async} 使用,可以在其中检查 firstRender。 有关详细信息,请参阅“Blazor 生命周期”一文中的使用 JavaScript 互操作预呈现

处理 OnNavigateAsync 中的取消

传递到 OnNavigateAsync 回调的 NavigationContext 对象包含的 CancellationToken 在发生新导航事件时进行设置。 设置此取消标记时,OnNavigateAsync 回调必须引发,以避免在过时的导航中继续运行 OnNavigateAsync 回调。

如果用户导航到某个终结点,但随后立即导航到新的终结点,则应用不应继续为第一个终结点运行 OnNavigateAsync 回叫。

在下面的 App 组件示例中:

  • 取消标记在对 PostAsJsonAsync 的调用中传递,用户离开 /about 终结点后它可取消 POST。
  • 如果用户离开 /store 终结点,则会在产品预提取操作期间设置取消操作。

App.razor:

@inject HttpClient Http
@inject ProductCatalog Products

<Router AppAssembly="@typeof(App).Assembly" 
    OnNavigateAsync="@OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext context)
    {
        if (context.Path == "/about") 
        {
            var stats = new Stats { Page = "/about" };
            await Http.PostAsJsonAsync("api/visited", stats, 
                context.CancellationToken);
        }
        else if (context.Path == "/store")
        {
            var productIds = new[] { 345, 789, 135, 689 };

            foreach (var productId in productIds) 
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                Products.Prefetch(productId);
            }
        }
    }
}
@inject HttpClient Http
@inject ProductCatalog Products

<Router AppAssembly="@typeof(Program).Assembly" 
    OnNavigateAsync="@OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext context)
    {
        if (context.Path == "/about") 
        {
            var stats = new Stats { Page = "/about" };
            await Http.PostAsJsonAsync("api/visited", stats, 
                context.CancellationToken);
        }
        else if (context.Path == "/store")
        {
            var productIds = new[] { 345, 789, 135, 689 };

            foreach (var productId in productIds) 
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                Products.Prefetch(productId);
            }
        }
    }
}

注意

随着 ASP.NET Core 5.0.1 的发布及任何附加 5.x 版本的推出,Router 组件包含 PreferExactMatches 参数(设置为 @true)。 有关详细信息,请参阅从 ASP.NET Core 3.1 迁移到 5.0

注意

如果取消 NavigationContext 中的取消标记会导致意外的行为(例如,呈现上一次导航中的组件),则不会引发。

处理/阻止位置更改

RegisterLocationChangingHandler 注册一个处理程序来处理传入的导航事件。 LocationChangingContext 提供的处理程序上下文包括以下属性:

组件可以在其 OnAfterRenderOnAfterRenderAsync 方法 中注册多个位置更改处理程序。 导航调用在整个应用(跨多个组件)中注册的所有位置更改处理程序,并且任何内部导航都并行执行它们。 除了调用 NavigateTo 处理程序之外:

  • 选择内部链接时,这些链接指向应用基路径下的 URL。
  • 在浏览器中使用前进和后退按钮进行导航时。

处理程序仅针对应用中的内部导航执行。 如果用户选择导航到另一个站点的链接或手动将地址栏更改为另一个站点,则不会执行位置更改处理程序。

实现 IDisposable 并释放已注册的处理程序以取消注册它们。 有关详细信息,请参阅 ASP.NET Core Razor 组件生命周期

重要

处理位置更改时,请勿尝试通过 JavaScript (JS) 互操作执行文档对象模型 (DOM) 清理任务。 在客户端上使用 JS 中的 MutationObserver 模式。 有关详细信息,请参阅从 ASP.NET Core Blazor 中的 .NET 方法调用 JavaScript 函数

在以下示例中,为导航事件注册了位置更改处理程序。

Pages/NavHandler.razor:

@page "/nav-handler"
@inject NavigationManager Navigation
@implements IDisposable

<p>
    <button @onclick="@(() => Navigation.NavigateTo("/"))">
        Home (Allowed)
    </button>
    <button @onclick="@(() => Navigation.NavigateTo("/counter"))">
        Counter (Prevented)
    </button>
</p>

@code {
    private IDisposable? registration;

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            registration = 
                Navigation.RegisterLocationChangingHandler(OnLocationChanging);
        }
    }

    private ValueTask OnLocationChanging(LocationChangingContext context)
    {
        if (context.TargetLocation == "/counter")
        {
            context.PreventNavigation();
        }

        return ValueTask.CompletedTask;
    }

    public void Dispose() => registration?.Dispose();
}

由于可以异步取消内部导航,因此可能会出现对已注册处理程序的多个重叠调用。 例如,当用户快速选择页面上的后退按钮或在执行导航之前选择多个链接时,可能会发生多个处理程序调用。 下面是异步导航逻辑的摘要:

  • 如果注册了任何位置更改处理程序,则所有导航最初都将还原,如果导航没有取消,则会重播。
  • 如果发出重叠的导航请求,则最新的请求始终会取消先前的请求,这意味着:
    • 应用可将多个后退和前进按钮选择视为单个选择。
    • 如果用户在导航完成之前选择了多个链接,则最后选择的链接决定该导航。

有关将 NavigationOptions 传递到 NavigateTo 以控制导航历史记录堆栈的条目和状态的详细信息,请参阅导航选项部分。

有关其他示例代码,请参阅 BasicTestAppdotnet/aspnetcore 参考源)中的 NavigationManagerComponent

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

只要呈现导航事件,NavigationLock 组件就会将其拦截,除非决定继续或取消,否则将一直有效“锁定”任何给定的导航。 当导航拦截的范围可以限定为组件的生存期时,使用 NavigationLock

NavigationLock 参数:

在下面的 NavLock 组件中:

Pages/NavLock.razor:

@page "/nav-lock"
@inject IJSRuntime JSRuntime
@inject NavigationManager Navigation

<NavigationLock ConfirmExternalNavigation="true" 
    OnBeforeInternalNavigation="OnBeforeInternalNavigation" />

<p>
    <button @onclick="Navigate">Navigate</button>
</p>

<p>
    <a href="https://www.microsoft.com">Microsoft homepage</a>
</p>

@code {
    private void Navigate()
    {
        Navigation.NavigateTo("/");
    }

    private async Task OnBeforeInternalNavigation(LocationChangingContext context)
    {
        var isConfirmed = await JSRuntime.InvokeAsync<bool>("confirm", 
            "Are you sure you want to navigate to the Index page?");

        if (!isConfirmed)
        {
            context.PreventNavigation();
        }
    }
}

有关其他示例代码,请参阅 BasicTestAppdotnet/aspnetcore 参考源)中的 ConfigurableNavigationLock 组件

创建导航链接时,请使用 NavLink 组件代替 HTML 超链接元素 (<a>)。 NavLink 组件的行为方式类似于 <a> 元素,但它根据其 href 是否与当前 URL 匹配来切换 active CSS 类。 active 类可帮助用户了解所显示导航链接中的哪个页面是活动页面。 也可以选择将 CSS 类名分配到 NavLink.ActiveClass,以便在当前路由与 href 匹配时将自定义 CSS 类应用到呈现的链接。

注意

NavMenu 组件 (NavMenu.razor) 将在 Blazor 项目模板生成的应用的 Shared 文件夹中提供。

有两个 NavLinkMatch 选项可分配给 <NavLink> 元素的 Match 属性:

在前面的示例中,HomeNavLinkhref="" 与主页 URL 匹配,并且仅在应用程序的默认基路径 (/) 处接收 active CSS 类。 当用户访问带有 component 前缀的任何 URL(例如,/component/component/another-segment)时,第二个 NavLink 接收 active 类。

其他 NavLink 组件属性会传递到呈现的定位标记。 在以下示例中,NavLink 组件包括 target 属性:

<NavLink href="example-page" target="_blank">Example page</NavLink>

呈现以下 HTML 标记:

<a href="example-page" target="_blank">Example page</a>

警告

由于 Blazor 呈现子内容的方式,如果在 NavLink(子)组件的内容中使用递增循环变量,则在 for 循环内呈现 NavLink 组件需要本地索引变量:

@for (int c = 0; c < 10; c++)
{
    var current = c;
    <li ...>
        <NavLink ... href="@c">
            <span ...></span> @current
        </NavLink>
    </li>
}

子内容中使用循环变量的任何子组件(而不仅仅是 NavLink 组件)都要求必须在此方案中使用索引变量。

或者,将 foreach 循环用于 Enumerable.Range

@foreach (var c in Enumerable.Range(0,10))
{
    <li ...>
        <NavLink ... href="@c">
            <span ...></span> @c
        </NavLink>
    </li>
}

ASP.NET Core 终结点路由集成

本部分仅适用于 Blazor Server应用。

Blazor Server 已集成到 ASP.NET Core 终结点路由中。 ASP.NET Core 应用配置为接受 Program.cs 中带有 MapBlazorHub 的交互式组件的传入连接:

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

Blazor Server 已集成到 ASP.NET Core 终结点路由中。 ASP.NET Core 应用配置为接受 Startup.Configure 中带有 MapBlazorHub 的交互式组件的传入连接。

Startup.cs:

using Microsoft.AspNetCore.Builder;

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        });
    }
}
using Microsoft.AspNetCore.Builder;

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        });
    }
}

典型的配置是将所有请求路由到 Razor 页面,该页面充当 Blazor Server应用的服务器端部分的主机。 按照约定,主机页面通常在应用的 Pages 文件夹中被命名为 _Host.cshtml

主机文件中指定的路由称为回退路由,因为它在路由匹配中以较低的优先级运行。 其他路由不匹配时,会使用回退路由。 这让应用能够使用其他控制器和页面,而不会干扰 Blazor Server应用中的组件路由。

若要了解如何为非根 URL 服务器托管配置 MapFallbackToPage,请参阅托管和部署 ASP.NET Core Blazor