リフレクションを用いたサービスの自動登録
.NET 6 以降の最小ホスティングモデルでは、WebApplication.CreateBuilder から得られる IServiceCollection インスタンスに対して標準の依存性注入コンテナを構築します。大量のインターフェースと実装クラスを手動で AddSingleton や AddScoped で登録する代わりに、リフレクションを用いて命名規則を認識させ、一括バインドする拡張メソッドを実装することで開発効率を向上できます。
public static class ServiceRegistryExtensions
{
public static IServiceCollection AutoRegisterServices(this IServiceCollection services, params Assembly[] assemblies)
{
foreach (var assembly in assemblies)
{
var candidateClasses = assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && t.Name.EndsWith("Service"));
foreach (var concreteType in candidateClasses)
{
// 接頭辞 'I' に一致するインタフェースを検出
var targetType = concreteType.GetInterfaces()
.FirstOrDefault(i => i.Name == $"I{concreteType.Name}");
if (targetType != null && !services.Any(s => s.ServiceType == targetType))
{
services.AddSingleton(targetType, concreteType);
}
}
}
return services;
}
}
跨アセンブリ時の型解決設定
別のプロジェクトや外部 DLL から型を取得する場合、Type.GetType(string) が正しく実行されるためには、ビルド出力時に生成されるアセンブリ名の情報が正確である必要があります。<Project>.csproj ファイル内で以下のプロパティを明示的に定義すると、文字列ベースの型パス指定が安定します。
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Tellyes.Unicorn.CaseService</AssemblyName>
<RootNamespace>Tellyes.Unicorn.CaseService</RootNamespace>
</PropertyGroup>
コンストラクタインジェクションの設計原則
DI コンテナを通じて依存関係を解決する際、クラスメンバやコンストラクタ引数には常に抽象インタフェース型を宣言してください。具体クラスを直接インスタンス化したりメンバ変数に保持したりすると、ユニットテスト時のモック置換やライフサイクル管理の制約が生じます。例えば IUserValidator 型のフィールドを受け取り、内部ロジックはすべてインタフェース経由でコールすることで、疎結合なアーキテクチャを維持できます。
プログラム初期化時の適用方法
最新テンプレートの Program.cs では、サービス登録フェーズを以下のように記述します。既定のアセンブリに加えて、Assembly.Load() で動的に外部ライブラリを読み込むことも可能です。
var builder = WebApplication.CreateBuilder(args);
// 既定のアセンブリおよび指定した外部 DLL を対象に自動登録を実行
builder.Services.AutoRegisterServices(
typeof(IProductService).Assembly,
Assembly.Load("CrossCutting.LoggingModule")
);
var app = builder.Build();
app.Run();