マニアックですがASP.NET CoreのDIコンテナ登録が冗長なので簡略化して書けるようにしました。
従来のDIコンテナの登録方法
以下のようにインターフェース毎にDIコンテナを1つずつ登録する必要があります。コードが肥大化するにつれてDIコンテナも比例して増えていくので簡略化したいと思います。
builder.Services.AddScoped(serviceType1, implementationType2);
builder.Services.AddScoped(serviceType2, implementationType2);
設計
むやみやたらに登録するのはよろしくないのでレイヤーを分けて登録できるようにします。近年のアーキテクチャ設計(クリーンアーキテクチャ等)においてレイヤーを明確に分けることが多いと思います。正解は一つではないので全てのアーキテクチャに適用できるわけではありませんが、私の場合は名前空間毎に登録できるようにしました。
実装
インターフェースは以下のようにしました。登録するレイヤーは複数対応します。
builder.Services.AddServiceDynamically(nameof(Namespace1), nameof(Namespace2));
内容は以下のように実装しました。ポイントは以下で説明します。
public static void AddServiceDynamically(this IServiceCollection services, params string[] namespaces)
{
var assemblies = Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t =>
(t.IsClass || t.IsInterface) &&
!t.IsNested &&
namespaces.Any(name => t.Namespace?.Contains(name) == true));
foreach (var implementationType in assemblies.Where(a => a.IsClass))
{
var serviceTypes = assemblies
.Where(a =>
a.Namespace == implementationType.Namespace &&
implementationType.GetInterfaces().Any(i => i.Name == a.Name));
if (!serviceTypes.Any())
continue;
foreach (var serviceType in serviceTypes)
{
services.AddScoped(serviceType, implementationType);
}
}
}
builder.Services.AddScoped(serviceType1, implementationType2);
builder.Services.AddScoped(serviceType2, implementationType2);
...
// ↑が一行になりました👍
builder.Services.AddServiceDynamically(nameof(Namespace1), nameof(Namespace2));
AddScopedのみ対応
理由としては、重複で登録された場合は最後に登録したインターフェースが適用されるため、特定のインターフェースのみシングルトンにする場合は一斉登録した後の行で明示的に適用すれば個別対応が可能なのでAddScopedのみ対応しました。
IsNested
IsNestedプロパティをチェックしないと指定した名前空間に含まれるアセンブリ情報が大量に取得されてしまいます。名前空間のアセンブリ情報のみ取得したいので追加しました
まとめ
クリーンアーキテクチャ等の設計をされたアプリケーションにおいては適用出来る仕様だと思います。いろんな考慮が漏れている可能性がありますし、比較的シンプルに作ったものなのですが参考程度になれば幸いです