Blazor 路由及導(dǎo)航開發(fā)指南
本文轉(zhuǎn)載自微信公眾號「技術(shù)譯站」,作者技術(shù)譯民 。轉(zhuǎn)載本文請聯(lián)系技術(shù)譯站公眾號。
翻譯自 Waqas Anwar 2021年4月2日的文章 《A Developer’s Guide To Blazor Routing and Navigation》 [1]
檢查傳入的請求 URL 并將它們導(dǎo)航到對應(yīng)的視圖或頁面是每個(gè)單頁應(yīng)用程序 (SPA) 框架的基本功能。Blazor Server 和 WebAssembly 應(yīng)用程序也同樣支持使用一些內(nèi)置組件和服務(wù)進(jìn)行路由。在本教程中,我將向您介紹在 Blazor 應(yīng)用程序中實(shí)現(xiàn)路由所需了解的所有內(nèi)容。
Blazor 應(yīng)用程序中的路由配置
在開始為不同的 Blazor 組件/頁面創(chuàng)建路由之前,我們需要了解如何將 Blazor Server 應(yīng)用程序集成到 ASP.NET Core Endpoint 路由中。Blazor Server 應(yīng)用程序通過 SignalR 連接與客戶端進(jìn)行通信,為了接受 Blazor 組件傳入的連接,我們在 Startup.cs 文件的 Configure 方法中調(diào)用了 MapBlazorHub 方法,如下所示:
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapBlazorHub();
- endpoints.MapFallbackToPage("/_Host");
- });
默認(rèn)配置將所有請求都轉(zhuǎn)發(fā)到一個(gè) Razor 頁面,該頁面扮演 Blazor Server 應(yīng)用程序服務(wù)端主機(jī)的角色。按照慣例,該主頁是 _Host.cshtml,它位于應(yīng)用程序的 Pages 文件夾中。該主文件中指定的路由稱之為應(yīng)急路由,在路由匹配中具有極低的優(yōu)先級,這意味著當(dāng)沒有其他路由匹配時(shí),才會(huì)使用該路由。
Blazor 路由組件介紹
Router[2] 組件是 Blazor 中的內(nèi)置組件之一,用在 Blazor 應(yīng)用程序的 App 組件之中。該組件啟用了 Blazor 應(yīng)用程序中的路由,并提供與當(dāng)前導(dǎo)航狀態(tài)相對應(yīng)的路由數(shù)據(jù)。它攔截傳入的請求并呈現(xiàn)與請求 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 組件的屬性。
當(dāng)編譯 Blazor 組件 (.razor) 時(shí),它們生成的 C# 類會(huì)保存在 obj\Debug\net5.0\Razor\Pages 文件夾中。
如果您打開任意一個(gè)已編譯的文件,將會(huì)注意到在編譯之后,所有帶有 @page 指令的組件都生成了一個(gè)帶有 RouteAttribute 特性的類。
當(dāng)應(yīng)用程序啟動(dòng)時(shí),會(huì)掃描通過 AppAssembly 屬性指定的程序集,從所有指定了 RouteAttribute 特性的類中收集路由信息。
- <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
如果您創(chuàng)建了獨(dú)立的組件類庫,并希望應(yīng)用程序從這些程序集中掃描和加載路由,那么您可以使用 AdditionalAssemblies 屬性來接受一個(gè) Assembly 對象集合。
下面是一個(gè)從定義在組件類庫中的兩個(gè)可路由組件(Component1 和 Component2)加載路由信息的示例。
- <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true"
- AdditionalAssemblies="new[] { typeof(Component1).Assembly, typeof(Component2).Assembly }">
- </Router>
在運(yùn)行時(shí),RouteView 組件從 Router 接收 RouteData 以及任意路由參數(shù),并使用組件中定義的布局渲染指定的組件。如果未定義布局,則使用 DefaultLayout 屬性指定的布局。默認(rèn)的布局通常是 Shared 文件夾中的 MainLayout 組件,不過您也可以創(chuàng)建并指定一個(gè)自定義布局。
Found 模板用于在找到匹配的路由時(shí)顯示其內(nèi)容,正如您在下圖中所看到的那樣,其中找到了一個(gè)匹配路由,并在瀏覽器中呈現(xiàn)了一個(gè) Counter 頁面。
NotFound 模板用于在沒有找到匹配的路由時(shí)顯示內(nèi)容。默認(rèn)情況下,NotFound 模板僅顯示一條消息,如下面的截圖所示。
我們還可以創(chuàng)建自定義錯(cuò)誤的布局和頁面,以顯示自定義錯(cuò)誤頁面。讓我們在 Shared 文件夾中創(chuàng)建一個(gè)新的名為 ErrorLayout.razor 的自定義布局。
ErrorLayout.razor
- @inherits LayoutComponentBase
- <main role="main" class="container">
- <div class="text-center">
- @Body
- </div>
- </main>
然后將 LayoutView 組件的 Layout 屬性改為 ErrorLayout,并將 LayoutView 里的內(nèi)容修改如下:
- <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>
現(xiàn)在,如果您在瀏覽器中運(yùn)行應(yīng)用程序,并嘗試訪問一個(gè)未在應(yīng)用中任何位置指定過的 URL,那么您將會(huì)看到一個(gè)自定義的 404 錯(cuò)誤頁面,如下所示。
所有 Blazor 應(yīng)用程序都應(yīng)將 PreferExactMatches 特性顯式地設(shè)置為 @true,以便路由匹配更傾向于精確匹配,而不是通配符匹配。根據(jù) Microsoft 官方文檔,此特性從 .NET 6 開始將不可用,路由器將總是更傾向于精確匹配。
定義路由、參數(shù)和約束
在我們學(xué)習(xí)如何為 Blazor 組件定義路由之前,我們需要確保下面的 base 標(biāo)簽在每個(gè)頁面都可用,以便正確地解析 URL。如果創(chuàng)建的是 Blazor Server 應(yīng)用程序,那么您可以將此標(biāo)簽添加到 Pages/_Host.cshtml 文件的 head 部分,如果是 Blazor WebAssembly 應(yīng)用程序,則可以將此標(biāo)簽添加到 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++;
- }
- }
現(xiàn)在我們就可以使用 /counter URL 訪問 Counter 組件了。
我們還可以使用多個(gè) @page 指令定義多個(gè)路由模板,如下面例所示。
- @page "/counter"
- @page "/mycounter"
這意味著現(xiàn)在也可以使用 /mycounter URL 訪問同一個(gè) Counter 組件:
使用路由參數(shù)將數(shù)據(jù)從一個(gè)頁面?zhèn)鬟f到另一個(gè)頁面是十分常見的做法,Blazor 路由模板支持路由參數(shù)。路由參數(shù)名稱不區(qū)分大小寫,一旦我們定義了路由參數(shù),路由器就會(huì)自動(dòng)填充對應(yīng)的具有相同名稱的組件屬性。例如,在下面的代碼片段中,我們在組件中定義了一個(gè)路由參數(shù) title,并創(chuàng)建了一個(gè)對應(yīng)的屬性 Title。此屬性將自動(dòng)使用路由參數(shù)文本的值填充。然后,我們在 h1 元素中顯示 Title 屬性作為頁面的標(biāo)題。
- @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++;
- }
- }
運(yùn)行應(yīng)用程序,并嘗試在地址欄中 /counter/ 之后指定任意的字符串,您將看到路由參數(shù)的值會(huì)顯示為頁面標(biāo)題。
我們還可以定義可選的路由參數(shù),如下例所示,其中 title 是可選參數(shù),因?yàn)樵诖藚?shù)名稱后面帶有問號 (?)。假如我們不提供此路由參數(shù)的值,該參數(shù)將在 OnInitialized 方法中使用默認(rèn)值 Counter 進(jìn)行初始化。
- @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 還支持路由約束,在路由上強(qiáng)制類型匹配。在下面的代碼片段中,我創(chuàng)建了一個(gè) int 類型的路由參數(shù) start,這意味著現(xiàn)在我只能為此路由參數(shù)提供整數(shù)值。計(jì)數(shù)器現(xiàn)在將以路由參數(shù)中指定的值開始計(jì)數(shù)。
- @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++;
- }
- }
在瀏覽器中運(yùn)行應(yīng)用程序,并在 URL 中指定任一整數(shù)值,比如 /counter/4,您會(huì)看到計(jì)數(shù)器將以該起始值遞增。
下表顯示了 Blazor 路由約束支持的類型。
還可以定義多個(gè)路由參數(shù),如下例所示,我們將 start 和 increment 定義為 int 類型的參數(shù)。
- @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;
- }
- }
如下所示,運(yùn)行應(yīng)用程序并在 URL 地址中指定 start 和 increment 的值,您會(huì)注意到,當(dāng)您每次點(diǎn)擊 Click me 按鈕時(shí),計(jì)數(shù)器不僅會(huì)以數(shù)字 2 開始計(jì)數(shù),而且會(huì)以 3 遞增。
Blazor NavigationManager 服務(wù)概述
NavigationManager 服務(wù)允許我們在 C# 代碼中管理 URI 和導(dǎo)航。NavigationManager 類具有以下常見的屬性、方法和事件。
讓我們來創(chuàng)建一個(gè)頁面,查看一下以上屬性和方法的一些實(shí)際行為。創(chuàng)建一個(gè)新的 Blazor 組件并使用 @inject 指令注入 NavigationManager 服務(wù)。嘗試在頁面上打印出 Uri 和 BaseUri 屬性,來查看一下它們返回的是什么類型的 URI。
- @page "/navigationmanager"
- @inject NavigationManager nvm
- <h3>Navigation Manager</h3>
- <br />
- <p>@nvm.Uri</p>
- <p>@nvm.BaseUri</p>
運(yùn)行應(yīng)用程序,您將在瀏覽器中看到類似以下內(nèi)容的輸出。Uri 屬性顯示當(dāng)前頁面的絕對 URI,而 BaseUri 屬性顯示當(dāng)前的基 URI。
在頁面上添加兩個(gè)按鈕 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");
- }
- }
運(yùn)行應(yīng)用程序并試著點(diǎn)擊這兩個(gè)按鈕,將按預(yù)期的那樣,您可以導(dǎo)航到主頁和計(jì)數(shù)器頁面。
如果不想以編程方式處理導(dǎo)航,而想在 HTML 中生成超鏈接,則可以使用 Blazor NavLink 組件。NavLink 組件類似于 HTML 中的 元素,具有一些很酷的功能。如果 NavLink 的 href 特性值與當(dāng)前的 URL 相匹配,則會(huì)自動(dòng)切換該元素的 active CSS 類(class)。這就使得我們可以在當(dāng)前選中的鏈接上應(yīng)用不同的樣式。您可以在 Shared/NavMenu.razor 文件中看到這個(gè)組件的用法。
- <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 組件還有一個(gè) Match 屬性,可以設(shè)置為以下選項(xiàng)之一:
- NavLinkMatch.All:指定當(dāng) NavLink 與整個(gè)當(dāng)前 URL 匹配時(shí)應(yīng)處于活動(dòng)狀態(tài)。
- NavLinkMatch.Prefix(默認(rèn)值):指定當(dāng) NavLink 與當(dāng)前 URL 的任意前綴匹配時(shí)應(yīng)處于活動(dòng)狀態(tài)。
Match 屬性:獲取或設(shè)置一個(gè)值,該值表示 URL 匹配行為。
總結(jié)
在本教程中,我嘗試介紹 Blazor 應(yīng)用程序中的多種路由功能,還介紹了開發(fā)者可用的與路由相關(guān)的一些組件和服務(wù)。我希望您現(xiàn)在能夠更熟練地定義路由、參數(shù)和約束。如果您喜歡本教程,請與他人分享以傳播知識。