1. 程式人生 > >ASP.NET Core MVC 中重寫DefaultControllerActivator實現屬性注入

ASP.NET Core MVC 中重寫DefaultControllerActivator實現屬性注入

ASP.NET Core中注入方式預設為構造器注入,不支援屬性注入以及其他更高階的注入.參考下面的說明:

但是對於習慣了屬性注入的開發人員來說比較頭疼,為了實現自動注入,需要額外加一個建構函式,還需要把需要提供的服務一一對應,這種操作兼職逼死強迫症.當然官方也給出解決方案,就是使用第三方的容器,比如Autofac,Unity.但是為了一個屬性注入而拋棄內建的容器引入第三方容器,感覺也得不償失.所以如果能在內建容器的基礎上突破構造器的限制,則是兩全其美.

屬性注入就細節也有兩種方式:1. 通過名稱,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. 實現IControllerActivator介面

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<string, IEnumerable<PropertyInfo>> _publicPropertyCache = new Dictionary<string, IEnumerable<PropertyInfo>>();

        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<object>(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<RequiredService>() != 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. 替換DefaultControllerActivator

在Startup類中找出DefaultControllerActivator,Remove,並注入AutoBindProControllerActivator.

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<IControllerActivator, AutoBindProControllerActivator >();
            }

        }

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. 問題

因為我們的切入點是Activator,所以只能解決控制器內的屬性注入,服務內部的注入還不能解決.目前服務的例項化是通過一個靜態類ActivatorUtilities來實現,這個靜態類並沒有注入到容器中,所以不能通過服務替換的方式解決.如果有其他方法解決,不妨在下方留言,謝謝.