public void PropertyActivate(object service, IServiceProvider provider) { var serviceType = service.GetType(); var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_")); foreach (PropertyInfo property in properties) { var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>(); if (autowiredAttr != null) { //从DI容器获取实例 var innerService = provider.GetService(property.PropertyType); if (innerService != null) { //递归解决服务嵌套问题 PropertyActivate(innerService, provider); //属性赋值 property.SetValue(service, innerService); } } } }
然后在控制器中激活属性:
[Autowired] public IAccountService _accountService { get; set; } public LoginController(IHttpContextAccessor httpContextAccessor) { var pro = new AutowiredServiceProvider(); pro.PropertyActivate(this, httpContextAccessor.HttpContext.RequestServices); }
这样子下来,虽然功能实现了,但是里面存着几个问题。第一个是由于控制器的构造函数中不能直接使用ControllerBase的HttpContext属性,所以必须要通过注入IHttpContextAccessor对象来获取,貌似问题又回到原点。第二个是每个构造函数中都要写这么一堆代码,不能忍。于是想有没有办法在控制器被激活的时候做一些操作?没考虑引入AOP框架,感觉为了这一个功能引入AOP有点重。经过网上搜索,发现Asp.Net Core框架激活控制器是通过IControllerActivator接口实现的,它的默认实现是DefaultControllerActivator(https://github.com/aspnet/AspNetCore/blob/master/src/Mvc/Mvc.Core/src/Controllers/DefaultControllerActivator.cs):
/// <inheritdoc /> public object Create(ControllerContext controllerContext) { if (controllerContext == null) { throw new ArgumentNullException(nameof(controllerContext)); } if (controllerContext.ActionDescriptor == null) { throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( nameof(ControllerContext.ActionDescriptor), nameof(ControllerContext))); } var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo; if (controllerTypeInfo == null) { throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( nameof(controllerContext.ActionDescriptor.ControllerTypeInfo), nameof(ControllerContext.ActionDescriptor))); } var serviceProvider = controllerContext.HttpContext.RequestServices; return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType()); }
这样一来,我自己实现一个Controller激活器不就可以接管控制器激活了,于是有如下这个类:
public class HosControllerActivator : IControllerActivator { public object Create(ControllerContext actionContext) { var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); var instance = actionContext.HttpContext.RequestServices.GetRequiredService(controllerType); PropertyActivate(instance, actionContext.HttpContext.RequestServices); return instance; } public virtual void Release(ControllerContext context, object controller) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (controller == null) { throw new ArgumentNullException(nameof(controller)); } if (controller is IDisposable disposable) { disposable.Dispose(); } } private void PropertyActivate(object service, IServiceProvider provider) { var serviceType = service.GetType(); var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_")); foreach (PropertyInfo property in properties) { var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>(); if (autowiredAttr != null) { //从DI容器获取实例 var innerService = provider.GetService(property.PropertyType); if (innerService != null) { //递归解决服务嵌套问题 PropertyActivate(innerService, provider); //属性赋值 property.SetValue(service, innerService); } } } } }
需要注意的是,DefaultControllerActivator中的控制器实例是从TypeActivatorCache获取的,而自己的激活器是从DI获取的,所以必须额外把系统所有控制器注册到DI中,封装成如下的扩展方法:
/// <summary> /// 自定义控制器激活,并手动注册所有控制器 /// </summary> /// <param></param> /// <param></param> public static void AddHosControllers(this IServiceCollection services, object obj) { services.Replace(ServiceDescriptor.Transient<IControllerActivator, HosControllerActivator>()); var assembly = obj.GetType().GetTypeInfo().Assembly; var manager = new ApplicationPartManager(); manager.ApplicationParts.Add(new AssemblyPart(assembly)); manager.FeatureProviders.Add(new ControllerFeatureProvider()); var feature = new ControllerFeature(); manager.PopulateFeature(feature); feature.Controllers.Select(ti => ti.AsType()).ToList().ForEach(t => { services.AddTransient(t); }); }
在ConfigureServices中调用:
services.AddHosControllers(this);
到此,大功告成!可以愉快的继续CRUD了。
结尾