ASP.NetでDI用にサービスを自動登録させる

久しぶりに書きます。
基本は備忘録ですが、誰かの役に立てれば幸いです。

今回はASP.Net5でDI用にサービス登録する部分をStartup.csにずらずら書かなくてもいいようにしてみました。
きっと、すでにほかでもしていると思いますが、自身のメモとして書いておきます。

# 基本方針
　今回は以下の様に考えてみました。
1. 基底クラスを作成してその派生クラスを全て自動的に登録するようにする。
2. services.AddScoped<クラス>の登録ができる。
3. services.AddScoped<インタフェース,クラス>はインタフェースが１つの場合のみ対応。複数のインタフェースがある場合はどれになるかわからないので自動登録できない。（なんか方法があるかもしれないけど）

#基底クラス
　DIでサービスに登録したい場合にこの基底クラスを継承して作ると自動的に登録するようになります。
　この基底クラスにはインスタンスメソッドはなく、派生クラスを全て探してきて登録する為のクラスメソッドのみを記述しています。このメソッドを「Startup.cs」に入れておけば、このクラスを継承しているクラスをDI用として登録します。

```cs:DIBase.cs
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Reflection;

namespace DIAutoTest
{
    /// <summary>
    /// サービスの基本クラス。
    /// このクラス自身は継承先で使うメソッドは持たないが、Startup.csでDIの設定を自動で行うようになる。（DIのサービス登録が不要になる）
    /// </summary>
    public class DIBase
    {
        /// <summary>
        /// このクラスの派生クラスを全てDI用のサービスとして登録する
        /// </summary>
        /// <param name="services">サービスリスト</param>
        static public void RegistAllServices(IServiceCollection services)
        {
            // 利用するサービスリストの「AddScoped」メソッドの引数の型を取得（services.AddScoped<T>()の引数はservicesになっている）
            var types = new Type[] { typeof(IServiceCollection) };

            // DIBaseクラスの全ての派生クラスを取得
            var serviceTypes = Assembly.GetAssembly(typeof(DIBase)).GetTypes().Where(t =>
            {
                return t.IsSubclassOf(typeof(DIBase)) && !t.IsAbstract;
            });

            // すべての派生クラスを処理
            foreach (var serviceType in serviceTypes)
            {
                // クラスのインタエースリストを取得
                var interfaces = serviceType.GetInterfaces();

                // インタフェースが２個以上ある場合は、判定できないのでエラー
                if (interfaces.Length > 1)
                {
                    throw new Exception("インタフェースを2つ以上定義しているクラスを自動的にDIに登録することはできません。");
                }

                if (interfaces.Length == 0)
                {
                    // インタフェースが無い場合はジェネリックの型が１つしかないメソッドを取得
                    var method = typeof(ServiceCollectionServiceExtensions).GetMethod("AddScoped", 1, types);

                    // 「AddScoped」メソッドのジェネリックメソッドを作成
                    var genericMethod = method.MakeGenericMethod(new Type[] { serviceType });

                    // 「AddScoped」を実行
                    genericMethod.Invoke(null, new object[] { services });
                }
                else
                {
                    var method = typeof(ServiceCollectionServiceExtensions).GetMethod("AddScoped", 2, types);

                    // 「AddScoped」メソッドのジェネリックメソッドを作成
                    var genericMethod= method.MakeGenericMethod(new Type[] { interfaces[0], serviceType });

                    // 「AddScoped」を実行
                    genericMethod.Invoke(null, new object[] { services });
                }
            }
        }
    }
}
```

# Startup.csに記述
　Startup.csには上記で作ったstaticメソッドを呼び出すだけ。

```cs:Startup.cs
        public void ConfigureServices(IServiceCollection services)
        {
            ....

            // DIBaseクラスの派生クラスを全てDIに登録する
            DIBase.RegistAllServices(services);

            ....
        }
```

これでいちいち登録を追加する必要がなさそう。

## 登録したいクラスが他のクラスを継承している場合
　もともとクラスを継承しているクラスの場合、多重継承ができないのでこの方法では設定できません。ならばと、継承元のクラスに継承させて、このクラスからの継承を２段階以上にした場合、対象のクラスと元のクラスの両方が登録されるようです。ただし、クラスでの利用なら問題ないですが、インタフェースで利用する場合、後から登録されたものが有効になっていました。どちらが後になるかよくわからないので使わない方がいいでしょう。
　まあ、そんなややこしいものは単独でこの方法の例外として単独で登録すればいいでしょう。

## AddSingletonやAddTransient
　この２つのパターンはやっていません。
　同じようにすればできると思います。