ASP.NET Core MVCでDefaultControllerActivatvatorを書き換えて属性注入を実現


ASP.NET Coreにおける注入方式はデフォルトではコンストラクタ注入である、属性注入やその他のより高度な注入はサポートされていない.以下の説明を参照してください.
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#default-service-container-replacement
しかし、属性注入に慣れた開発者にとっては頭が痛く、自動注入を実現するためには構造関数を追加する必要があり、提供するサービスを一つ一つ対応する必要がある.もちろん、Autofac、Unityなどのサードパーティ製の容器を使用するソリューションも提供されています.しかし、一つの属性注入のために内蔵容器を捨てて第三者容器に導入することは、損をしないような気がする.したがって、内蔵容器に基づいてコンストラクタの制限を突破できれば、両方の美しさである.
属性注入には細部についても2つの方法がある:1.名前、2.通過特性どの属性が注入を必要とするか、どの属性が注入を必要としないかを制御するとともに、サービスが提供できない場合に異常注意を与えるために、第2の方法を選択する.

1.新しいプロパティRequiredServiceAttribute

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace OneSmart.Store.Admin.Web.Extensions
{
    [AttributeUsage(AttributeTargets.Property)]
    public class RequiredServiceAttribute : Attribute
    {
    }
}

2.IControllerActivatvatorインタフェースの実現

using System;
using System.Collections.Generic;
using System.Linq;
using System.Resources;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

namespace OneSmart.Store.Admin.Web.Extensions
{
    public class AutoBindProControllerActivator : IControllerActivator
    {
        private readonly ITypeActivatorCache _typeActivatorCache;
        private static IDictionary> _publicPropertyCache = new Dictionary>();

        public AutoBindProControllerActivator(ITypeActivatorCache typeActivatorCache)
        {
            if (typeActivatorCache == null)
            {
                throw new ArgumentNullException(nameof(typeActivatorCache));
            }

            _typeActivatorCache = typeActivatorCache;
        }

        public object Create(ControllerContext controllerContext)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException(nameof(controllerContext));
            }

            if (controllerContext.ActionDescriptor == null)
            {
                throw new ArgumentException(nameof(ControllerContext.ActionDescriptor));
            }

            var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo;

            if (controllerTypeInfo == null)
            {
                throw new ArgumentException(nameof(controllerContext.ActionDescriptor.ControllerTypeInfo));
            }

            var serviceProvider = controllerContext.HttpContext.RequestServices;
            var instance = _typeActivatorCache.CreateInstance(serviceProvider, controllerTypeInfo.AsType());
            if (instance != null)
            {
                if (!_publicPropertyCache.ContainsKey(controllerTypeInfo.FullName))
                {
                    var ps = controllerTypeInfo.GetProperties(BindingFlags.Public | BindingFlags.Instance).AsEnumerable();
                    ps = ps.Where(c => c.GetCustomAttribute() != null);
                    _publicPropertyCache[controllerTypeInfo.FullName] = ps;
                }

                var requireServices = _publicPropertyCache[controllerTypeInfo.FullName];
                foreach (var item in requireServices)
                {
                    var service = serviceProvider.GetService(item.PropertyType);
                    if (service == null)
                    {
                        throw new InvalidOperationException($"Unable to resolve service for type '{item.PropertyType.FullName}' while attempting to activate '{controllerTypeInfo.FullName}'");
                    }
                    item.SetValue(instance, service);
                }
            }
            return instance;
        }

        public void Release(ControllerContext context, object controller)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (controller == null)
            {
                throw new ArgumentNullException(nameof(controller));
            }

            var disposable = controller as IDisposable;
            if (disposable != null)
            {
                disposable.Dispose();
            }
        }
    }

}

3.DefaultControllerActivvatorの置き換え


StartupクラスでDefaultControllerActivatvator,Removeを見つけ、AutoBindProControllerActivatvatorに注入します.
public void ConfigureServices(IServiceCollection services)
        {
            

            services.AddMvc(options =>
            {
                options.Filters.Add(typeof(GlobalExceptionFilterAttribute));
            }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            //     AddMvc    DefaultActivator =
            var defaultActivator = services.FirstOrDefault(c => c.ServiceType == typeof(IControllerActivator));
            if (defaultActivator != null)
            {
                services.Remove(defaultActivator);
                services.AddSingleton();
            }

        }

4.使用

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using OneSmart.Core;
using OneSmart.Store.Admin.Web.Interfaces;
using OneSmart.Store.Admin.Web.Models;

namespace OneSmart.Store.Admin.Web.Controllers
{
    public class HomeController : Controller
    {
        [RequiredService]
        public IService MyService{get;set;}

        public IActionResult Index()
        {
            // MyService.dosomething...
            return View();
        }
    }
}

5.質問


我々の切り込み点はActivvatorであるため、コントローラ内の属性注入しか解決できず、サービス内部の注入はまだ解決できない.現在のサービスの実例化は、コンテナに注入する静的クラスActivatorUtilitiesによって実現されているため、サービス置換によって解決することはできない.他に解決策があれば、下にメッセージを残してください.ありがとうございます.