ASP.NET Core Blazor 依存関係の挿入
注意
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
作成者: Rainer Stropek、Mike Rousos
この記事では、Blazor アプリでサービスをコンポーネントに挿入する方法について説明します。
依存関係の挿入 (DI) は、中央の場所で構成されたサービスにアクセスするための手法です。
- フレームワークによって登録されたサービスは、Razor コンポーネントに直接挿入できます。
- Blazor アプリによって、カスタム サービスの定義と登録が行われ、DI を通じてアプリ全体でそれらが使用できるようになります。
Note
このトピックを読む前に、ASP.NET Core での依存関係の挿入に関する記事をお読みになることをお勧めします。
既定のサービス
Blazor アプリでよく使用されるサービスを次の表に示します。
サービス | 有効期間 | 説明 |
---|---|---|
HttpClient | スコープ | URI によって識別されるリソースに HTTP 要求を送信し、そのリソースから HTTP 応答を受信するためのメソッドが提供されます。 クライアント側では、HttpClient のインスタンスは サーバー側では、既定では HttpClient はサービスとして構成されていません。 サーバー側のコードで、HttpClient を指定します。 詳しくは、「ASP.NET Core Blazor アプリから Web API を呼び出す」をご覧ください。 HttpClient は、シングルトンではなく、スコープ サービスとして登録されます。 詳細については、「サービスの有効期間」セクションを参照してください。 |
IJSRuntime | クライアント側: シングルトン サーバー側: スコープ済み Blazor フレームワークによって、アプリのサービス コンテナーに IJSRuntime が登録されます。 |
JavaScript の呼び出しがディスパッチされる JavaScript ランタイムのインスタンスを表します。 詳細については、「ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」を参照してください。 シングルトン サービスにサービスを挿入する場合は、次のいずれかの方法を使用します:
|
NavigationManager | クライアント側: シングルトン サーバー側: スコープ済み Blazor フレームワークによって、アプリのサービス コンテナーに NavigationManager が登録されます。 |
URI とナビゲーション状態を操作するためのヘルパーが含まれます。 詳細については、「URI およびナビゲーション状態ヘルパー」を参照してください。 |
Blazor フレームワークによって登録された追加のサービスは、ドキュメントで説明されており、構成やログ記録などの Blazor 機能の説明に使用されています。
カスタム サービス プロバイダーでは、表に示されている既定のサービスは自動的に提供されません。 カスタム サービス プロバイダーを使用し、表に示されているいずれかのサービスが必要な場合は、必要なサービスを新しいサービス プロバイダーに追加します。
クライアント側サービスの追加
Program
ファイルで、アプリのサービス コレクション用のサービスを構成します。 次の例では、ExampleDependency
の実装が IExampleDependency
に登録されます。
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<IExampleDependency, ExampleDependency>();
...
await builder.Build().RunAsync();
ホストが構築されると、コンポーネントがレンダリングされる前に、ルート DI スコープからサービスを使用できるようになります。 これは、コンテンツをレンダリングする前に初期化ロジックを実行する場合に役に立ちます。
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<WeatherService>();
...
var host = builder.Build();
var weatherService = host.Services.GetRequiredService<WeatherService>();
await weatherService.InitializeWeatherAsync();
await host.RunAsync();
ホストによって、アプリの中央構成インスタンスが提供されます。 前の例を基にして、天気予報サービスの URL を、既定の構成ソース (appsettings.json
など) から InitializeWeatherAsync
に渡します。
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<WeatherService>();
...
var host = builder.Build();
var weatherService = host.Services.GetRequiredService<WeatherService>();
await weatherService.InitializeWeatherAsync(
host.Configuration["WeatherServiceUrl"]);
await host.RunAsync();
サーバー側サービスの追加
新しいアプリを作成した後、Program
ファイルの一部を調べます。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder
変数は、サービス記述子オブジェクトのリストである IServiceCollection を持つ WebApplicationBuilder を表します。 サービスは、サービス コレクションにサービス記述子を提供することによって追加されます。 次の例では、IDataAccess
インターフェイスとその具象実装 DataAccess
での概念を示します。
builder.Services.AddSingleton<IDataAccess, DataAccess>();
新しいアプリを作成した後、Startup.cs
で Startup.ConfigureServices
メソッドを調べます。
using Microsoft.Extensions.DependencyInjection;
...
public void ConfigureServices(IServiceCollection services)
{
...
}
ConfigureServices メソッドには、サービス記述子オブジェクトのリストである IServiceCollection が渡されます。 サービスは、ConfigureServices
メソッドでサービス コレクションにサービス記述子を提供することによって追加されます。 次の例では、IDataAccess
インターフェイスとその具象実装 DataAccess
での概念を示します。
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDataAccess, DataAccess>();
}
一般的なサービスの登録
1 つ以上の一般的なサービスがクライアント側とサーバー側で必要な場合は、クライアント側のメソッドに共通サービス登録を配置し、メソッドを呼び出して両方のプロジェクトにサービスを登録できます。
まず、一般的なサービス登録を別のメソッドに組み込みます。 たとえば、ConfigureCommonServices
メソッドをクライアント側に作成します:
public static void ConfigureCommonServices(IServiceCollection services)
{
services.Add...;
}
クライアント側 Program
ファイルの場合は、共通サービスを登録するために ConfigureCommonServices
を呼び出します:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
ConfigureCommonServices(builder.Services);
サーバー側 Program
ファイルで、共通サービスを登録する ConfigureCommonServices
呼び出します:
var builder = WebApplication.CreateBuilder(args);
...
Client.Program.ConfigureCommonServices(builder.Services);
この方法の例については、「ASP.NET Core Blazor WebAssembly のセキュリティに関するその他のシナリオ」を参照してください。
プリレンダリング中にクライアント側サービスが失敗する
このセクションは、Blazor Web App の WebAssembly コンポーネントにのみ適用されます。
通常、Blazor Web App はクライアント側の WebAssembly コンポーネントをプリレンダリングします。 .Client
プロジェクトに登録されている必要なサービスのみでアプリが実行されている場合、コンポーネントがプリレンダリング中にその必要なサービスを使用しようとすると、アプリの実行で次のようなランタイム エラーが発生します。
InvalidOperationException: 型 '{ASSEMBLY}}.Client.Pages.{COMPONENT NAME}' の {PROPERTY} の値を指定できません。 型 '{SERVICE}' の登録済みサービスはありません。
この問題を解決するには、次の "いずれか" の方法を使用します。
- メイン プロジェクトにサービスを登録して、コンポーネントのプリレンダリング中に使用できるようにする。
- コンポーネントにプリレンダリングが必要ない場合は、ASP.NET Core Blazor レンダー モードのガイダンスに従ってプリレンダリングを無効にする。 このアプローチを採用する場合、サービスをメイン プロジェクトに登録する必要はありません。
詳細については、「プリレンダリング中にクライアント側サービスによる解決が失敗する」を参照してください。
サービスの有効期間
サービスは、次の表に示す有効期間で構成できます。
有効期間 | 説明 |
---|---|
Scoped | クライアント側には現在、DI スコープの概念はありません。 サーバー側の開発では、HTTP 要求間で
サーバー側アプリでのユーザー状態の保持の詳細については、「ASP.NET Core Blazor 状態管理」を参照してください。 |
Singleton | DI では、サービスの "単一インスタンス" が作成されます。 Singleton サービスを必要とするすべてのコンポーネントは、サービスの同じインスタンスを受け取ります。 |
Transient | コンポーネントは、サービス コンテナーから Transient サービスのインスタンスを取得するたびに、サービスの "新しいインスタンス" を受け取ります。 |
DI システムは、ASP.NET Core の DI システムが基になっています。 詳細については、「ASP.NET Core での依存関係の挿入」を参照してください。
コンポーネント内のサービスを要求する
コンポーネントにサービスを挿入するために、Blazor はコンストラクター挿入とプロパティ挿入をサポートしています。
コンストラクターの挿入
サービスがサービス コレクションに追加されたら、コンストラクター挿入を使って 1 つ以上のサービスをコンポーネントに挿入します。 次の例では、NavigationManager
サービスを挿入します。
ConstructorInjection.razor
:
@page "/constructor-injection"
<button @onclick="HandleClick">
Take me to the Counter component
</button>
ConstructorInjection.razor.cs
:
using Microsoft.AspNetCore.Components;
public partial class ConstructorInjection(NavigationManager navigation)
{
private void HandleClick()
{
navigation.NavigateTo("/counter");
}
}
プロパティの挿入
サービスがサービス コレクションに追加されたら、@inject
Razor ディレクティブ (2 つのパラメーターがあります) を使って、1 つ以上のサービスをコンポーネントに挿入します。
- 型:挿入するサービスの型。
- プロパティ:挿入されたアプリ サービスを受け取るプロパティの名前。 プロパティを手動で作成する必要はありません。 プロパティはコンパイラによって作成されます。
詳細については、「ASP.NET Core でのビューへの依存関係の挿入」を参照してください。
異なるサービスを挿入するには、複数の @inject
ステートメントを使用します。
次の例は、@inject
ディレクティブの使用方法を示しています。 Services.NavigationManager
を実装するサービスを、コンポーネントのプロパティ Navigation
に挿入します。 このコードでは、NavigationManager
抽象化だけを使っていることに注意してください。
PropertyInjection.razor
:
@page "/property-injection"
@inject NavigationManager Navigation
<button @onclick="@(() => Navigation.NavigateTo("/counter"))">
Take me to the Counter component
</button>
内部的には、生成されたプロパティ (Navigation
) によって、[Inject]
属性が使用されます。 通常、この属性を直接使用することはありません。 コンポーネントで基底クラスが必要であり、基底クラスで挿入されたプロパティも必要な場合は、[Inject]
属性を手動で追加します。
using Microsoft.AspNetCore.Components;
public class ComponentBase : IComponent
{
[Inject]
protected NavigationManager Navigation { get; set; } = default!;
...
}
メモ
挿入されたサービスは使用できるようになると予想されるため、null を許容する演算子 (default!
) を持つ既定のリテラルが .NET 6 以降で割り当てられます。 詳細については、「null 許容参照型 (NRT) と .NET コンパイラの null 状態の静的分析」を参照してください。
基底クラスから派生されたコンポーネントでは、@inject
ディレクティブは必要ありません。 基底クラスの InjectAttribute で十分です。 このコンポーネントには、@inherits
ディレクティブのみが必要です。 次の例で、CustomComponentBase
の挿入されたサービスはすべて、Demo
コンポーネントに使用できます。
@page "/demo"
@inherits CustomComponentBase
サービスで DI を使用する
複雑なサービスでは、追加のサービスが必要になる場合があります。 次の例では、DataAccess
に HttpClient の既定のサービスが必要です。 @inject
(または [Inject]
属性) は、サービスでは使用できません。 代わりに、"コンストラクター挿入" を使用する必要があります。 サービスのコンストラクターにパラメーターを追加することによって、必要なサービスが追加されます。 DI では、サービスを作成するときに、コンストラクターで必要なサービスが認識され、それに応じてサービスが提供されます。 次の例では、コンストラクターは DI で HttpClient を受け取ります。 HttpClient は既定のサービスです。
using System.Net.Http;
public class DataAccess : IDataAccess
{
public DataAccess(HttpClient http)
{
...
}
...
}
コンストラクターの挿入は、C# 12 (.NET 8) 以降でのプライマリ コンストラクターでサポートされています。
using System.Net.Http;
public class DataAccess(HttpClient http) : IDataAccess
{
...
}
コンストラクター挿入の前提条件:
- DI によってすべての引数を満たすことができるコンストラクターが 1 つ存在する必要があります。 DI で満たすことができない追加のパラメーターは、既定値が指定されている場合に許可されます。
- 該当するコンストラクターは、
public
である必要があります。 - 該当するコンストラクターが 1 つ存在する必要があります。 あいまいさがある場合は、DI で例外がスローされます。
キー付きサービスをコンポーネントに挿入する
Blazor では、[Inject]
属性を使ったキー付きサービスの挿入がサポートされています。 キーを利用すると、依存関係の挿入を使うときに、サービスの登録と使用のスコープを設定できます。 InjectAttribute.Key プロパティを使用して、挿入するサービスのキーを指定します:
[Inject(Key = "my-service")]
public IMyService MyService { get; set; }
DI スコープを管理するためのユーティリティの基本コンポーネント クラス
Blazor ASP.NET Core アプリ以外では、スコープ付きのサービスと一時的なサービスは通常、現在の要求にスコープ指定されます。 要求が完了すると、スコープ付きのサービスと一時的なサービスは DI システムによって破棄されます。
対話型のサーバー側 Blazor アプリでは、回線 (クライアントとサーバー間の SignalR 接続) がある間は DI スコープが存続します。そのため、スコープが指定された破棄可能な一時サービスが、1 つのコンポーネントの有効期間よりもはるかに長く存続する可能性があります。 そのため、サービスの有効期間をコンポーネントの有効期間と一致させる場合は、スコープ付きのサービスをコンポーネントに直接挿入しないでください。 IDisposable を実装していないコンポーネントに挿入された一時サービスは、コンポーネントが破棄されるときにガベージ コレクションされます。 ただし、"IDisposable を実装する" 挿入された一時サービスは、回路の有効期間中は DI コンテナーによって維持されるため、コンポーネントが破棄されるときにサービスのガベージ コレクションが行われず、メモリ リークが発生します。 OwningComponentBase 型に基づいたスコープ付きのサービスの代替アプローチについては、このセクションの後半で説明します。破棄可能な一時サービスはまったく使わないでください。 詳細については、「Blazor Server 上の一時的な破棄を解決するための設計 (dotnet/aspnetcore
#26676)」を参照してください。
回線上で動作しないクライアント側 Blazor アプリでも、スコープ付きの有効期間で登録されたサービスはシングルトンとして扱われるため、通常の ASP.NET Core アプリのスコープ付きサービスより長く存続します。 クライアント側の破棄可能な一時サービスは、それらが挿入されたコンポーネントよりも長く存続します。その理由は、破棄可能なサービスへの参照を保持する DI コンテナーがアプリの有効期間中は存続し、サービスのガベージ コレクションが行われないためです。 存続期間が長い破棄可能な一時サービスはサーバー上でより大きな懸念事項になりますが、クライアント サービスの登録としても避ける必要があります。 サービスの有効期間を制御するために、クライアント側のスコープ付きのサービスでも OwningComponentBase 型を使うことが推奨されます。また、破棄可能な一時サービスはまったく使わないでください。
サービスの有効期間を制限する方法には OwningComponentBase 型を使用します。 OwningComponentBase は ComponentBase から派生された抽象型であり、"コンポーネントの有効期間" に対応する DI スコープを作成します。 このスコープを使うと、コンポーネントはスコープ付きの有効期間でサービスを挿入し、コンポーネントと同じ期間だけ存続させることができます。 コンポーネントが破棄されると、コンポーネントのスコープ サービス プロバイダーからのサービスも破棄されます。 これは、コンポーネント内では再利用され、コンポーネント間では共有されないサービスの場合に役立ちます。
2 つのバージョンの OwningComponentBase 型を使用でき、次の 2 つのセクションで説明されています。
OwningComponentBase
OwningComponentBase は、ComponentBase 型の抽象的で破棄可能な子であり、IServiceProvider型の保護された ScopedServices プロパティがあります。 このプロバイダーを使用すると、コンポーネントの有効期間にスコープが設定されているサービスを解決できます。
@inject
または [Inject]
属性 を使用してコンポーネントに挿入された DI サービスは、コンポーネントのスコープでは作成されません。 コンポーネントのスコープを使用するには、GetRequiredService または GetService で ScopedServices を使用してサービスを解決する必要があります。 ScopedServices プロバイダーを使用して解決されたすべてのサービスには、コンポーネントのスコープで提供される依存関係があります。
次の例は、スコープ付きサービスを直接挿入した場合と、サーバーの ScopedServices を使用してサービスを解決した場合の違いを示しています。 タイム トラベル クラスの次のインターフェイスと実装には、DateTime 値を保持する DT
プロパティが含まれています。 TimeTravel
クラスがインスタンス化されると、実装によって DateTime.Now が呼び出され、DT
が設定されます。
ITimeTravel.cs
:
public interface ITimeTravel
{
public DateTime DT { get; set; }
}
TimeTravel.cs
:
public class TimeTravel : ITimeTravel
{
public DateTime DT { get; set; } = DateTime.Now;
}
サービスは、サーバー側 Program
ファイルのスコープとして登録されます。 サーバー側のスコープ サービスの有効期間は、回線の期間と同じです。
Program
ファイルでは:
builder.Services.AddScoped<ITimeTravel, TimeTravel>();
次の TimeTravel
コンポーネントでは、以下のことを行います。
- タイム トラベル サービスは、
TimeTravel1
として@inject
で直接挿入されます。 - サービスは、
TimeTravel2
として ScopedServices と GetRequiredService でも個別に解決されます。
TimeTravel.razor
:
@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase
<h1><code>OwningComponentBase</code> Example</h1>
<ul>
<li>TimeTravel1.DT: @TimeTravel1?.DT</li>
<li>TimeTravel2.DT: @TimeTravel2?.DT</li>
</ul>
@code {
private ITimeTravel TimeTravel2 { get; set; } = default!;
protected override void OnInitialized()
{
TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
}
}
@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase
<h1><code>OwningComponentBase</code> Example</h1>
<ul>
<li>TimeTravel1.DT: @TimeTravel1?.DT</li>
<li>TimeTravel2.DT: @TimeTravel2?.DT</li>
</ul>
@code {
private ITimeTravel TimeTravel2 { get; set; } = default!;
protected override void OnInitialized()
{
TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
}
}
最初に TimeTravel
コンポーネントに移動すると、タイム トラベル サービスはコンポーネントの読み込み時に 2 回インスタンス化され、TimeTravel1
と TimeTravel2
の初期値が同じになります。
TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:45 PM
TimeTravel
コンポーネントから別のコンポーネントに移動し、TimeTravel
コンポーネントに戻る場合:
TimeTravel1
には、コンポーネントが最初に読み込まれたときに作成されたのと同じサービス インスタンスが提供されるため、DT
の値は変わりません。TimeTravel2
は、新しい DT 値を使用してTimeTravel2
に新しいITimeTravel
サービス インスタンスを取得します。
TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:48 PM
TimeTravel1
はユーザーの回線に関連付けられています。これはそのまま残り、基になる回線が分解されるまで破棄されません。 たとえば、切断された回線の保持期間に回線が切断された場合、サービスは破棄されます。
Program
ファイルのスコープ付きサービスの登録とユーザーの回線の有効期間にかかわらず、コンポーネントが初期化されるたびに TimeTravel2
は新しい ITimeTravel
サービス インスタンスを受け取ります。
OwningComponentBase<TService>
OwningComponentBase から派生する OwningComponentBase<TService> では、スコープ DI プロバイダーから T
のインスタンスを返すプロパティ Service が追加されます。 この型は、アプリで 1 つのプライマリ サービスをコンポーネントのスコープを使用して DI コンテナーに要求するときに、IServiceProvider のインスタンスを使用せずにスコープ サービスにアクセスするための便利な方法です。 ScopedServices プロパティを使用できるので、必要に応じて、アプリで他の型のサービスを取得できます。
@page "/users"
@attribute [Authorize]
@inherits OwningComponentBase<AppDbContext>
<h1>Users (@Service.Users.Count())</h1>
<ul>
@foreach (var user in Service.Users)
{
<li>@user.UserName</li>
}
</ul>
クライアント側の一時的な破棄を検出する
カスタム コードをクライアント側 Blazor アプリに追加して、アプリ内で OwningComponentBase を使用する必要がある、破棄可能で一時的なサービスを検出できます。 このアプローチは、将来的にアプリに追加されるコードが、ライブラリによって追加されたサービスを含む 1 つ以上の一時的で破棄可能なサービスを使用することが懸念される場合に役立ちます。 デモ コードはBlazor サンプル GitHub リポジトリ (ダウンロード方法) にあります。
.NET 6 以降のバージョンのBlazorSample_WebAssembly
サンプルで、次の内容を調べます。
DetectIncorrectUsagesOfTransientDisposables.cs
Services/TransientDisposableService.cs
- In:
Program.cs
- アプリの
Services
名前空間が、ファイルの先頭に用意されています (using BlazorSample.Services;
)。 DetectIncorrectUsageOfTransients
は、builder
が WebAssemblyHostBuilder.CreateDefault から割り当てられた直後に呼び出されます。TransientDisposableService
が登録されます (builder.Services.AddTransient<TransientDisposableService>();
)。EnableTransientDisposableDetection
が、アプリの処理パイプライン内のビルドされたホスト (host.EnableTransientDisposableDetection();
) で呼び出されます。
- アプリの
- アプリによって、例外をスローせずに
TransientDisposableService
サービスが登録されます。 ただし、TransientService.razor
でサービスを解決しようとすると、フレームワークがTransientDisposableService
のインスタンスを構築しようとしたときに InvalidOperationException がスローされます。
サーバー側の一時的な破棄可能なアイテムを検出する
カスタム コードをサーバー側 Blazor アプリに追加して、アプリ内で OwningComponentBase を使用する必要がある、サーバー側の破棄可能で一時的なサービスを検出できます。 このアプローチは、将来的にアプリに追加されるコードが、ライブラリによって追加されたサービスを含む 1 つ以上の一時的で破棄可能なサービスを使用することが懸念される場合に役立ちます。 デモ コードはBlazor サンプル GitHub リポジトリ (ダウンロード方法) にあります。
.NET 8 以降のバージョンの BlazorSample_BlazorWebApp
サンプルで、次の内容を調べます。
.NET 6 または .NET 7 のバージョンの BlazorSample_Server
サンプルで、次の内容を調べます。
DetectIncorrectUsagesOfTransientDisposables.cs
Services/TransitiveTransientDisposableDependency.cs
:- In:
Program.cs
- アプリの
Services
名前空間が、ファイルの先頭に用意されています (using BlazorSample.Services;
)。 DetectIncorrectUsageOfTransients
が、ホスト ビルダー (builder.DetectIncorrectUsageOfTransients();
) で呼び出されます。TransientDependency
サービスが登録されます (builder.Services.AddTransient<TransientDependency>();
)。TransitiveTransientDisposableDependency
がITransitiveTransientDisposableDependency
(builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();
) に登録されます。
- アプリの
- アプリによって、例外をスローせずに
TransientDependency
サービスが登録されます。 ただし、TransientService.razor
でサービスを解決しようとすると、フレームワークがTransientDependency
のインスタンスを構築しようとしたときに InvalidOperationException がスローされます。
IHttpClientFactory
/HttpClient
ハンドラーの一時的なサービス登録
IHttpClientFactory/HttpClient ハンドラーの一時サービス登録をお勧めします。 アプリが IHttpClientFactory/HttpClient ハンドラーを含んでおり、IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> を使用して認証のサポートを追加する場合は、クライアント側認証用の次の一時的な破棄も検出されますが、これは想定されたもので、無視して構いません。
IHttpClientFactory/HttpClient の他のインスタンスも検出されます。 これらのインスタンスは無視することもできます。
Blazor サンプル GitHub リポジトリ (ダウンロード方法) の Blazor サンプル アプリは、一時的な破棄可能を検出するコードを示しています。 ただし、サンプル アプリには IHttpClientFactory/HttpClient ハンドラーが含まれているため、コードは非アクティブ化されています。
デモ コードをアクティブにしてその操作を確認するには、以下の手順を実行します。
Program.cs
内の一時的な破棄可能の行のコメントを解除します。アプリのナビゲーション サイドバーに
TransientService
コンポーネントが表示されないようにする、NavLink.razor
の条件チェックを削除します。- else if (name != "TransientService") + else
サンプル アプリを実行し、
/transient-service
にあるTransientService
コンポーネントに移動します。
DI からの Entity Framework Core (EF Core) DbContext の使用
詳細については、「ASP.NET Core Blazor と Entity Framework Core (EF Core)」をご覧ください。
異なる DI スコープからサーバー側の Blazor サービスにアクセスする
回線アクティビティ ハンドラーには、Blazor ではない依存関係の挿入 (DI) スコープ (IHttpClientFactory を使って作成されたスコープなど) からスコープ付き Blazor サービスにアクセスするためのアプローチが用意されています。
.NET 8 の ASP.NET Core がリリースされる前は、他の依存関係の挿入スコープから回線スコープ付きサービスにアクセスするには、カスタム ベース コンポーネントの種類を使う必要がありました。 回線アクティビティ ハンドラーを使う場合、次の例で示すようにカスタム ベース コンポーネントの種類は必要ありません。
public class CircuitServicesAccessor
{
static readonly AsyncLocal<IServiceProvider> blazorServices = new();
public IServiceProvider? Services
{
get => blazorServices.Value;
set => blazorServices.Value = value;
}
}
public class ServicesAccessorCircuitHandler(
IServiceProvider services, CircuitServicesAccessor servicesAccessor)
: CircuitHandler
{
public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
Func<CircuitInboundActivityContext, Task> next) =>
async context =>
{
servicesAccessor.Services = services;
await next(context);
servicesAccessor.Services = null;
};
}
public static class CircuitServicesServiceCollectionExtensions
{
public static IServiceCollection AddCircuitServicesAccessor(
this IServiceCollection services)
{
services.AddScoped<CircuitServicesAccessor>();
services.AddScoped<CircuitHandler, ServicesAccessorCircuitHandler>();
return services;
}
}
必要な場所に CircuitServicesAccessor
を挿入して、回線スコープ付きサービスにアクセスします。
IHttpClientFactory を使って DelegatingHandler の設定から AuthenticationStateProvider にアクセスする方法を示す例については、「サーバーサイド ASP.NET Core Blazor のセキュリティに関するその他のシナリオ」を参照してください。
Razor コンポーネントが、別の DI スコープでコードを実行する非同期メソッドを呼び出す場合があります。 正しいアプローチを使用しないと、これらの DI スコープで Blazor のサービスにアクセスできません (IJSRuntime や Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage など)。
たとえば、IHttpClientFactory を使って作成された HttpClient インスタンスには、独自の DI サービス スコープがあります。 その結果、HttpClient で構成された HttpMessageHandler インスタンスは、Blazor サービスを直接挿入できません。
AsyncLocal
を定義するクラス BlazorServiceAccessor
を作成します。これは、現在の非同期コンテキストの BlazorIServiceProvider を格納します。 BlazorServiceAccessor
インスタンスを別の DI サービス スコープ内から取得して、Blazor サービスにアクセスできます。
BlazorServiceAccessor.cs
:
internal sealed class BlazorServiceAccessor
{
private static readonly AsyncLocal<BlazorServiceHolder> s_currentServiceHolder = new();
public IServiceProvider? Services
{
get => s_currentServiceHolder.Value?.Services;
set
{
if (s_currentServiceHolder.Value is { } holder)
{
// Clear the current IServiceProvider trapped in the AsyncLocal.
holder.Services = null;
}
if (value is not null)
{
// Use object indirection to hold the IServiceProvider in an AsyncLocal
// so it can be cleared in all ExecutionContexts when it's cleared.
s_currentServiceHolder.Value = new() { Services = value };
}
}
}
private sealed class BlazorServiceHolder
{
public IServiceProvider? Services { get; set; }
}
}
async
コンポーネント メソッドが呼び出されたときに BlazorServiceAccessor.Services
の値を自動的に設定するには、Razor コンポーネント コードに 3 つの主要な非同期エントリ ポイントを再実装するカスタム基本コンポーネントを作成します。
次のクラスは、基本コンポーネントの実装を示しています。
CustomComponentBase.cs
:
using Microsoft.AspNetCore.Components;
public class CustomComponentBase : ComponentBase, IHandleEvent, IHandleAfterRender
{
private bool hasCalledOnAfterRender;
[Inject]
private IServiceProvider Services { get; set; } = default!;
[Inject]
private BlazorServiceAccessor BlazorServiceAccessor { get; set; } = default!;
public override Task SetParametersAsync(ParameterView parameters)
=> InvokeWithBlazorServiceContext(() => base.SetParametersAsync(parameters));
Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
=> InvokeWithBlazorServiceContext(() =>
{
var task = callback.InvokeAsync(arg);
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
task.Status != TaskStatus.Canceled;
StateHasChanged();
return shouldAwaitTask ?
CallStateHasChangedOnAsyncCompletion(task) :
Task.CompletedTask;
});
Task IHandleAfterRender.OnAfterRenderAsync()
=> InvokeWithBlazorServiceContext(() =>
{
var firstRender = !hasCalledOnAfterRender;
hasCalledOnAfterRender |= true;
OnAfterRender(firstRender);
return OnAfterRenderAsync(firstRender);
});
private async Task CallStateHasChangedOnAsyncCompletion(Task task)
{
try
{
await task;
}
catch
{
if (task.IsCanceled)
{
return;
}
throw;
}
StateHasChanged();
}
private async Task InvokeWithBlazorServiceContext(Func<Task> func)
{
try
{
BlazorServiceAccessor.Services = Services;
await func();
}
finally
{
BlazorServiceAccessor.Services = null;
}
}
}
CustomComponentBase
を拡張するすべてのコンポーネントでは、BlazorServiceAccessor.Services
が現在の Blazor DI スコープの IServiceProvider に自動的に設定されます。
最後に、Program
ファイルで、スコープ付きサービスとして BlazorServiceAccessor
を追加します:
builder.Services.AddScoped<BlazorServiceAccessor>();
最後に、Startup.cs
の Startup.ConfigureServices
で BlazorServiceAccessor
をスコープ付きサービスとして追加します。
services.AddScoped<BlazorServiceAccessor>();
その他のリソース
ASP.NET Core