Microsoft.Extensions.DependencyInjection を使った DI の基本

Dependency Injection は Autofac を使っていたのだけど、.NET Core を使い始めると、Microsoft製のMicrosoft.Extensions.DependencyInjection がシンプルでよいという話を同僚から聞いたので、その基本をまとめておきたい。

# Microsoft.Extensions.DependencyInjection

公式ページはこちら

* [ASP.NET Core での依存関係の挿入](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1)
* [aspnet/DependencyInjection](https://github.com/aspnet/DependencyInjection)

実際に私はASP.NET というより、Azure Functions で使うことを想定していますので生の基本的な挙動を理解したいと思っています。

# オブジェクトの注入

次のようなクライアントとそのインターフェイスがあるとします。

```csharp
    public interface ISomeClient
    {
        string Id { get; set; }
    }

    public class SomeClient : ISomeClient
    {
        public string Id {get; set;}
        public SomeClient()
        {
            this.Id = Guid.NewGuid().ToString();
        }
        public SomeOption SomeOptions {get; set;}
    }
```

## 依存性の注入

DI コンテナに`ISomeClient` インターフェイスをキーに、`SomeClient` のインスタンスを返却するには次の通り。DIの情報が格納されるコンテナは[ServiceCollection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection?view=aspnetcore-2.1)クラスです。これが主要なパーツになります。そこに登録していきます。ここでは、`Singleton` というスコープで登録していますが、スコープに関しては次に説明します。`AddSingleton<ISomeClient, SomeClient>()` によって、`ISomeClient` でインスタンスを要求されたら、`SomeClinet` が返却されるという作りです。

```csharp
            var services = new ServiceCollection();
            services.AddSingleton<ISomeClient, SomeClient>();
```

利用する側は次のとおりです。`BuildServiceProvider()` メソッドを実行して`IServiceProvider` を獲得し、`GetRequiredService` もしくは、`GetService`メソッドを使って登録したインスタンスを獲得します。`GetRequiredService` の場合、もしインスタンスがなければ Exception がスローされ、`GetService` の場合は、nullが返ってきます。

```csharp
var provider = services.BuildServiceProvider();
var someClient1 = provider.GetRequiredService<ISomeClient>();
Console.WriteLine($"SomeClient1(Singleton): Type: {someClient1.GetType()} Id: {someClient1.Id}"); 
```

## 実行結果

とても簡単でした。

```
SomeClient1(Singleton): Type: DIFundamental.SomeClient Id: b248f5ca-21d5-4106-a2f6-6940bed988a4
```

## スコープ

依存性を注入する場合、３つのスコープを使うことができます。

* AddSingleton<Interface, Implementation>(...): シングルトンのスコープつまり、インスタンスは１回だけ生成される
* AddScoped<Interface, Implementation>(...): １回のリクエストに対して、インスタンスが１回生成される
* AddTransient<Interface, Implementation>(...): 毎回新しいインスタンスが生成される

上記のことをテストしてみましょう。OtherClient の方は、SomeClient と全く同じつくりにしています。ちなみにOtherClient の方は書き方を変えていますが、意味は同じで、このように、返却したいインスタンスを作るファクトリの関数を持たせることもできます。

```csharp
            // Basic Registration
            services.AddSingleton<ISomeClient, SomeClient>();

            // Registration with Function
            services.AddTransient<IOtherClient>(client =>
            {
                return new OtherClient();
            });
```

呼び出してみましょう。

```csharp
            // Singleton
            var someClient1 = provider.GetRequiredService<ISomeClient>();
            Console.WriteLine($"SomeClient1(Singleton): Type: {someClient1.GetType()} Id: {someClient1.Id}");            
            var someClient2 = provider.GetRequiredService<ISomeClient>();
            Console.WriteLine($"SomeClient2(Singleton): Type: {someClient2.GetType()} Id: {someClient2.Id}");
            var someClient3 = (ISomeClient)provider.GetRequiredService(typeof(ISomeClient));
            Console.WriteLine($"SomeClient3(Singleton): Type: {someClient3.GetType()} Id: {someClient3.Id}");
            // Transient
            var otherClient1 = provider.GetService<IOtherClient>();
            Console.WriteLine($"OtherClient1(Transient): Type: {otherClient1.GetType()} Id: {otherClient1.Id}");
            var otherClient2 = provider.GetService<IOtherClient>();
            Console.WriteLine($"OtherClient2(Transient): Type: {otherClient2.GetType()} Id: {otherClient2.Id}");
```

### 結果

Scoped は ASP.NET がないと検証が厳しいので省いて、今回は２つに絞ってやっててみました。Singleton の場合は、インスタンスが一つですね。

```
SomeClient1(Singleton): Type: DIFundamental.SomeClient Id: b248f5ca-21d5-4106-a2f6-6940bed988a4
SomeClient2(Singleton): Type: DIFundamental.SomeClient Id: b248f5ca-21d5-4106-a2f6-6940bed988a4
SomeClient3(Singleton): Type: DIFundamental.SomeClient Id: b248f5ca-21d5-4106-a2f6-6940bed988a4
OtherClient1(Transient): Type: DIFundamental.OtherClient Id: e0bd12f1-a8c7-4438-9c87-b3af630c14f1
OtherClient2(Transient): Type: DIFundamental.OtherClient Id: 967d2af8-7b63-46f9-8237-a301838fbffc
```

ちなみに、スコープの他に、`TryAdd...` 系のメソッドも用意されていて、もし、すでに登録されていたら登録をスキップ的な使い方をするようです（試してませんが）

# Config

実際にDI のコードを書いていると、ある特定の時に、コンフィグレーションオプションを使いたくなる時があります。そのようなときのための機構も用意されています。　ご覧の通り、登録する時に使うオプションをコンフィグできて、`BuildServiceProvider()`が実行される前にコンフィグを注入しています。

```csharp
            // Register Option value object
            services.Configure<SomeOption>(o => new SomeOption
            {
                Value1 = "Hello",
                Value2 = "World"
            });
            // Options
            services.AddSingleton<SomeClient>(serviceProvider =>
            {
                var options = serviceProvider.GetRequiredService<IOptions<SomeOption>>();
                var client =  new SomeClient();
                client.SomeOptions = options.Value;
                return client;
            });
```

# コンストラクターインジェクション

複数のクラスがあって、それを組み合わせてインジェクションするのも簡単です。例えば、SomeClient をもった SomeService をインジェクションしたい場合、コンストラクターインジェクションは何も意識せず実行できます。

```csharp
    public interface ISomeService
    {
        void Greeting();
    }

    public class SomeService : ISomeService
    {
        private ISomeClient client;
        public SomeService (ISomeClient client)
        {
            this.client = client;
        }
        public void Greeting()
        {
            Console.WriteLine($"Type: {this.client.GetType()} Id: {this.client.Id}");
        }
    }
```

上記のようにコンストラクタにISomeClient をしておいて普通に登録します。すでに、ISomeClientが登録ずみならこれでOK. 

```csharp
            services.AddSingleton<ISomeService, SomeService>();
```

名前の解決

```csharp
            var service = provider.GetRequiredService<ISomeService>();
            Console.WriteLine($"SomeService Greeting calling...");
            service.Greeting();   
```

結果

```
SomeService Greeting calling...
Type: DIFundamental.SomeClient Id: b248f5ca-21d5-4106-a2f6-6940bed988a4

```

# ServiceCollection の拡張

次のような形で自分独自のメソッドを定義したい場合はServiceCollection の拡張メソッドを使います。ちょっとかっこいい！特に、複雑なインジェクションをしたい場合、それを隠蔽するために、特別なメソッドを書いたりすると利用するほうが便利です。

```csharp
services.AddSpecialClient();
```

拡張メソッドを定義しましょう。今回は単にSingletonにデリゲートしているだけです。

```csharp
    public static class ServiceCollectionExteions
    {
        public static void AddSpecialClient(this IServiceCollection services)
        {
            services.AddSingleton<ISpecialClient, SpecialClient>();
        }
    }
```

# まとめ

基本的な `ServiceCollection` の使い方は大体カバーできているのではないでしょうか？ASP.NET もDIを基本的に使うようになっているので、テスタビリティの向上が期待できます。（本当は Azure Functions もそうしてよと思いますw）ちなみに、このライブラリを拡張して、もっと便利にインジェクションできるようにしたライブラリもあります。

* [StructureMap](http://structuremap.github.io/documentation/)

というものです。海外のイケメン系の人はだいたい次にこれに流れるっぽいですが、次の機会に。また、英語ですが、ちょっと凝ったユースケースに出会ったので、そのやり方もブログに書いておきました。

* [ASP.NET Core での依存関係の挿入](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1)
* [aspnet/DependencyInjection](https://github.com/aspnet/DependencyInjection)
* [ServiceCollection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollection?view=aspnetcore-2.1)
* [ServiceCollection DI with Optional Settings](https://medium.com/@tsuyoshiushio/servicecollection-di-with-optional-settings-fc4ee46984d1)