ASP.NET Core MVC 中重寫DefaultControllerActivator實現屬性注入
阿新 • • 發佈:2018-12-18
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來實現,這個靜態類並沒有注入到容器中,所以不能通過服務替換的方式解決.如果有其他方法解決,不妨在下方留言,謝謝.