- 僅針對Controller的屬性注入;
- 使用默認容器,不依賴第三方庫;
故事背景
??閑來無事給專案做優化,發現大多數Controller里面都會用到Logger和AutoMapper,每個Controller都建構式注入,感覺重復勞動太多了,ASP.NET Core默認容器也并不支持屬性注入,寫到基類里面通過構造注入ServiceProvider進行獲取?這樣每次都要傳遞ServiceProvider,也并不方便,嗯,,,有沒有辦法使用默認容器實作Controller的屬性注入呢,本著有問題先搜一搜的態度,找到了一篇相關文章,
??文中使用替換IControllerActivator,并將Controller手動添加到DI容器(可以直接使用AddControllersAsServices拓展方法)的方式實作了屬性注入,也就是說必須使用從DI獲取Controller的方式,才能使用這種屬性注入的方式,不過之前看過一篇文章(找不到了,,,)里面的回復,大意為ASP.NET Core默認不使用DI獲取Controller,是因為DI容器構建完成后就不能變更了,但是Controller是可能有動態加載的需求的,嗯,,,本著最大兼容性的考慮,看看有沒有不干涉Controller創建方式也能實作這個功能的辦法,
想了個邏輯
??首先確認目的:在不改變ControllerActivator功能的前提下,為其生成的Controller設定屬性,具體的邏輯就有點繞了:我們需要實作一個設定Controller屬性的IControllerActivator(一個靜態代理類),并替換掉DI容器中的IControllerActivator宣告,但仍然需要DI容器構建之前已宣告的ControllerActivator(因為我們不知道具體如何構建),并且在我們實作的IControllerActivator中訪問(作為被代理的物件),
針對每個邏輯:
- 實作一個設定Controller屬性的
IControllerActivator靜態代理:這個很好寫; - 用靜態代理類替換掉DI容器中的
IControllerActivator宣告:這個也很簡單; - DI容器保留之前已宣告的
ControllerActivator:這個可以直接將IControllerActivator宣告中的實作型別作為服務添加到ServiceCollection中; - 在靜態代理類中訪問上一步添加到
ServiceCollection的IControllerActivator具體實作:這個問題有點小麻煩,因為靜態代理類和原始的IControllerActivator實作類都需要使用DI容器構建,那么靜態代理類只能通過建構式注入IControllerActivator實作類,但是我們沒辦法在寫代碼的時候就確定要注入的型別,也不能直接依賴IControllerActivator(自己依賴自己了),想來想去,,,嗯,,,我們可以使用泛型實作代理類的邏輯,并在添加服務描述時動態生成一個泛型型別,
寫代碼
首先需要一個ASP.NET Core專案,
關鍵代碼
嗯,,屬性注入的關鍵點,只要有ServiceProvider,其它的就好說了,
- 那就先寫一個
IServiceProviderSetter放在這里
using System;
public interface IServiceProviderSetter
{
void SetServiceProvider(IServiceProvider serviceProvider);
}
- 實作泛型靜態代理
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
public class ControllerActivatorProxy<TControllerActivator> : IControllerActivator where TControllerActivator : IControllerActivator
{
private readonly TControllerActivator _originalControllerActivator;
public ControllerActivatorProxy(TControllerActivator originalControllerActivator)
{
//參考原始ControllerActivator
_originalControllerActivator = originalControllerActivator;
}
public object Create(ControllerContext context)
{
//使用原始ControllerActivator創建controller
var controller = _originalControllerActivator.Create(context);
if (controller is IServiceProviderSetter serviceProviderSetter)
{
//具體要干什么由controller內部決定
serviceProviderSetter.SetServiceProvider(context.HttpContext.RequestServices);
}
return controller;
}
public void Release(ControllerContext context, object controller)
{
_originalControllerActivator.Release(context, controller);
}
}
- 替換服務描述,保留之前已宣告的
ControllerActivator,為了方便,寫個拓展方法吧,
using System.Linq;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
public static class AutowiredControllerServiceProviderExtensions
{
public static void AutowiredControllerServiceProvider(this IMvcBuilder mvcBuilder)
{
var services = mvcBuilder.Services;
//查找原始服務描述
var serviceDescriptor = services.Where(m => m.ServiceType == typeof(IControllerActivator)).First();
var existedServiceType = serviceDescriptor.ImplementationType;
//動態創建代理型別
var controllerActivatorType = typeof(ControllerActivatorProxy<>).MakeGenericType(existedServiceType);
//將原始實作直接添加到services中
services.Add(ServiceDescriptor.Describe(existedServiceType, existedServiceType, serviceDescriptor.Lifetime));
//替換IControllerActivator服務為使用動態構建的代理型別
services.Replace(ServiceDescriptor.Describe(typeof(IControllerActivator), controllerActivatorType, serviceDescriptor.Lifetime));
}
}
嗯,,這好像有點有趣的地方,,
關鍵代碼寫完了,接下來是使用
- 建立一個
Controller基類并應用屬性
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
[ApiController]
[Route("api/[controller]")]
public class TestBaseController : ControllerBase, IServiceProviderSetter
{
//logger只是個示例,僅在使用時才創建,實際還可以直接獲取,或者使用Lazy<T>等各種操作,其它屬性同理,
private ILogger _logger;
private IServiceProvider _serviceProvider;
protected ILogger Logger
{
get
{
if (_logger != null)
{
return _logger;
}
_logger = _serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(GetType());
return _logger;
}
}
[NonAction]
public void SetServiceProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}
- 在
Startup中配置使用
public void ConfigureServices(IServiceCollection services)
{
//使用默認Controller生成方式
services.AddControllers().AutowiredControllerServiceProvider();
//使用DI生成Controller方式
//services.AddControllers().AddControllersAsServices().AutowiredControllerServiceProvider();
}
- 示例
Controller,這里就直接使用默認生成的Controller示范了
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
public class WeatherForecastController : TestBaseController
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
Logger.LogInformation("Start");
var rng = new Random();
var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
Logger.LogInformation("End");
return result;
}
}
好了,可以看效果了

僅僅是個思路,僅供參考,代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/256581.html
標籤:.NET技术
上一篇:C#之rpc很火么?
