目次
C#のDIフレームワークは色々ありますが、現段階ではバックエンドでもクライアントでもMicrosoft.Extentions.DependencyInjectionライブラリを使うのが一番無難だと思います。他のDIフレームワークと比較して軽量で拡張性が高く、Microsoft公式がメンテナンスしているので信頼できるライブラリです。
依存性注入?DIって何?という話については解説している記事が多いので割愛。Microsoft.Extentions.DependencyInjectionライブラリに依存しているMicrosoft.Extensions.Loggingのコードを読んでいたら、センスのいい使い方をしていたので、本記事では同じMicrosoftExtentionsシリーズのコードを参考にしてMicrosoft.Extentions.DependencyInjectionライブラリによる依存性注入テクニックをまとます。
メソッドチェーンを使って依存性を構築する
多くのDIフレームワークは属性(データアノテーション)と呼ばれる方法でクラスの依存関係を構築しますが、Microsoft.Extentions.DependencyInjectionライブラリにはそれがありません。というのも属性を使うとライブラリ汚染度が高くなり、後で他のライブラリに移行することが難しくなるなど問題があるからです。そのため、DIに関するコードは1つのファイルにまとめるのが理想であり、下記のメソッドチェーンと呼ばれる方法で構築することを推奨します。
1 2 3 4 5 6 7 8 9 10 11 | public static class SampleServiceCollectionExtentions { public static IServiceCollection AddSample( this IServiceCollection services) { services .AddSingleton<SampleRepository>() .AddSingleton<SampleViewModel>() .AddSingleton<SampleView() ; } } |
メソッドチェーンは下記のように使うことが出来ます。名前の通りメソッドが鎖のよう連結するコードとなります。
1 2 3 4 5 | var _provider = new ServiceCollection() .AddOther() .AddSample() .AddOther() ; |
注入するサービス群を変更するテクニック
プラットフォーム、プロジェクトの種類、ビルド構成によって注入するサービスを変更したい場合があります。
下記のコードはHttpClientのサービス群を示すインターフェースを用意し、アプリケーションプロジェクト用とユニットテストプロジェクト用の2つの実装クラスを作る例です。
1 2 3 4 | public interface IHttpClientServices { void Configure(IServiceCollection services); } |
アプリケーションプロジェクト用のサービス群です。サーバとの通信が発生するクラスです。
1 2 3 4 5 6 7 8 | public class HttpClientServices : IHttpClientServices { public void Configure(IServiceCollection services) => services .AddSingleton<IAuthService, AuthService>() .AddSingleton<ITaskService, TaskService>() ; } |
ユニットテストプロジェクト用のサービス群。Moqフレームワークで作った場合の例です。
1 2 3 4 5 6 7 8 | public class MockHttpClientServices : IHttpClientServices { public void Configure(IServiceCollection services) => services .AddSingleton<IAuthService>(_ => new Mock<IAuthService>().Object) .AddSingleton<ITaskService>(_ => new Mock<ITaskService>().Object) ; } |
そして、IServiceCollectionの拡張メソッドを作ります。Activatorクラスで上記のクラスをインスタンスします。
1 2 3 4 5 6 7 8 | public static class HttpClientServicesExtentions { public static void AddHttpClient<TServices>( this IServiceCollection services) where TServices : IHttpClientServices { var instance = (TServices)Activator.CreateInstance( typeof (TServices)); instance.Configure(services); } } |
アプリケーションプロジェクトで使う場合
1 | new ServiceCollection().AddHttpClient<HttpClientServices>(); |
ユニットテストプロジェクトで使う場合
1 | new ServiceCollection().AddHttpClient<MockHttpClientServices>(); |
Builderパターンで実装
Builderインターフェースを用意する例もあったので簡単に説明。
1 2 3 4 | public interface ISampleBuilder { IServiceCollection Services { get; } } |
実装クラスはinternal修飾子を付けます。
1 2 3 4 5 6 7 8 9 | internal class SampleBuilder : ISampleBuilder { public SampleBuilder(IServiceCollection services) { Services = services ?? throw new ArgumentNullException(nameof(services)); } public IServiceCollection Services { get ; } } |
ISampleBuilderの拡張メソッドを作ります。
1 2 3 4 5 6 7 | public static class SampleBuilderServicesExtentions { public static void AddBuilder( this ISampleBuilder builder) { builder.Services.AddSingleton<ISampleService, SampleService>(); } } |
IServiceCollectionの拡張メソッドを作ります。
1 2 3 4 5 6 7 8 | public static class SampleServiceCollectionExtentions { public static IServiceCollection AddSample( this IServiceCollection services, Action<IHttpClientServicesBuilder> configure) { configure( new HttpSampleBuilder(services)); return services; } } |
こんな感じで使います。
1 | new ServiceCollection().AddSample(_ => _.AddBuilder()) |
さいごに
他にもProviderパターンやOptionパターンで依存性を注入するコードがMicrosoftExtentionsシリーズのコードにあったので追々、解読していみたいと思います。