BlazorでSPAするぞ!(6) - Routing -正式版対応済み
※最終更新日: 2020/5/24 正式リリース版に対応修正しました。
という事で、↓↓↓の続きです。
今回はルーティングについて。
URLに対するページ(コンポーネント)のルーティング(マッチング)ですね。
1. ルーティング(Routing)
サンプルプロジェクトからルーティングを学んでいきたいと思います。
まず、dotnet new blazorwasm コマンドでプロジェクトを作成します。
dotnet new blazorwasm -o route_blazor
自動生成コードには、ルーティング実装も ある程度組み込まれています。
dotnet run で実行し、http://localhost:5000 にアクセスします。
これは「/」(ルート)へのアクセスになります。
「/」に対するルーティング設定は、「Pages/Index.razor」となっています。
サイドメニューから「Counter」をクリックします。
ブラウザのURLが http://localhost:5000/counter になりました。
「/counter」へのアクセス、そして「/counter」へのルーティング設定は「Pages/Counter.razor」となります。
同様にサイドメニューから「Fetch data」をクリックします。
ブラウザのURLが http://localhost:5000/fetchdata になりました。
「/fetchdata」へのアクセス、そして「/fetchdata」へのルーティング設定は「Pages/FetchData.razor」となります。
1.1. @Page
「/」「/counter」「/fetchdata」のような各URLに対する .razor ファイルのルーティングは、「@Page」ディレクティブで設定されます。
自動生成された3つのコンポーネント(Pages配下の.razorファイル)の定義は、それぞれ以下の通りです。
# Pages/Index.razor # @page "/" ...省略
# Pages/Counter.razor # @page "/counter" ...省略
# Pages/FetchData.razor # @page "/fetchdata" ...省略
1.2. ユーザ一覧画面を実装する場合
では、新規に「ユーザ一覧画面」を追加する想定の実装例を見てみましょう。
ルーティング設定するURLは「/users/list」とします。
.razorコンポーネントは「Pages/UserList.razor」ファイルで定義します。
以下がその定義になります。
# Pages/UserList.razor # @page "/users/list" ユーザ一覧画面です!!! ...省略...
1.3. URLが変わるけどSPA
自動生成されたプロジェクトを動作させるだけでもわかりますが、ページ(コンポーネント)を切り替えると、ブラウザのURLが切り替わります。
ただし、Blazorはクライアントサイドで動作しているので、対象のページ(コンポーネント)をサーバにHttpRequestするような事はせずSPAとして動作しています。
ChromeであればDevToolを表示し「Network」の監視を行えば確認することができます。
以下が実際に route_blazor を起動した後(ルートを表示した後)、「Counterメニュークリック」→「Fetch dataメニュークリック」という操作を行った際のNetwork状態です。
Counterメニュー クリック時:「favicon.ico」リクエストのみ
Fetch dataメニュー クリック時:「favicon.ico」リクエストおよび「weather.json」リクエストのみ。weather.jsonはアプリロジック的にサーバからjsonデータを明示的にリクエストしているため。
2. <Router>の初期化
@Pageディレクティブ定義によるルーティング設定方法を先に説明しましたが、アプリケーションでは<Router>を初期化しておく必要があります。
ルーティングを有効化する初期化処理は「/App.razor」で行います。
自動生成されたプロジェクトでも、以下のように既に実装が行われています。
# /App.razor # <Router AppAssembly="typeof(Program).Assembly" />
<Router>を定義し、AppAssembly属性に「typeof(Program).Assembly」を設定します。
Routerクラスは、「Microsoft.AspNetCore.Components.Routing」名前空間で定義されています。
2.1. 無効ルーティング時の表示
定義されたルーティングにマッチしなかった場合(フォールバックした場合)に表示するコンテンツを、「Router - NotFound属性」で指定することができます。
# /App.razor # ... <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> ...
「<p>Sorry, there's nothing at this address.</p>」部分は任意のコンポーネントに置き換えることも可能です。
以下のように「Shared/FallbackPage.razor」ページを用意して、App.razorのNotFountコンテンツとして指定します。
# Shared/FallbackPage.razor # @DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") <p>無効なパスが要求されました;;</p>
# App.razor # <Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <FallbackPage /> </LayoutView> </NotFound> </Router>
dotnet runで実行し、http://localhost:5000/invalid にアクセスしてみます。「/invalid」はどのコンポーネントにもルーティングされていません。
以下のように、フォールバックページ(Shared/FallbackPage.razor)が表示されました。
RouterのFallbackComponent属性が未設定の場合、無効なURLがアクセスされると、以下のように「Loading...」画面で止まってしまいます。
Chromeの「DevTools - Console」を確認すると、WASMのエラーが多数出力された状態で止まっていることが確認できます。
2.2. 複数のルート設定
1つのコンポーネント(ページ)には、複数のルーティング定義を行うことができます。
つまり @Page を複数設定可能という事です。
先程の「UserList.razor」に対して、2つのURLルーティング設定を行った例が以下です。
# Pages/UserList.razor # @page "/users/list" @page "/employees/list" ユーザ一覧画面です!!!
3. NavLinkコンポーネント
ルーティング設定された各コンポーネント(ページ)の表示切替は <a href="/xxx"> タグで可能です。
<a>タグをラップしたコンポーネントとして「NavLinkコンポーネント(Microsoft.AspNetCore.Components.Routing名前空間)」が用意されています。
サンプルプロジェクトでは、左サイドのメニューを「Shared/NavMenu.razor」で実装しており、ここでNavLinkコンポーネントを利用しています。
# Shared/NavMenu.razor # <div class="top-row pl-4 navbar navbar-dark"> <a class="navbar-brand" href="">route_blazor</a> <button class="navbar-toggler" onclick="@ToggleNavMenu"> <span class="navbar-toggler-icon"></span> </button> </div> <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> <li> </li> </ul> </div> @code { bool collapseNavMenu = true; string NavMenuCssClass => collapseNavMenu ? "collapse" : null; void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } }
3.1. href属性
<a>タグと同様に「href属性」でURLを設定します。この「URL」と「.razprで定義された@Page値」によってルーティング先コンポーネントが決定されます。
3.2. 「active」CSSクラス
NavLinkコンポーネントの特徴(機能の1つ)として、「href属性値」と「現在のURL」がマッチした場合、「active」CSSクラスが付与される というものがあります。
以下は「現在のURLが /counter」なので「Counterメニューにactive cssクラス」が付いています。
加えて、NavLinkは<a>タグとしてレンダリングされていることが確認できます。
Match属性
NavLinkコンポーネントには「Match属性(Microsoft.AspNetCore.Components.Routing名前空間のNavLinkMatch列挙型)」というものがあります。
href値とURLのマッチングルールを設定します。
以下の2つの列挙値があります。
- NavLinkMatch.All
URLが完全一致した場合に active CSSクラスが適用されます。
※ href=/usersに対しては、URL=/users はマッチするが、URL=/users/list はマッチしない - NavLinkMatch.Prefix
URLのプレフィクスが一致した場合に active CSSクラスが適用されます。
※ href=/usersに対しては、URL=/users も URL=/users/list もマッチとして扱われる
4. ルートパラメータ
ルーティングのURLにパラメータを含めることができます。
それらのパラメータ値は、ルーティング先のコンポーネントで受け取ることができます。
Pages/Index.razorコンポーネントでルート パラメータを受け取るようにしたいと思います。
4.1. @page ディレクティブにパラメータ定義を追加
ルートパラメータは @Page ディレクティブで設定を行います。
Index.razorは「/」(ルート)にルーティングされています。
「/【Message】」といった形式でパラメータを受け取るように設定します。
# Pages/Index.razor # @page "/" @page "/{Message}" <h1>Hello, world!</h1> your message is '@Message' <br /> @code { private string _Message = "None"; [Parameter] protected string Message { get { return this._Message; } set { this._Message = System.Net.WebUtility.UrlDecode(value); } } }
上記のように、ルートパラメータ定義は「{【パラメータ名】} 」の形式をとります。
Messageは、コードブロックで[Parameter]属性を付与したプロパティとして定義します。
ここでは「%20(スペース)」などのURLエンコードされた文字列パラメータを、URLデコードしてMessageプロパティに保持するために、getter / setterを定義し setterではUrlDecode()処理を行っています。
dotnet run コマンドで実行し、以下のURLをブラウザでアクセスします。
http://localhost:5000/Blazor%20is%20new%20SPA%20framework
URLパラメータを受け取り、画面に表示することが出来ました。
4.2. ルートパラメータの型指定
ルートパラメータは厳密な型を指定することができます。
Pages/Counter.razorコンポーネントに、CurrentCountの初期値をint型ルートパラメータとして受け取るような実装を追加したいと思います。
以下のように「{【パラメータ名】:【型名】}」の形式でルーティング設定を記述します。
コードブロック側も、[Parameter]属性を適用するために、CurrentCountを 変数 から setterを持つプロパティ に修正しています。
# Pages/Counter.razor # @page "/counter" @page "/counter/{currentCount:int}" <h1>Counter</h1> <p>Current count: @CurrentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { [Parameter] public int CurrentCount {get; set; } = 0; void IncrementCount() { this.CurrentCount++; } }
「http://localhost:5000/counter/100」にアクセスした実行画面は以下です。
5. まとめ
ルーティングも従来のASP.NET Core等の仕組みと基本思想は同じくしているので、スムーズに理解することができたました。
次の投稿では、DI(Dependency Injection)を見ていこうと思います。