ASP.NET MVC で DI したくて SimpleInjector でサービス一括登録した

## やったこと
ASP.NET MVCでロジックや共通部品をサービスとしてDIするときに、基本的にはサービスと実装が同名で1対1対応するので、それらをいちいちRegisterしなくていいようにした。ドキュメントに載ってはいるが一応メモ。

## バージョン

.NET Framework 4.8
Microsoft.AspNet.Mvc 5.2.7
[NuGet Gallery | SimpleInjector.Integration.Web.Mvc 5.0.0](https://www.nuget.org/packages/SimpleInjector.Integration.Web.Mvc/5.0.0?_src=template) とその依存関係

## コード

```csharp:Global.asax
using DiAndAop.Services;
using SimpleInjector;
using SimpleInjector.Integration.Web;
using SimpleInjector.Integration.Web.Mvc;
using System;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace DiAndAop
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            RegisterDiContainer();

            // ...
        }

        /// <summary>
        /// DIコンテナを設定する。
        /// </summary>
        private static void RegisterDiContainer()
        {
            // Create the container as usual.
            Container container = new Container();
            // デフォルトの生存期間を設定
            WebRequestLifestyle webRequestLifestyle = new WebRequestLifestyle();
            container.Options.DefaultScopedLifestyle = webRequestLifestyle;
            // 一括登録しない実装を指定
            Type[] batchRegExcludeType = { typeof(Hello) };
            BatchReg(container, batchRegExcludeType, webRequestLifestyle);

            // 一括登録以外のサービスを登録
            container.Register<IHello, Hello>(Lifestyle.Scoped);

            // This is an extension method from the integration package.
            container.RegisterMvcControllers(Assembly.GetExecutingAssembly());

            container.Verify();

            DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
        }


        /// <summary>
        /// DIコンテナにサービスを一括登録する。実装とサービスが(頭の"I"を除いて)同名のクラスを対象とする。
        /// サービスと同名のもの以外に実装が存在する場合も、同名の実装が対象となる。
        /// </summary>
        /// <param name="container">DIコンテナ</param>
        /// <param name="batchRegExcludeType">除外する型</param>
        /// <param name="lifestyle">生成・破棄のオプション</param>

        private static void BatchReg(Container container, Type[] batchRegExcludeType, Lifestyle lifestyle)
        {
            Lifestyle _lifestyle = lifestyle is null ? Lifestyle.Transient : lifestyle;

            // MvcApplication の部分は、サービスとクライアントがいるアセンブリを指定できればなんでもよい
            Assembly assembly = typeof(MvcApplication).Assembly;
            // 一括登録対象を抽出
            var registrations = assembly.GetExportedTypes()
                .AsParallel()
                .Where(type => type.Namespace.StartsWith("DiAndAop.Services"))
                .Where(impl => !batchRegExcludeType.Contains(impl))
                .Select(impl => impl.GetInterfaces()
                    .Where(service => service.Name == "I" + impl.Name)
                    .Select(service => (impl, service))
                    .SingleOrDefault())
                .Where(pair => !pair.Equals(default));
            // 一括登録実施
            foreach (var reg in registrations)
            {
                container.Register(reg.service, reg.impl, _lifestyle);
            }
        }
    }
}

```

- 例では `DiAndAop.Services` 名前空間にサービスがある想定となっている(`Where`の中)ので、実態に合わせて書き換えること。
- 気分で `AsParallel` を使っており、対象が少ないとかえって遅いかもしれない。
- サービスと同名だが他の実装を使いたい場合や、他とは異なる生存期間を設定したい場合など、一括登録の対象から除外したい場合は `batchRegExcludeType` に型を羅列すればいい。

## リンク

- [ASP.NET MVC Integration Guide — Simple Injector 5 documentation](https://docs.simpleinjector.org/en/latest/mvcintegration.html#)  
  MVC用のおまじないが必要のようでこの説明に従った。
- [Advanced Scenarios — Simple Injector 5 documentation](https://docs.simpleinjector.org/en/latest/advanced.html#batch-registration-auto-registration)  
  一括登録の説明。難しいAPI用意するよりLINQでやったほうが読み書きしやすいし柔軟じゃん、とのこと。たしかに。