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来实现,这个静态类并没有注入到容器中,所以不能通过服务替换的方式解决.如果有其他方法解决,不妨在下方留言,谢谢.