Blazor 路由及導航開發指南
翻譯自 Waqas Anwar 2021年4月2日的文章 《A Developer』s Guide To Blazor Routing and Navigation》 [1]
檢查傳入的請求 URL 並將它們導航到對應的視圖或頁面是每個單頁應用程序 (SPA) 框架的基本功能。Blazor Server 和 WebAssembly 應用程序也同樣支持使用一些內置組件和服務進行路由。在本教程中,我將向您介紹在 Blazor 應用程序中實現路由所需了解的所有內容。
Blazor 應用程序中的路由配置
在開始為不同的 Blazor 組件/頁面創建路由之前,我們需要了解如何將 Blazor Server 應用程序集成到 ASP.NET Core Endpoint 路由中。Blazor Server 應用程序通過 SignalR 連接與客戶端進行通信,為了接受 Blazor 組件傳入的連接,我們在 Startup.cs 文件的 Configure 方法中調用了 MapBlazorHub 方法,如下所示:
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
默認配置將所有請求都轉發到一個 Razor 頁面,該頁面扮演 Blazor Server 應用程序服務端主機的角色。按照慣例,該主頁是 _Host.cshtml,它位於應用程序的 Pages 文件夾中。該主文件中指定的路由稱之為應急路由,在路由匹配中具有極低的優先級,這意味着當沒有其他路由匹配時,才會使用該路由。
Blazor 路由組件介紹
Router[2] 組件是 Blazor 中的內置組件之一,用在 Blazor 應用程序的 App 組件之中。該組件啟用了 Blazor 應用程序中的路由,並提供與當前導航狀態相對應的路由數據。它攔截傳入的請求並呈現與請求 URL 相匹配的頁面。
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<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>
下表顯示了 Router 組件的屬性。
屬性 | 說明 |
---|---|
AdditionalAssemblies | 獲取或設置其他程序集的集合,這些程序集應在搜索可與 URI 匹配的組件時搜索。 |
AppAssembly | 獲取或設置應在其中搜索與 URI 匹配的組件的程序集。 |
Found | 獲取或設置當為請求的路由找到匹配項時要顯示的內容。 |
Navigating | 獲取或設置異步導航正在進行時顯示的內容。 |
NotFound | 獲取或設置當沒有為請求的路由找到匹配項時要顯示的內容。 |
OnNavigateAsync | 獲取或設置在導航到新頁之前應調用的處理程序。 |
當編譯 Blazor 組件 (.razor) 時,它們生成的 C# 類會保存在 obj\Debug\net5.0\Razor\Pages 文件夾中。
如果您打開任意一個已編譯的文件,將會注意到在編譯之後,所有帶有 @page 指令的組件都生成了一個帶有 RouteAttribute 特性的類。
當應用程序啟動時,會掃描通過 AppAssembly 屬性指定的程序集,從所有指定了 RouteAttribute 特性的類中收集路由信息。
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
如果您創建了獨立的組件類庫,並希望應用程序從這些程序集中掃描和加載路由,那麼您可以使用 AdditionalAssemblies 屬性來接受一個 Assembly 對象集合。
下面是一個從定義在組件類庫中的兩個可路由組件(Component1 和 Component2)加載路由信息的示例。
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true"
AdditionalAssemblies="new[] { typeof(Component1).Assembly, typeof(Component2).Assembly }">
</Router>
在運行時,RouteView 組件從 Router 接收 RouteData 以及任意路由參數,並使用組件中定義的布局渲染指定的組件。如果未定義布局,則使用 DefaultLayout 屬性指定的布局。默認的布局通常是 Shared 文件夾中的 MainLayout 組件,不過您也可以創建並指定一個自定義布局。
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
Found 模板用於在找到匹配的路由時顯示其內容,正如您在下圖中所看到的那樣,其中找到了一個匹配路由,並在瀏覽器中呈現了一個 Counter 頁面。
NotFound 模板用於在沒有找到匹配的路由時顯示內容。默認情況下,NotFound 模板僅顯示一條消息,如下面的截圖所示。
我們還可以創建自定義錯誤的布局和頁面,以顯示自定義錯誤頁面。讓我們在 Shared 文件夾中創建一個新的名為 ErrorLayout.razor 的自定義布局。
ErrorLayout.razor
@inherits LayoutComponentBase
<main role="main" class="container">
<div class="text-center">
@Body
</div>
</main>
然後將 LayoutView 組件的 Layout 屬性改為 ErrorLayout,並將 LayoutView 里的內容修改如下:
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(ErrorLayout)">
<h1 class="display-1">404</h1>
<h1 class="display-4">Not Found</h1>
<p class="lead">
Oops! Looks like this page doesn't exist.
</p>
</LayoutView>
</NotFound>
</Router>
現在,如果您在瀏覽器中運行應用程序,並嘗試訪問一個未在應用中任何位置指定過的 URL,那麼您將會看到一個自定義的 404 錯誤頁面,如下所示。
所有 Blazor 應用程序都應將 PreferExactMatches 特性顯式地設置為 @true
,以便路由匹配更傾向於精確匹配,而不是通配符匹配。根據 Microsoft 官方文檔,此特性從 .NET 6 開始將不可用,路由器將總是更傾向於精確匹配。
定義路由、參數和約束
在我們學習如何為 Blazor 組件定義路由之前,我們需要確保下面的 base
標籤在每個頁面都可用,以便正確地解析 URL。如果創建的是 Blazor Server 應用程序,那麼您可以將此標籤添加到 Pages/_Host.cshtml 文件的 head
部分,如果是 Blazor WebAssembly 應用程序,則可以將此標籤添加到 wwwroot/index.html 文件中。
<base href="~/" />
要定義路由,我們可以使用 @page 指令,如下面的 Counter 組件示例所示。
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
現在我們就可以使用 /counter URL 訪問 Counter 組件了。
我們還可以使用多個 @page 指令定義多個路由模板,如下面例所示。
@page "/counter"
@page "/mycounter"
這意味着現在也可以使用 /mycounter URL 訪問同一個 Counter 組件:
使用路由參數將數據從一個頁面傳遞到另一個頁面是十分常見的做法,Blazor 路由模板支持路由參數。路由參數名稱不區分大小寫,一旦我們定義了路由參數,路由器就會自動填充對應的具有相同名稱的組件屬性。例如,在下面的代碼片段中,我們在組件中定義了一個路由參數 title,並創建了一個對應的屬性 Title。此屬性將自動使用路由參數文本的值填充。然後,我們在 h1
元素中顯示 Title 屬性作為頁面的標題。
@page "/counter/{title}"
<h1>@Title</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
[Parameter]
public string Title { get; set; }
private void IncrementCount()
{
currentCount++;
}
}
運行應用程序,並嘗試在地址欄中 /counter/ 之後指定任意的字符串,您將看到路由參數的值會顯示為頁面標題。
我們還可以定義可選的路由參數,如下例所示,其中 title
是可選參數,因為在此參數名稱後面帶有問號 (?)。假如我們不提供此路由參數的值,該參數將在 OnInitialized 方法中使用默認值 Counter 進行初始化。
@page "/counter/{title?}"
<h1>@Title</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
[Parameter]
public string Title { get; set; }
protected override void OnInitialized()
{
Title = Title ?? "Counter";
}
private void IncrementCount()
{
currentCount++;
}
}
Blazor 還支持路由約束,在路由上強制類型匹配。在下面的代碼片段中,我創建了一個 int
類型的路由參數 start
,這意味着現在我只能為此路由參數提供整數值。計數器現在將以路由參數中指定的值開始計數。
@page "/counter/{start:int}"
<h1>Counter</h1>
<p>Current count: @Start</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
[Parameter]
public int Start { get; set; }
private void IncrementCount()
{
Start++;
}
}
在瀏覽器中運行應用程序,並在 URL 中指定任一整數值,比如 /counter/4,您會看到計數器將以該起始值遞增。
下表顯示了 Blazor 路由約束支持的類型。
約束 | 示例 | 匹配項示例 |
---|---|---|
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 |
還可以定義多個路由參數,如下例所示,我們將 start
和 increment
定義為 int
類型的參數。
@page "/counter/{start:int}/{increment:int}"
<h1>Counter</h1>
<p>Current count: @Start</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
[Parameter]
public int Start { get; set; }
[Parameter]
public int Increment { get; set; }
private void IncrementCount()
{
Start+=Increment;
}
}
如下所示,運行應用程序並在 URL 地址中指定 start
和 increment
的值,您會注意到,當您每次點擊 Click me 按鈕時,計數器不僅會以數字 2
開始計數,而且會以 3
遞增。
Blazor NavigationManager 服務概述
NavigationManager 服務允許我們在 C# 代碼中管理 URI 和導航。NavigationManager 類具有以下常見的屬性、方法和事件。
名稱 | 類型 | 說明 |
---|---|---|
BaseUri | 屬性 | 獲取或設置當前的基 URI。BaseUri 始終表示為字符串形式的絕對 URI,以斜杠結尾。 通常,這與文檔中 <base> 元素的 href 特性相對應。 |
Uri | 屬性 | 獲取或設置當前 URI。 Uri 始終以字符串形式表示為絕對 URI。 |
NavigateTo | 方法 | 導航到指定 URI。 |
ToAbsoluteUri | 方法 | 將相對 URI 轉換為絕對 URI。 |
ToBaseRelativePath | 方法 | 給定基 URI (比如,前面的 BaseUri 的返回值),將絕對 URI 轉換為相對於基 URI 前綴的 URI。 |
LocationChanged | 事件 | 當導航位置變化時觸發的事件。 |
讓我們來創建一個頁面,查看一下以上屬性和方法的一些實際行為。創建一個新的 Blazor 組件並使用 @inject
指令注入 NavigationManager 服務。 嘗試在頁面上打印出 Uri 和 BaseUri 屬性,來查看一下它們返回的是什麼類型的 URI。
@page "/navigationmanager"
@inject NavigationManager nvm
<h3>Navigation Manager</h3>
<br />
<p>@nvm.Uri</p>
<p>@nvm.BaseUri</p>
運行應用程序,您將在瀏覽器中看到類似以下內容的輸出。Uri 屬性顯示當前頁面的絕對 URI,而 BaseUri 屬性顯示當前的基 URI。
在頁面上添加兩個按鈕 Home Page 和 Counter Page,並在 @code 代碼塊中添加它們的 onclick
事件處理方法。在事件處理方法中,我們可以在 C# 代碼中使用 NavigateTo 方法將用戶重定向到其它的 Blazor 組件。
@page "/navigationmanager"
@inject NavigationManager nvm
<h3>Navigation Manager</h3>
<br />
<p>@nvm.Uri</p>
<p>@nvm.BaseUri</p>
<button class="btn btn-primary" @onclick="GoToHome">
Home Page
</button>
<button class="btn btn-primary" @onclick="GoToCounter">
Counter Page
</button>
@code {
private void GoToHome()
{
nvm.NavigateTo("/");
}
private void GoToCounter()
{
nvm.NavigateTo("counter");
}
}
運行應用程序並試着點擊這兩個按鈕,將按預期的那樣,您可以導航到主頁和計數器頁面。
如果不想以編程方式處理導航,而想在 HTML 中生成超鏈接,則可以使用 Blazor NavLink 組件。 NavLink 組件類似於 HTML 中的 <a>
元素,具有一些很酷的功能。如果 NavLink 的 href 特性值與當前的 URL 相匹配,則會自動切換該元素的 active CSS 類(class)。這就使得我們可以在當前選中的鏈接上應用不同的樣式。您可以在 Shared/NavMenu.razor 文件中看到這個組件的用法。
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
</ul>
</div>
NavLink 組件還有一個 Match 屬性,可以設置為以下選項之一:
- NavLinkMatch.All:指定當 NavLink 與整個當前 URL 匹配時應處於活動狀態。
- NavLinkMatch.Prefix(默認值):指定當 NavLink 與當前 URL 的任意前綴匹配時應處於活動狀態。
Match 屬性:獲取或設置一個值,該值表示 URL 匹配行為。
總結
在本教程中,我嘗試介紹 Blazor 應用程序中的多種路由功能,還介紹了開發者可用的與路由相關的一些組件和服務。我希望您現在能夠更熟練地定義路由、參數和約束。如果您喜歡本教程,請與他人分享以傳播知識。
相關閱讀:
- Blazor Server 和 WebAssembly 應用程序入門指南
- Blazor 組件入門指南
- Blazor 數據綁定開發指南
- Blazor 事件處理開發指南
- Blazor 組件之間使用 EventCallback 進行通信
- Blazor 路由及導航開發指南
-
//www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-routing-and-navigation A Developer』s Guide To Blazor Routing and Navigation ↩︎
-
//docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.router ↩︎