トレーニング
モジュール
ASP.NET Core で依存関係の挿入を使用してサービスを構成する - Training
ASP.NET Core アプリの依存関係の挿入を理解して実装します。 ASP.NET Core の組み込みのサービス コンテナーを使用して依存関係を管理します。 サービスをサービス コンテナーに登録します。
このブラウザーはサポートされなくなりました。
Microsoft Edge にアップグレードすると、最新の機能、セキュリティ更新プログラム、およびテクニカル サポートを利用できます。
.NET では依存関係の挿入 (DI) ソフトウェア設計パターンがサポートされています。これは、クラスとその依存関係の間で制御の反転 (IoC) を実現するための手法です。 .NET での依存関係の挿入は、構成、ログ、オプション パターンと共に、フレームワークの組み込み部分です。
"依存関係" とは、他のオブジェクトが依存するオブジェクトのことです。 他のクラスが依存している、次の Write
メソッドを備えた MessageWriter
クラスを調べます。
public class MessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
クラスは、MessageWriter
クラスのインスタンスを作成して、その Write
メソッドを使用することができます。 次の例で、MessageWriter
クラスは Worker
クラスの依存関係です。
public class Worker : BackgroundService
{
private readonly MessageWriter _messageWriter = new();
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
このクラスは MessageWriter
クラスを作成し、これに直接依存しています。 ハードコーディングされた依存関係には、前の例のような問題があり、次の理由から回避する必要があります。
MessageWriter
を別の実装で置き換えるには、Worker
クラスを変更する必要があります。MessageWriter
が依存関係を含んでいる場合、これらは Worker
クラスによって構成する必要があります。 複数のクラスが MessageWriter
に依存している大規模なプロジェクトでは、構成コードがアプリ全体に分散するようになります。MessageWriter
クラスを使用する必要がありますが、この方法では不可能です。依存関係の挿入は、次の方法によってこれらの問題に対応します。
たとえば、IMessageWriter
インターフェイスに Write
メソッドを定義します。
namespace DependencyInjection.Example;
public interface IMessageWriter
{
void Write(string message);
}
このインターフェイスは、具象型 MessageWriter
によって実装されます。
namespace DependencyInjection.Example;
public class MessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
このサンプル コードの場合、具象型 MessageWriter
を使用して IMessageWriter
サービスが登録されます。 AddSingleton メソッドでは、シングルトンの有効期間 (アプリの有効期間) でサービスを登録します。 サービスの有効期間については、この記事の後半で説明します。
using DependencyInjection.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();
using IHost host = builder.Build();
host.Run();
前のコードでは、サンプル アプリで次の処理が行われます。
ホスト アプリ ビルダーのインスタンスを作成します。
次を登録してサービスを構成します。
Worker
。 詳細については、「.NET の Worker サービス」を参照してください。MessageWriter
クラスの該当実装があるシングルトン サービスとしての IMessageWriter
インターフェイス。ホストをビルドして実行します。
ホストには、依存関係挿入サービス プロバイダーが含まれています。 また、Worker
のインスタンスを自動的に作成し、対応する IMessageWriter
実装を引数として提供するために必要なその他すべての関連サービスが含まれています。
namespace DependencyInjection.Example;
public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
DI パターンを使用することにより、Worker サービスは次のようになります。
MessageWriter
は使用されず、実装される IMessageWriter
インターフェイスのみが使用されます。 これにより、Worker サービスが使用する実装は、Worker サービスを変更することなく、簡単に変更できるようになります。MessageWriter
のインスタンスは作成できません。 このインスタンスは DI コンテナーによって作成されます。組み込みのログ API を使用すると、IMessageWriter
インターフェイスの実装を向上させることができます。
namespace DependencyInjection.Example;
public class LoggingMessageWriter(
ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
public void Write(string message) =>
logger.LogInformation("Info: {Msg}", message);
}
更新された AddSingleton
メソッドでは、新しい IMessageWriter
の実装が登録されます。
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
HostApplicationBuilder (builder
) 型は、Microsoft.Extensions.Hosting
NuGet パッケージの一部です。
LoggingMessageWriter
は、コンストラクターで要求される ILogger<TCategoryName> によって異なります。 ILogger<TCategoryName>
は、フレームワークで提供されるサービスです。
依存関係の挿入をチェーン形式で使用することはよくあります。 次に、要求されたそれぞれの依存関係が、それ自身の依存関係を要求します。 コンテナーによってグラフ内の依存関係が解決され、完全に解決されたサービスが返されます。 解決する必要がある依存関係の集合的なセットは、通常、"依存関係ツリー"、"依存関係グラフ"、または "オブジェクト グラフ" と呼ばれます。
コンテナーでは、(ジェネリック) オープン型を活用し、すべての (ジェネリック) 構築型を登録する必要をなくすことで、ILogger<TCategoryName>
を解決します。
依存関係の挿入に関する用語では、サービスは次のようになります。
IMessageWriter
サービスなど) にサービスを提供するオブジェクトです。フレームワークは、堅牢な ログ システムを備えています。 前の例に示されている IMessageWriter
の実装は、ログを実装するのではなく、基本的な DI を実演するために記述されています。 ほとんどのアプリでは、ロガーを記述する必要はありません。 次のコードは、既定のログを使用する方法を示しています。この場合、Worker
をホステッド サービス AddHostedService として登録するだけで済みます。
public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1_000, stoppingToken);
}
}
}
前のコードを使用すると、ログがフレームワークによって提供されるため、Program.cs を更新する必要はありません。
型で複数のコンストラクターが定義されている場合、サービス プロバイダーにはどのコンストラクターを使うかを決定するためのロジックがあります。 型が DI 解決可能であるパラメーターを一番多く持つコンストラクターが選ばれます。 次の C# のサービス例を考えてみます。
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(FooService fooService, BarService barService)
{
// omitted for brevity
}
}
上記のコードでは、ログ記録が追加されておりサービス プロバイダーから解決可能ですが、FooService
型と BarService
型は解決できないことを想定してください。 ILogger<ExampleService>
パラメーターを持つコンストラクターが ExampleService
インスタンスを解決するために使われます。 より多くのパラメーターが定義されたコンストラクターが存在しますが、FooService
型と BarService
型は DI 解決可能ではありません。
コンストラクターを検出するときにあいまいさがある場合は、例外がスローされます。 次の C# のサービス例を考えてみます。
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
警告
あいまいな DI 解決可能型パラメーターを持つ ExampleService
コードでは、例外がスローされます。 これは行わないでください。これは "あいまいな DI 解決可能型" の意味を示すことを目的としたものです。
前の例では、3 つのコンストラクターがあります。 最初のコンストラクターにはパラメーターがなく、サービス プロバイダーからのサービスを必要としません。 ログ記録とオプションの両方が DI コンテナーに追加されており、DI 解決可能なサービスであることを想定してください。 DI コンテナーによって ExampleService
型の解決が試みられると、2 つのコンストラクターがあいまいであるため、例外がスローされます。
代わりに両方の DI 解決可能な型を受け入れるコンストラクターを定義することで、あいまいさを回避できます。
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(
ILogger<ExampleService> logger,
IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Microsoft 拡張機能には、関連するサービスのグループを登録するための規則が使用されます。 規則は、単一の Add{GROUP_NAME}
拡張メソッドを使用して、フレームワーク機能に必要なすべてのサービスを登録するというものです。 たとえば、AddOptions 拡張メソッドにより、オプションの使用に必要なすべてのサービスが登録されます。
使用可能なホストまたはアプリ ビルダー パターンのいずれかを利用すると、既定値が適用され、サービスがフレームワークによって登録されます。 最も一般的なホストおよびアプリ ビルダー パターンをいくつか考えてみましょう。
上記の API のいずれかでビルダーを作成したら、ホストの構成方法に応じて、フレームワークで定義されたサービスが IServiceCollection
に登録されます。 .NET テンプレートをベースにしたアプリの場合、フレームワークで数百単位のサービスを登録できます。
次の表に、フレームワークによって登録されるサービスのごく一部を示します。
サービスは、次のいずれかの有効期間で構成できます。
次のセクションでは、前の有効期間について個別に説明します。 登録される各サービスの適切な有効期間を選択します。
有効期間が一時的なサービスは、サービス コンテナーから要求されるたびに作成されます。 "一時的なもの" としてサービスを登録するには、AddTransient を呼び出します。
要求を処理するアプリでは、一時的なサービスが要求の最後に破棄されます。 この有効期間では、サービスが解決され、毎回構築されるため、要求ごとの割り当てが発生します。 詳細については、「依存関係の挿入のガイドライン: 一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス」を参照してください。
Web アプリケーションの場合、スコープ付き有効期間は、クライアント要求 (接続) ごとにサービスが 1 回作成されることを示します。 AddScoped でスコープ付きサービスを登録します。
要求を処理するアプリでは、スコープ付きサービスは要求の最後で破棄されます。
Entity Framework Core を使用する場合、既定では AddDbContext 拡張メソッドによって、スコープ付き有効期間を持つ DbContext
型が登録されます。
注意
シングルトンからスコープ付きサービスを解決 "しないで" ください。また、たとえば一時的なサービスにより、間接的に解決しないようにご注意ください。 後続の要求を処理する際に、サービスが正しくない状態になる可能性があります。 次の場合は問題ありません。
既定では、開発環境で、より長い有効期間を持つ別のサービスからサービスを解決すると、例外がスローされます。 詳しくは、「スコープの検証」をご覧ください。
シングルトン有効期間サービスが作成されるのは、次のいずれかの場合です。
依存関係の挿入コンテナーから送信されるサービス実装の後続の要求すべてには、同じインスタンスが使用されます。 アプリをシングルトンで動作させる必要がある場合は、サービス コンテナーによるサービスの有効期間の管理を許可してください。 シングルトン デザイン パターンを実装したり、シングルトンを破棄するコードを提供したりしないでください。 コンテナーからサービスを解決したコードによって、サービスが破棄されることはありません。 型またはファクトリがシングルトンとして登録されている場合、コンテナーによってシングルトンが自動的に破棄されます。
シングルトン サービスを AddSingleton で登録します。 シングルトン サービスはスレッド セーフである必要があり、ほとんどの場合、ステートレス サービスで使用されます。
要求を処理するアプリでは、アプリのシャットダウン時に ServiceProvider が破棄されるとき、シングルトン サービスが破棄されます。 アプリがシャットダウンされるまでメモリは解放されないため、シングルトン サービスでのメモリ使用を考慮してください。
このフレームワークでは、特定のシナリオで役立つサービス登録拡張メソッドが提供されます。
方法 | 自動 object 破棄 |
複数 実装 |
引数を渡す |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() 例: services.AddSingleton<IMyDep, MyDep>(); |
はい | はい | いいえ |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) 例 : services.AddSingleton<IMyDep>(sp => new MyDep()); services.AddSingleton<IMyDep>(sp => new MyDep(99)); |
はい | イエス | はい |
Add{LIFETIME}<{IMPLEMENTATION}>() 例: services.AddSingleton<MyDep>(); |
はい | いいえ | いいえ |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) 例 : services.AddSingleton<IMyDep>(new MyDep()); services.AddSingleton<IMyDep>(new MyDep(99)); |
いいえ | イエス | はい |
AddSingleton(new {IMPLEMENTATION}) 例 : services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep(99)); |
いいえ | 番号 | はい |
型の廃棄の詳細については、「サービスの破棄」を参照してください。
実装型のみでサービスを登録することは、同じ実装とサービスの型でそのサービスを登録することと同じです。 表すクレソン、ダン橄欖岩製品構文解析木作法:
services.AddSingleton<ExampleService>();
これは、サービスを同じ型のサービスと実装の両方に登録することと同じです。
services.AddSingleton<ExampleService, ExampleService>();
これが同じであることが、明示的なサービス型を使用しないメソッドを使用してサービスの複数の実装を登録できない理由です。 これらのメソッドでは、サービスの複数の "インスタンス" を登録できますが、すべて同じ "実装" 型になります。
上記のサービス登録メソッドのずれかを使用して、同じサービス型の複数のサービス インスタンスを登録できます。 次の例では、IMessageWriter
をサービス型として使用して、AddSingleton
を 2 回呼び出します。 2 回目の AddSingleton
の呼び出しにより、IMessageWriter
として解決された場合は前のものがオーバーライドされ、IEnumerable<IMessageWriter>
を介して複数のサービスが解決された場合は前のものに追加されます。 IEnumerable<{SERVICE}>
を介して解決された場合、サービスは登録された順に表示されます。
using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();
using IHost host = builder.Build();
_ = host.Services.GetService<ExampleService>();
await host.RunAsync();
上記のサンプル ソース コードを使用すると、IMessageWriter
の 2 つの実装が登録されます。
using System.Diagnostics;
namespace ConsoleDI.IEnumerableExample;
public sealed class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is LoggingMessageWriter);
var dependencyArray = messageWriters.ToArray();
Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
}
}
ExampleService
により、2 つのコンストラクター パラメーター (1 つの IMessageWriter
と IEnumerable<IMessageWriter>
) が定義されます。 1 つの IMessageWriter
は登録された最後の実装です。一方、IEnumerable<IMessageWriter>
は登録されたすべての実装を表します。
フレームワークには TryAdd{LIFETIME}
拡張メソッドも用意されており、実装がまだ登録されていない場合にのみ、サービスが登録されます。
次の例では、AddSingleton
の呼び出しによって、IMessageWriter
の実装として ConsoleMessageWriter
が登録されます。 TryAddSingleton
の呼び出しでは何も行われません。IMessageWriter
には登録された実装が既に含まれているからです。
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
TryAddSingleton
は、既に追加されており、"試行" は失敗するため、効果はありません。 ExampleService
により、以下がアサートされます。
public class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is ConsoleMessageWriter);
Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
}
}
詳細については、以下を参照してください:
TryAddEnumerable(ServiceDescriptor) メソッドでは、"同じ型" の実装がまだ存在しない場合にのみサービスが登録されます。 複数のサービスは、IEnumerable<{SERVICE}>
によって解決されます。 サービスを登録するときに、同じ型のいずれかがまだ追加されていない場合は、インスタンスを追加します。 ライブラリの作成者は、コンテナー内の実装の複数のコピーを登録しないようにするため TryAddEnumerable
を使用します。
次の例では、TryAddEnumerable
の最初の呼び出しで、IMessageWriter1
の実装として MessageWriter
が登録されます。 2 番目の呼び出しでは IMessageWriter2
に MessageWriter
が登録されます。 3 番目の呼び出しでは何も行われません。IMessageWriter1
には MessageWriter
の登録済みの実装が既に含まれているからです。
public interface IMessageWriter1 { }
public interface IMessageWriter2 { }
public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
サービスの登録は、通常、同じ種類の複数の実装を登録する場合を除き、順序に依存しません。
IServiceCollection
は ServiceDescriptor オブジェクトのコレクションです。 次の例は、ServiceDescriptor
の作成と追加によってサービスを登録する方法を示しています。
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
typeof(IMessageWriter),
_ => new DefaultMessageWriter(secretKey),
ServiceLifetime.Transient);
services.Add(descriptor);
組み込みの Add{LIFETIME}
メソッドでも同じ方法が使用されます。 たとえば、AddScoped のソース コードをご覧ください。
サービスは次を使用することによって解決できます。
コンストラクターは、依存関係の挿入によって提供されない引数を受け取ることができますが、引数は既定値を割り当てる必要があります。
IServiceProvider
または ActivatorUtilities
によってサービスを解決する場合、コンストラクターの挿入には "パブリック" コンストラクターが必要です。
ActivatorUtilities
によってサービスを解決する場合、コンストラクターの挿入に必要なことは、該当するコンストラクターが 1 つだけ存在することです。 コンストラクターのオーバーロードはサポートされていますが、依存関係の挿入によってすべての引数を設定できるオーバーロードは 1 つしか存在できません。
アプリが Development
環境で実行されていて、CreateApplicationBuilder を呼び出してホストを構築している場合、既定のサービス プロバイダーが以下を確認するチェックを実行します。
BuildServiceProvider が呼び出されると、ルート サービス プロバイダーが作成されます。 ルート サービス プロバイダーの有効期間は、プロバイダーがアプリで開始されるとアプリの有効期間に対応し、アプリのシャットダウン時には破棄されます。
スコープ サービスは、それを作成したコンテナーによって破棄されます。 ルート コンテナーに作成されたスコープ付きサービスは、アプリのシャットダウン時に、ルート コンテナーによってのみ破棄されるため、サービスの有効期間は実質的にシングルトンに昇格されます。 BuildServiceProvider
が呼び出されると、サービス スコープの検証がこれらの状況をキャッチします。
IServiceScopeFactory は常にシングルトンとして登録されますが、IServiceProvider は包含クラスの有効期間に基づいて変化する可能性があります。 たとえば、スコープからサービスを解決し、それらのサービスのいずれかが IServiceProvider 受け取ると、それは、スコープ付のインスタンスになります。
BackgroundService などの IHostedService の実装内でスコープ サービスを実現するには、コンストラクター インジェクションを介してサービスの依存関係を挿入 "しないでください"。 代わりに、IServiceScopeFactory を挿入し、スコープを作成し、そしてそのスコープから依存関係を解決して適切なサービス有効期間を使用します。
namespace WorkerScope.Example;
public sealed class Worker(
ILogger<Worker> logger,
IServiceScopeFactory serviceScopeFactory)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
try
{
logger.LogInformation(
"Starting scoped work, provider hash: {hash}.",
scope.ServiceProvider.GetHashCode());
var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
var next = await store.GetNextAsync();
logger.LogInformation("{next}", next);
var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
await processor.ProcessAsync(next);
logger.LogInformation("Processing {name}.", next.Name);
var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
await relay.RelayAsync(next);
logger.LogInformation("Processed results have been relayed.");
var marked = await store.MarkAsync(next);
logger.LogInformation("Marked as processed: {next}", marked);
}
finally
{
logger.LogInformation(
"Finished scoped work, provider hash: {hash}.{nl}",
scope.ServiceProvider.GetHashCode(), Environment.NewLine);
}
}
}
}
}
前のコードでは、アプリが実行されている間、バックグラウンド サービスは次のようになります。
サンプル ソース コードから、スコープ付きサービスの有効期間が IHostedService の実装にどのような恩恵をもたらすかを確認できます。
.NET 8 以降では、キーに基づくサービスの登録と検索もサポートされています。つまり、複数のサービスを別のキーで登録し、このキーを検索のために使用することが可能です。
たとえば、インターフェイス IMessageWriter
の異なる実装 (MemoryMessageWriter
と QueueMessageWriter
) がある場合を考えましょう。
これらのサービスは、次のようにパラメーターとしてキーをサポートするサービス登録メソッド (前述) のオーバーロードを使用して登録できます。
services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");
key
は string
に限定されず、型が正しく Equals
を実装する限り、任意の object
とすることができます。
IMessageWriter
を使用するクラスのコンストラクターで、次のように FromKeyedServicesAttribute を追加して解決するサービスのキーを指定します。
public class ExampleService
{
public ExampleService(
[FromKeyedServices("queue")] IMessageWriter writer)
{
// Omitted for brevity...
}
}
.NET に関するフィードバック
.NET はオープンソース プロジェクトです。 フィードバックを提供するにはリンクを選択します。
トレーニング
モジュール
ASP.NET Core で依存関係の挿入を使用してサービスを構成する - Training
ASP.NET Core アプリの依存関係の挿入を理解して実装します。 ASP.NET Core の組み込みのサービス コンテナーを使用して依存関係を管理します。 サービスをサービス コンテナーに登録します。
ドキュメント
.NET アプリを開発するための効果的な依存関係の挿入ガイドラインとベスト プラクティスを見つけます。 制御の反転に関する理解を深めます。
この包括的なチュートリアルを使用して、.NET アプリケーションで依存関係の挿入を使用する方法について説明します。 この実用的なガイドに従って、C# における DI を理解してください。
ASP.NET Core で依存関係の挿入を実装する方法とそれを使う方法について説明します。