ASP.NET Core MVC 中重写DefaultControllerActivator实现属性注入

ASP.NET Core中注入方式默认为构造器注入,不支持属性注入以及其他更高级的注入.参考下面的说明:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#default-service-container-replacement

但是对于习惯了属性注入的开发人员来说比较头疼,为了实现自动注入,需要额外加一个构造函数,还需要把需要提供的服务一一对应,这种操作兼职逼死强迫症.当然官方也给出解决方案,就是使用第三方的容器,比如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来实现,这个静态类并没有注入到容器中,所以不能通过服务替换的方式解决.如果有其他方法解决,不妨在下方留言,谢谢.

猜你喜欢

转载自blog.csdn.net/u013710468/article/details/83588725