翻譯自 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 路由及導航開發指南
https://www.ezzylearning.net/tutorial/a-developers-guide-to-blazor-routing-and-navigation A Developer’s Guide To Blazor Routing and Navigation ︎
https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.components.routing.router ︎