「Blazor - ルーティング」の版間の差分
(ページの作成:「== 概要 == Blazorでは、URLパターンとコンポーネントを紐付けることでルーティングを実現する。<br> <br> Blazorのルーティングは、<code>@page</code>ディレクティブを使用して設定する。<br> このように定義することにより、/counterというURLにアクセスした時にこのコンポーネントが表示される。<br> <syntaxhighlight lang="c#"> // Pages/Counter.razorファイル @page "/count…」) |
|||
(同じ利用者による、間の7版が非表示) | |||
4行目: | 4行目: | ||
Blazorのルーティングは、<code>@page</code>ディレクティブを使用して設定する。<br> | Blazorのルーティングは、<code>@page</code>ディレクティブを使用して設定する。<br> | ||
このように定義することにより、/counterというURLにアクセスした時にこのコンポーネントが表示される。<br> | このように定義することにより、/counterというURLにアクセスした時にこのコンポーネントが表示される。<br> | ||
<br> | |||
<u>※注意</u><br> | |||
<u>ファイル名は大文字で始まる必要があり、razorファイルの拡張子は.razorの必要がある。</u><br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
// Pages/Counter.razorファイル | // Pages/Counter.razorファイル | ||
19行目: | 23行目: | ||
public int currentCount { get; set; } | public int currentCount { get; set; } | ||
} | } | ||
</syntaxhighlight> | |||
<br><br> | |||
== ルーティングファイル == | |||
Blazorでは、Pagesディレクトリに配置されたrazorファイルがルーティングの対象となる。<br> | |||
<br> | |||
以下に示す<u>/counter</u>の場合では、デフォルトでは以下に示すファイルが参照される。<br> | |||
* /<プロジェクトのトップディレクトリ>/Pages/Counter.razorファイル | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
@page "/counter" | |||
<h1>Counter</h1> | |||
</syntaxhighlight> | |||
<br> | |||
ただし、これは規約ベースのルーティングであり、<code>@page</code>ディレクティブで明示的に別のルートを指定することも可能である。<br> | |||
<br> | |||
以下に示すように設定することにより、異なるファイル配置でも/counterにルーティングすることができる。<br> | |||
<syntaxhighlight lang="c#"> | |||
// 例 : /Pages/MyCounter/Index.razorファイル | |||
@page "/counter" | |||
</syntaxhighlight> | |||
<br> | |||
Pagesディレクトリ配下のファイルへのルーティング<br> | |||
* /counter --> /Pages/Counter.razor | |||
<br> | |||
また、ネストされたディレクトリの場合は以下のようになる。<br> | |||
* /mypage --> /Pages/MyPage.razor | |||
* /admin/dashboard --> /Pages/Admin/Dashboard.razor | |||
* /settings/profile --> /Pages/Settings/Profile.razor | |||
<br> | |||
特殊なケースとして、Indexページは以下のようになる。<br> | |||
* / --> /Pages/Index.razor | |||
* /admin --> /Pages/Admin/Index.razor | |||
<br> | |||
なお、<code>@page</code>ディレクティブで明示的にルートを指定した場合は、この規約が上書きされる。<br> | |||
<br><br> | |||
== レンダーフラグメント == | |||
レンダーフラグメントは、UIの一部を表すデリゲート型であり、コンポーネント間でコンテンツを受け渡すための仕組みである。<br> | |||
これは、JSXのchildren propやReactのprops.childrenに相当する概念である。<br> | |||
<br> | |||
また、<code>@Body</code>は、このレンダーフラグメントの特別な実装である。<br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
[Parameter] | |||
public RenderFragment CustomContent { get; set; } | |||
// 使用例 | |||
<div> | |||
@CustomContent | |||
</div> | |||
</syntaxhighlight> | |||
<br> | |||
レンダーフラグメントは、Blazorにおける重要な型であり、以下に示す用途で使用される。<br> | |||
<syntaxhighlight lang="c#"> | |||
// LayoutComponentBaseクラスでの定義 | |||
public RenderFragment Body { get; set; } | |||
// カスタムコンポーネントでの使用例 | |||
[Parameter] | |||
public RenderFragment ChildContent { get; set; } | |||
// テンプレート用途での使用例 | |||
[Parameter] | |||
public RenderFragment<TItem> ItemTemplate { get; set; } | |||
</syntaxhighlight> | |||
<br> | |||
==== @Body ==== | |||
<code>@Body</code>は、レイアウトコンポーネント内でページコンテンツを表示する場所を指定するプレースホルダーである。<br> | |||
<br> | |||
@Bodyは、レイアウトを使用する各ページのコンテンツが配置される場所を示す。<br> | |||
<br> | |||
例えば、以下に示すようなページコンポーネントが存在する場合<br> | |||
<syntaxhighlight lang="c#"> | |||
// MainLayout.razorファイル | |||
<div class="page"> | |||
<div class="sidebar"> | |||
<NavMenu /> | |||
</div> | |||
<main> | |||
<div class="content"> | |||
@Body | |||
</div> | |||
</main> | |||
</div> | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
// Pages/Counter.razorファイル | |||
@page "/counter" | |||
<h1>Counter</h1> | |||
<p>Current count: @currentCount</p> | |||
<button @onclick="IncrementCount">Click me</button> | |||
@code { | |||
private int currentCount = 0; | |||
private void IncrementCount() => currentCount++; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
上記のCounter.razorファイルの内容が、MainLayout.razorファイルの@Body部分に展開されて表示される。<br> | |||
<syntaxhighlight lang="c#"> | |||
// MainLayout.razorファイル | |||
<div class="page"> | |||
<div class="sidebar"> | |||
<NavMenu /> | |||
</div> | |||
<main> | |||
<div class="content"> | |||
// ここに、Counter.razorファイルの内容が展開される | |||
<h1>Counter</h1> | |||
<p>Current count: 0</p> | |||
<button>Click me</button> | |||
</div> | |||
</main> | |||
</div> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
25行目: | 151行目: | ||
ルートパラメータは波括弧で囲んで指定して、対応するプロパティに<code>[Parameter]</code>属性を付与する。<br> | ルートパラメータは波括弧で囲んで指定して、対応するプロパティに<code>[Parameter]</code>属性を付与する。<br> | ||
<br> | <br> | ||
Blazorのパラメータ受け渡しには、3つの方法がある。<br> | |||
<br> | |||
==== ルートパラメータ ==== | |||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
@page "/user/{Id}" | @page "/user/{Id:int}" | ||
@code { | @code { | ||
[Parameter] | [Parameter] | ||
public | public int Id { get; set; } | ||
// 整数以外の値が渡された場合は404エラー | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== 複数パラメータ ==== | |||
<syntaxhighlight lang="c#"> | |||
@page "/orders/{CustomerId:int}/{OrderId:int}" | |||
@code { | |||
[Parameter] | |||
public int CustomerId { get; set; } | |||
[Parameter] | |||
public int OrderId { get; set; } | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== クエリパラメータ ==== | |||
<syntaxhighlight lang="c#"> | |||
// URL: /search?query=blazor&page=1 | |||
@page "/search" | |||
@inject NavigationManager Navigation | |||
@code { | |||
private string SearchQuery; | |||
private int Page; | |||
protected override void OnInitialized() | |||
{ | |||
var uri = Navigation.ToAbsoluteUri(Navigation.Uri); | |||
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("query", out var query)) | |||
{ | |||
SearchQuery = query; | |||
} | |||
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("page", out var page)) | |||
{ | |||
Page = Convert.ToInt32(page); | |||
} | |||
} | |||
} | } | ||
</syntaxhighlight> | |||
<br> | |||
==== 制約パターンの使用 ==== | |||
制約パターンも使用できる。<br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
@page "/date/{date:datetime}" | |||
@page "/products/{id:guid}" | |||
@page "/blog/{*slug}" // catch-allパラメータ | |||
</syntaxhighlight> | |||
<br> | |||
==== カスケード ==== | |||
これらのパラメータは、コンポーネント間でのカスケード時にも使用可能である。<br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
<ChildComponent CustomerId="@CustomerId" /> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
40行目: | 228行目: | ||
==== NavigationManagerを使用したプログラムによる遷移 ==== | ==== NavigationManagerを使用したプログラムによる遷移 ==== | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
@inject NavigationManager | @inject NavigationManager Navigation | ||
@code { | @code { | ||
private void | private void Navigate() | ||
{ | { | ||
// 基本的な遷移 | |||
Navigation.NavigateTo("counter"); | |||
// 強制的なページリロード | |||
Navigation.NavigateTo("counter", forceLoad: true); | |||
// 相対パスでの遷移 | |||
Navigation.NavigateTo("counter", forceLoad: false); | |||
// クエリパラメータを含む遷移 | |||
Navigation.NavigateTo($"search?query={Uri.EscapeDataString(searchTerm)}"); | |||
// 戻る | |||
Navigation.NavigateTo(Navigation.Uri); | |||
} | |||
// URLの変更を検知 | |||
protected override void OnInitialized() | |||
{ | |||
Navigation.LocationChanged += LocationChanged; | |||
} | |||
private void LocationChanged(object sender, LocationChangedEventArgs e) | |||
{ | |||
// URLが変更された時の処理 | |||
} | } | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
==== | ==== NavLinkコンポーネントを使用したリンクによる遷移 ==== | ||
NavLinkコンポーネントを使用して遷移する。<br> | NavLinkコンポーネントを使用して遷移する。<br> | ||
<br> | <br> | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
<NavLink href="counter"> | // 基本的な使用方法 | ||
<NavLink href="counter">Counter</NavLink> | |||
</NavLink> | |||
// アクティブ時のスタイル指定 | |||
<NavLink href="counter" ActiveClass="active">Counter</NavLink> | |||
// Match属性による一致条件の指定 | |||
<NavLink href="users" Match="NavLinkMatch.Prefix">Users</NavLink> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
== オプションパラメータ / クエリパラメータ == | == オプションパラメータ / クエリパラメータ == | ||
パラメータ処理により、以下に示すようなURLに対応することができる。<br> | |||
* /products | |||
*: カテゴリなし | |||
* /products/1 | |||
*: カテゴリID指定 | |||
* /products?search=keyboard | |||
*: 検索キーワード指定 | |||
* /products/1?search=keyboard&page=2 | |||
*: 全てのパラメータ指定 | |||
<br> | |||
==== オプションパラメータ ==== | ==== オプションパラメータ ==== | ||
波括弧内に<code>?</code>を付加することにより、実現できる。<br> | 波括弧内に<code>?</code>を付加することにより、実現できる。<br> | ||
<br> | <br> | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
@page "/user/{id?}" | @page "/user/{id?}" // idパラメータはオプション (省略可能) | ||
@page "/blog/{year:int?}/{month:int?}" // 年月も省略可能 | |||
@code { | @code { | ||
[Parameter] | [Parameter] | ||
public string Id { get; set; } | public string Id { get; set; } | ||
[Parameter] | |||
public int? Year { get; set; } | |||
[Parameter] | |||
public int? Month { get; set; } | |||
protected override void OnInitialized() | |||
{ | |||
// パラメータが省略された場合のデフォルト値設定 | |||
Id ??= "default"; // Idが未指定の場合は、"default"を設定 | |||
Year ??= DateTime.Now.Year; // 年が未指定の場合は現在の年を設定 | |||
Month ??= DateTime.Now.Month; // 月が未指定の場合は現在の月を設定 | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== クエリパラメータ ==== | |||
<syntaxhighlight lang="c#"> | |||
@page "/search" | |||
@inject NavigationManager NavigationManager // URLの操作に必要なサービスの注入 | |||
@code { | |||
// クエリパラメータを格納するディクショナリ | |||
private Dictionary<string, string> QueryParameters; | |||
protected override void OnInitialized() | protected override void OnInitialized() | ||
{ | { | ||
// 現在のURLからクエリパラメータを解析 | |||
var uri = new Uri(NavigationManager.Uri); | |||
QueryParameters = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query) | |||
.ToDictionary(p => p.Key, p => p.Value.ToString()); | |||
} | |||
// 検索条件を更新してページ遷移を行うメソッド | |||
private void UpdateSearch(string query, int page) | |||
{ | |||
// クエリパラメータの設定 | |||
var parameters = new Dictionary<string, string> | |||
{ | { | ||
{ "q", query }, // 検索キーワード | |||
} | { "page", page.ToString() } // ページ番号 | ||
}; | |||
// クエリパラメータを含むURLを生成して遷移 | |||
var url = QueryHelpers.AddQueryString("/search", parameters); | |||
NavigationManager.NavigateTo(url); | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== 2つのパラメータの組み合わせ ==== | |||
<syntaxhighlight lang="c#"> | |||
@page "/products/{categoryId?}" // カテゴリIDはオプション | |||
@inject NavigationManager NavigationManager | |||
@code { | |||
[Parameter] | |||
public string CategoryId { get; set; } // URLパラメータ | |||
// クエリパラメータから検索キーワードを取得 | |||
private string SearchTerm => GetQueryParameter("search"); | |||
// クエリパラメータからページ番号を取得 (デフォルト : 1) | |||
private int Page => int.Parse(GetQueryParameter("page") ?? "1"); | |||
// クエリパラメータを取得するヘルパーメソッド | |||
private string GetQueryParameter(string key) | |||
{ | |||
var uri = new Uri(NavigationManager.Uri); | |||
var parameters = QueryHelpers.ParseQuery(uri.Query); | |||
return parameters.TryGetValue(key, out var value) ? value.ToString() : null; | |||
} | } | ||
} | } | ||
83行目: | 377行目: | ||
== レイアウトの適用 == | == レイアウトの適用 == | ||
Blazorのルーティングでは、レイアウト機能も重要である。<br> | |||
レイアウト機能により、共通のUIパーツを効率的に管理でき、ページごとに異なるレイアウトを適用することができる。<br> | |||
<br> | |||
_Imports.razorファイルにおいて、デフォルトレイアウトを指定して、必要に応じて個別のコンポーネントで<code>@layout</code>指示子を使用して上書きすることができる。<br> | _Imports.razorファイルにおいて、デフォルトレイアウトを指定して、必要に応じて個別のコンポーネントで<code>@layout</code>指示子を使用して上書きすることができる。<br> | ||
<br> | |||
==== 基本的なレイアウトファイルの構造 ==== | |||
<syntaxhighlight lang="c#"> | |||
// MainLayout.razorファイル | |||
@inherits LayoutComponentBase | |||
<div class="page"> | |||
<div class="sidebar"> | |||
<NavMenu /> | |||
</div> | |||
<main> | |||
<div class="content"> | |||
@Body | |||
</div> | |||
</main> | |||
</div> | |||
</syntaxhighlight> | |||
<br> | |||
==== _Imports.razorでのデフォルトレイアウト指定 ==== | |||
<syntaxhighlight lang="c#"> | |||
@using System.Net.Http | |||
@using Microsoft.AspNetCore.Components.Forms | |||
@using Microsoft.AspNetCore.Components.Routing | |||
@using Microsoft.AspNetCore.Components.Web | |||
@layout MainLayout // デフォルトレイアウトの指定 | |||
</syntaxhighlight> | |||
<br> | |||
==== 個別コンポーネントでのレイアウト上書き ==== | |||
<syntaxhighlight lang="c#"> | |||
// Pages/AdminPage.razorファイル | |||
@layout AdminLayout // このコンポーネントのみ異なるレイアウトを使用 | |||
@page "/admin" | |||
<h1>管理画面</h1> | |||
</syntaxhighlight> | |||
<br> | |||
==== 入れ子レイアウトの例 ==== | |||
<syntaxhighlight lang="c#"> | |||
// AdminLayout.razorファイル | |||
@inherits LayoutComponentBase | |||
<div class="admin-layout"> | |||
<nav class="admin-nav"> | |||
<AdminMenu /> | |||
</nav> | |||
<div class="admin-content"> | |||
@Body | |||
</div> | |||
</div> | |||
</syntaxhighlight> | |||
<br><br> | <br><br> | ||
90行目: | 441行目: | ||
404エラー等のルーティングエラーは、App.razorで設定したNotFoundテンプレートによって処理される。<br> | 404エラー等のルーティングエラーは、App.razorで設定したNotFoundテンプレートによって処理される。<br> | ||
これにより、ユーザフレンドリーなエラー画面を表示することが可能である。<br> | これにより、ユーザフレンドリーなエラー画面を表示することが可能である。<br> | ||
<br> | |||
例えば、NotFoundセクションをカスタマイズすることにより、より使用しやすいエラー画面を提供することができる。<br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
// App.razorファイル | |||
<Router AppAssembly="@typeof(App).Assembly"> | |||
<Found Context="routeData"> | |||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> | |||
</Found> | |||
<NotFound> | |||
<PageTitle>Not found</PageTitle> | |||
<LayoutView Layout="@typeof(MainLayout)"> | |||
<div class="alert alert-danger"> | |||
<h3>ページが見つかりません</h3> | |||
<p>申し訳ありません。要求されたページは存在しません。</p> | |||
<a href="/" class="btn btn-primary">ホームに戻る</a> | |||
</div> | |||
</LayoutView> | |||
</NotFound> | |||
</Router> | |||
</syntaxhighlight> | |||
<br> | |||
==== カスタム404ページ ==== | |||
<syntaxhighlight lang="c#"> | |||
// Pages/CustomNotFound.razorファイル | |||
@inject NavigationManager NavigationManager | |||
<div class="error-container"> | |||
<h2>404 - ページが見つかりません</h2> | |||
<p>リクエストされたURL: @NavigationManager.Uri</p> | |||
<div class="error-actions"> | |||
<button @onclick="GoBack">前のページに戻る</button> | |||
<a href="/" class="btn">ホームページへ</a> | |||
</div> | |||
</div> | |||
@code { | |||
private void GoBack() | |||
{ | |||
NavigationManager.NavigateTo(NavigationManager.Uri); | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
上記のファイルをApp.razorファイルで使用する。<br> | |||
<syntaxhighlight lang="c#"> | |||
// App.razorファイル | |||
<NotFound> | |||
<LayoutView Layout="@typeof(MainLayout)"> | |||
<CustomNotFound /> | |||
</LayoutView> | |||
</NotFound> | |||
</syntaxhighlight> | |||
<br><br> | <br><br> | ||
== 認証 / 認可 == | == 認証 / 認可 == | ||
<code>[Authorize]</code>属性を使用することにより、特定のルートへのアクセスを制限することができる。<br> | <code>[Authorize]</code>属性を使用することにより、特定のルートへのアクセスを制限することができる。<br> | ||
<br> | |||
==== 基本的な認証要求 ==== | |||
<syntaxhighlight lang="c#"> | |||
@page "/secured-page" | |||
[Authorize] | |||
public class SecuredPageBase : ComponentBase | |||
{ | |||
// このページは認証済みユーザーのみアクセス可能 | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== 特定のロールに基づく認証 ==== | |||
<syntaxhighlight lang="c#"> | |||
@page "/admin-page" | |||
[Authorize(Roles = "Admin")] | |||
public class AdminPageBase : ComponentBase | |||
{ | |||
// 管理者ロールを持つユーザーのみアクセス可能 | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== 複数のロールの指定 ==== | |||
<syntaxhighlight lang="c#"> | |||
[Authorize(Roles = "Admin,Manager")] | |||
// ポリシーベースの認証 | |||
[Authorize(Policy = "RequireAdminRole")] | |||
// カスタム認証要件 | |||
[Authorize(Policy = "MinimumAgeRequirement")] | |||
</syntaxhighlight> | |||
<br> | |||
==== Program.csでの認証サービスの設定 ==== | |||
<syntaxhighlight lang="c#"> | |||
builder.Services.AddAuthentication().AddCookie(); | |||
builder.Services.AddAuthorization(options => | |||
{ | |||
options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin")); | |||
options.AddPolicy("MinimumAgeRequirement", policy => policy.Requirements.Add(new MinimumAgeRequirement(18))); | |||
}); | |||
</syntaxhighlight> | |||
<br> | |||
==== 認証状態の確認と処理 ==== | |||
<syntaxhighlight lang="c#"> | |||
@inject AuthenticationStateProvider AuthenticationStateProvider | |||
@code { | |||
private async Task<bool> IsUserAuthenticated() | |||
{ | |||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); | |||
return authState.User.Identity.IsAuthenticated; | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br><br> | <br><br> | ||
2025年1月26日 (日) 23:41時点における最新版
概要
Blazorでは、URLパターンとコンポーネントを紐付けることでルーティングを実現する。
Blazorのルーティングは、@page
ディレクティブを使用して設定する。
このように定義することにより、/counterというURLにアクセスした時にこのコンポーネントが表示される。
※注意
ファイル名は大文字で始まる必要があり、razorファイルの拡張子は.razorの必要がある。
// Pages/Counter.razorファイル
@page "/counter"
@page "/counter/{currentCount:int}"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
@code {
// オプションのルートパラメータ
[Parameter]
public int currentCount { get; set; }
}
ルーティングファイル
Blazorでは、Pagesディレクトリに配置されたrazorファイルがルーティングの対象となる。
以下に示す/counterの場合では、デフォルトでは以下に示すファイルが参照される。
- /<プロジェクトのトップディレクトリ>/Pages/Counter.razorファイル
@page "/counter"
<h1>Counter</h1>
ただし、これは規約ベースのルーティングであり、@page
ディレクティブで明示的に別のルートを指定することも可能である。
以下に示すように設定することにより、異なるファイル配置でも/counterにルーティングすることができる。
// 例 : /Pages/MyCounter/Index.razorファイル
@page "/counter"
Pagesディレクトリ配下のファイルへのルーティング
- /counter --> /Pages/Counter.razor
また、ネストされたディレクトリの場合は以下のようになる。
- /mypage --> /Pages/MyPage.razor
- /admin/dashboard --> /Pages/Admin/Dashboard.razor
- /settings/profile --> /Pages/Settings/Profile.razor
特殊なケースとして、Indexページは以下のようになる。
- / --> /Pages/Index.razor
- /admin --> /Pages/Admin/Index.razor
なお、@page
ディレクティブで明示的にルートを指定した場合は、この規約が上書きされる。
レンダーフラグメント
レンダーフラグメントは、UIの一部を表すデリゲート型であり、コンポーネント間でコンテンツを受け渡すための仕組みである。
これは、JSXのchildren propやReactのprops.childrenに相当する概念である。
また、@Body
は、このレンダーフラグメントの特別な実装である。
[Parameter]
public RenderFragment CustomContent { get; set; }
// 使用例
<div>
@CustomContent
</div>
レンダーフラグメントは、Blazorにおける重要な型であり、以下に示す用途で使用される。
// LayoutComponentBaseクラスでの定義
public RenderFragment Body { get; set; }
// カスタムコンポーネントでの使用例
[Parameter]
public RenderFragment ChildContent { get; set; }
// テンプレート用途での使用例
[Parameter]
public RenderFragment<TItem> ItemTemplate { get; set; }
@Body
@Body
は、レイアウトコンポーネント内でページコンテンツを表示する場所を指定するプレースホルダーである。
@Bodyは、レイアウトを使用する各ページのコンテンツが配置される場所を示す。
例えば、以下に示すようなページコンポーネントが存在する場合
// MainLayout.razorファイル
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="content">
@Body
</div>
</main>
</div>
// Pages/Counter.razorファイル
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount() => currentCount++;
}
上記のCounter.razorファイルの内容が、MainLayout.razorファイルの@Body部分に展開されて表示される。
// MainLayout.razorファイル
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="content">
// ここに、Counter.razorファイルの内容が展開される
<h1>Counter</h1>
<p>Current count: 0</p>
<button>Click me</button>
</div>
</main>
</div>
パラメータの受け渡し
ルートパラメータは波括弧で囲んで指定して、対応するプロパティに[Parameter]
属性を付与する。
Blazorのパラメータ受け渡しには、3つの方法がある。
ルートパラメータ
@page "/user/{Id:int}"
@code {
[Parameter]
public int Id { get; set; }
// 整数以外の値が渡された場合は404エラー
}
複数パラメータ
@page "/orders/{CustomerId:int}/{OrderId:int}"
@code {
[Parameter]
public int CustomerId { get; set; }
[Parameter]
public int OrderId { get; set; }
}
クエリパラメータ
// URL: /search?query=blazor&page=1
@page "/search"
@inject NavigationManager Navigation
@code {
private string SearchQuery;
private int Page;
protected override void OnInitialized()
{
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("query", out var query))
{
SearchQuery = query;
}
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("page", out var page))
{
Page = Convert.ToInt32(page);
}
}
}
制約パターンの使用
制約パターンも使用できる。
@page "/date/{date:datetime}"
@page "/products/{id:guid}"
@page "/blog/{*slug}" // catch-allパラメータ
カスケード
これらのパラメータは、コンポーネント間でのカスケード時にも使用可能である。
<ChildComponent CustomerId="@CustomerId" />
ナビゲーション
プログラムによる画面遷移とリンクによる遷移の2つの方法がある。
@inject NavigationManager Navigation
@code {
private void Navigate()
{
// 基本的な遷移
Navigation.NavigateTo("counter");
// 強制的なページリロード
Navigation.NavigateTo("counter", forceLoad: true);
// 相対パスでの遷移
Navigation.NavigateTo("counter", forceLoad: false);
// クエリパラメータを含む遷移
Navigation.NavigateTo($"search?query={Uri.EscapeDataString(searchTerm)}");
// 戻る
Navigation.NavigateTo(Navigation.Uri);
}
// URLの変更を検知
protected override void OnInitialized()
{
Navigation.LocationChanged += LocationChanged;
}
private void LocationChanged(object sender, LocationChangedEventArgs e)
{
// URLが変更された時の処理
}
}
NavLinkコンポーネントを使用して遷移する。
// 基本的な使用方法
<NavLink href="counter">Counter</NavLink>
// アクティブ時のスタイル指定
<NavLink href="counter" ActiveClass="active">Counter</NavLink>
// Match属性による一致条件の指定
<NavLink href="users" Match="NavLinkMatch.Prefix">Users</NavLink>
オプションパラメータ / クエリパラメータ
パラメータ処理により、以下に示すようなURLに対応することができる。
- /products
- カテゴリなし
- /products/1
- カテゴリID指定
- /products?search=keyboard
- 検索キーワード指定
- /products/1?search=keyboard&page=2
- 全てのパラメータ指定
オプションパラメータ
波括弧内に?
を付加することにより、実現できる。
@page "/user/{id?}" // idパラメータはオプション (省略可能)
@page "/blog/{year:int?}/{month:int?}" // 年月も省略可能
@code {
[Parameter]
public string Id { get; set; }
[Parameter]
public int? Year { get; set; }
[Parameter]
public int? Month { get; set; }
protected override void OnInitialized()
{
// パラメータが省略された場合のデフォルト値設定
Id ??= "default"; // Idが未指定の場合は、"default"を設定
Year ??= DateTime.Now.Year; // 年が未指定の場合は現在の年を設定
Month ??= DateTime.Now.Month; // 月が未指定の場合は現在の月を設定
}
}
クエリパラメータ
@page "/search"
@inject NavigationManager NavigationManager // URLの操作に必要なサービスの注入
@code {
// クエリパラメータを格納するディクショナリ
private Dictionary<string, string> QueryParameters;
protected override void OnInitialized()
{
// 現在のURLからクエリパラメータを解析
var uri = new Uri(NavigationManager.Uri);
QueryParameters = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query)
.ToDictionary(p => p.Key, p => p.Value.ToString());
}
// 検索条件を更新してページ遷移を行うメソッド
private void UpdateSearch(string query, int page)
{
// クエリパラメータの設定
var parameters = new Dictionary<string, string>
{
{ "q", query }, // 検索キーワード
{ "page", page.ToString() } // ページ番号
};
// クエリパラメータを含むURLを生成して遷移
var url = QueryHelpers.AddQueryString("/search", parameters);
NavigationManager.NavigateTo(url);
}
}
2つのパラメータの組み合わせ
@page "/products/{categoryId?}" // カテゴリIDはオプション
@inject NavigationManager NavigationManager
@code {
[Parameter]
public string CategoryId { get; set; } // URLパラメータ
// クエリパラメータから検索キーワードを取得
private string SearchTerm => GetQueryParameter("search");
// クエリパラメータからページ番号を取得 (デフォルト : 1)
private int Page => int.Parse(GetQueryParameter("page") ?? "1");
// クエリパラメータを取得するヘルパーメソッド
private string GetQueryParameter(string key)
{
var uri = new Uri(NavigationManager.Uri);
var parameters = QueryHelpers.ParseQuery(uri.Query);
return parameters.TryGetValue(key, out var value) ? value.ToString() : null;
}
}
レイアウトの適用
Blazorのルーティングでは、レイアウト機能も重要である。
レイアウト機能により、共通のUIパーツを効率的に管理でき、ページごとに異なるレイアウトを適用することができる。
_Imports.razorファイルにおいて、デフォルトレイアウトを指定して、必要に応じて個別のコンポーネントで@layout
指示子を使用して上書きすることができる。
基本的なレイアウトファイルの構造
// MainLayout.razorファイル
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="content">
@Body
</div>
</main>
</div>
_Imports.razorでのデフォルトレイアウト指定
@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@layout MainLayout // デフォルトレイアウトの指定
個別コンポーネントでのレイアウト上書き
// Pages/AdminPage.razorファイル
@layout AdminLayout // このコンポーネントのみ異なるレイアウトを使用
@page "/admin"
<h1>管理画面</h1>
入れ子レイアウトの例
// AdminLayout.razorファイル
@inherits LayoutComponentBase
<div class="admin-layout">
<nav class="admin-nav">
<AdminMenu />
</nav>
<div class="admin-content">
@Body
</div>
</div>
エラーハンドリング
404エラー等のルーティングエラーは、App.razorで設定したNotFoundテンプレートによって処理される。
これにより、ユーザフレンドリーなエラー画面を表示することが可能である。
例えば、NotFoundセクションをカスタマイズすることにより、より使用しやすいエラー画面を提供することができる。
// App.razorファイル
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<div class="alert alert-danger">
<h3>ページが見つかりません</h3>
<p>申し訳ありません。要求されたページは存在しません。</p>
<a href="/" class="btn btn-primary">ホームに戻る</a>
</div>
</LayoutView>
</NotFound>
</Router>
カスタム404ページ
// Pages/CustomNotFound.razorファイル
@inject NavigationManager NavigationManager
<div class="error-container">
<h2>404 - ページが見つかりません</h2>
<p>リクエストされたURL: @NavigationManager.Uri</p>
<div class="error-actions">
<button @onclick="GoBack">前のページに戻る</button>
<a href="/" class="btn">ホームページへ</a>
</div>
</div>
@code {
private void GoBack()
{
NavigationManager.NavigateTo(NavigationManager.Uri);
}
}
上記のファイルをApp.razorファイルで使用する。
// App.razorファイル
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<CustomNotFound />
</LayoutView>
</NotFound>
認証 / 認可
[Authorize]
属性を使用することにより、特定のルートへのアクセスを制限することができる。
基本的な認証要求
@page "/secured-page"
[Authorize]
public class SecuredPageBase : ComponentBase
{
// このページは認証済みユーザーのみアクセス可能
}
特定のロールに基づく認証
@page "/admin-page"
[Authorize(Roles = "Admin")]
public class AdminPageBase : ComponentBase
{
// 管理者ロールを持つユーザーのみアクセス可能
}
複数のロールの指定
[Authorize(Roles = "Admin,Manager")]
// ポリシーベースの認証
[Authorize(Policy = "RequireAdminRole")]
// カスタム認証要件
[Authorize(Policy = "MinimumAgeRequirement")]
Program.csでの認証サービスの設定
builder.Services.AddAuthentication().AddCookie();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin"));
options.AddPolicy("MinimumAgeRequirement", policy => policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
認証状態の確認と処理
@inject AuthenticationStateProvider AuthenticationStateProvider
@code {
private async Task<bool> IsUserAuthenticated()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
return authState.User.Identity.IsAuthenticated;
}
}