-
Notifications
You must be signed in to change notification settings - Fork 0
MS_DotNetCoreDI
- 戻る
ミドルウェア / サービス / フレームワークの
差し替えを実現し、特定のフレームワークへの依存性を減らす。
-
明示的な依存関係の原則
constructor 注入の DI 方式を採用している。 -
- 抽象化は依存関係を interface に抽出することにより行われる。
- interface 実装をパラメタとして提供するのも、戦略設計パターンの例。
補足(.NET Core で「標準」になったことの意味): .NET Framework 時代は
DI コンテナーを自分で選んで導入するものだったが、
.NET Core 以降はMicrosoft.Extensions.DependencyInjectionが
ランタイムに標準搭載され、前提が変わった。
.NET Framework .NET Core / .NET コンテナー Unity / Autofac / Ninject 等を選ぶ 標準で同梱 フレームワークとの統合 各自でアダプタを書く フレームワーク自身が DI を前提に作られている 設定 web.config(XML)Program.cs(C#)適用範囲 Web 中心 汎用ホスト(コンソール、Worker、MAUI…) 「標準がある」ことの本質的な効果は、
ライブラリ側がIServiceCollectionを前提に API を提供できる
ようになった点にある。// どのライブラリも、この形で拡張メソッドを提供する builder.Services.AddDbContext<AppDbContext>(...); // EF Core builder.Services.AddHttpClient<IApiClient, ApiClient>(); builder.Services.AddAuthentication().AddJwtBearer();原文の言う「ミドルウェア / サービス / フレームワークの差し替え」は、
拡張メソッドによる登録という共通の作法として結実した、と言える。
以下のように、インジェクションを構成できる。
-
Public constructor
- 1 つの Public constructor(複数の Public constructor は例外)
- 依存関係の挿入によって提供されない引数は、既定値のサポートが必要。
-
抽象化は依存関係を interface に抽出
補足(この制約の実際): 「Public constructor が 1 つ」という制約は、
標準コンテナーのあいまいさを排除するための設計である。// ✗ 解決できない:どちらを使うべきか決められない public class Foo { public Foo(IA a) { } public Foo(IA a, IB b) { } } // → InvalidOperationException: // Multiple constructors accepting all given argument types...正確には、「解決可能な引数を最も多く持つコンストラクタが
一意に決まらない場合」に例外になる
(どちらか一方の引数集合がもう一方を包含していれば選ばれる)。Autofac 等のサードパーティ コンテナーは
より柔軟な選択規則を持つが、
コンストラクタは 1 つに絞るのが読みやすく、事故も少ない。「既定値のサポート」については、
DI で解決しない引数は既定値を持たせるか、
ActivatorUtilitiesで明示的に渡す。// 一部の引数だけ手で渡す var foo = ActivatorUtilities.CreateInstance<Foo>(sp, "手渡しの値");
補足(原文が空欄のため補う): 原文はここで力尽きているので、
登録と解決の実際を補っておく。① 登録(
Program.cs= 合成ルート)var builder = Host.CreateApplicationBuilder(args); // ライフタイムを選んで登録する([DI](MS_DI) の「コンテナー」節を参照) builder.Services.AddSingleton<IClock, SystemClock>(); builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>(); builder.Services.AddTransient<IMailer, SmtpMailer>(); // 設定のバインド(オプション パターン) builder.Services.Configure<MyOptions>( builder.Configuration.GetSection("MyOptions")); // ホステッド サービス(常駐処理) builder.Services.AddHostedService<Worker>(); var host = builder.Build(); await host.RunAsync();② 解決(constructor 注入)
public sealed class Worker( IOrderRepository repo, IOptions<MyOptions> options, ILogger<Worker> logger) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken ct) { ... } }要点:
ILogger<T>とIOptions<T>は登録不要(ホストが既定で登録する)IServiceProviderを注入してGetServiceを呼ぶのは避ける
(サービス ロケータになる。IoC 参照)AddHostedServiceは Singleton として登録される。
中でScoped(DbContext等)を使うなら
IServiceScopeFactoryでスコープを作る
補足(標準コンテナーで足りない場合): 標準コンテナーは
意図的に機能を絞っている。次の機能は標準では提供されない。
機能 標準 代替 プロパティ注入 × Autofac / 手動 名前付き登録 △(.NET 8 で キー付きサービスが追加) [FromKeyedServices]インターセプト(AOP) × Castle DynamicProxy(AOP) 自動登録(アセンブリ走査) × Scrutor デコレーター × Scrutor( Decorate<T,U>()).NET 8 の キー付きサービスは、
「同じインターフェイスの実装を用途で使い分ける」という
従来からの要望に応えたものである。builder.Services.AddKeyedSingleton<ICache, MemoryCache>("fast"); builder.Services.AddKeyedSingleton<ICache, RedisCache>("shared"); public class Svc([FromKeyedServices("shared")] ICache cache) { }コンテナーを差し替える場合も、
IServiceCollectionへの登録は標準の作法のままでよい
(UseServiceProviderFactoryで最後に差し替える)ため、
まず標準で始めて、必要になってから移るのが安全である。
補足(.NET Framework からの移行時の注意): 既存アプリを
.NET Coreへの移行 する際、
DI 周りで問題になりやすいのは次の点である。
移行元の実装 移行時の対応 静的クラス / シングルトン( Foo.Instance)インターフェイス化して AddSingletonHttpContext.CurrentIHttpContextAccessorを注入ConfigurationManager.AppSettingsIConfiguration/ オプション パターンサービス ロケータ constructor 注入に段階的に置換 Unity / Autofac 標準に寄せるか、Autofac を .NET Core 版に更新 なお、
Microsoft.Extensions.DependencyInjectionは
netstandard2.0対応のため、
.NET Framework のまま先に DI を導入しておくことができる。
移行の足場作りとして有効な手順である。
- .NET での依存関係の挿入
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/dependency-injection - 依存関係の挿入のガイドライン
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/dependency-injection-guidelines - 依存関係の挿入 - キー付きサービス
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/dependency-injection#keyed-services
-
Inversion of Control
http://deviq.com/inversion-of-control/ -
Dependency Inversion Principle
http://deviq.com/dependency-inversion-principle/ -
Explicit Dependencies Principle
http://deviq.com/explicit-dependencies-principle/
- Strategy Design Pattern
http://deviq.com/strategy-design-pattern/
-
.NET 系の DI コンテナ
https://qiita.com/okazuki/items/239ca5ef46e5a085e085 -
DI (依存性注入) って何のためにするの
かわからない人向けに頑張って説明してみる
https://qiita.com/okazuki/items/a0f2fb0a63ca88340ff6 -
DI って何でするのかわからない人向けに
頑張って説明してみる「本来の意味」
https://qiita.com/okazuki/items/0c17a161a921847cd080 -
DI コンテナは自分で new しないでフレームワークを探そう
https://qiita.com/okazuki/items/6327d05fd84fd5de3299 -
DIコンテナのテスト以外での利点について (7/15修正)
https://qiita.com/crexista/items/606976d941728a90b42b -
「DIコンテナのテスト以外での利点について」の自分の感想
https://qiita.com/okazuki/items/a470e05c1a263921a59c
Tags: 移行, プログラミング, .NET開発, .NET Core, ASP.NET, ASP.NET MVC
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。