2021年2月4日星期四

ASP.NET Core默认容器实现Controller的属性注入

  • 仅针对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中;
  • 在静态代理类中访问上一步添加到ServiceCollectionIControllerActivator具体实现:这个问题有点小麻烦。因为静态代理类和原始的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; }}

好了,可以看效果了

仅仅是个思路,仅供参考。代码









原文转载:http://www.shaoqun.com/a/525604.html

跨境电商:https://www.ikjzd.com/

extra:https://www.ikjzd.com/w/1736

韩蓬:https://www.ikjzd.com/w/1635


仅针对Controller的属性注入;使用默认容器,不依赖第三方库;故事背景  闲来无事给项目做优化,发现大多数Controller里面都会用到Logger和AutoMapper,每个Controller都构造函数注入,感觉重复劳动太多了,ASP.NETCore默认容器也并不支持属性注入。写到基类里面通过构造注入ServiceProvider进行获取?这样每次都要传递ServiceProvider
败欧洲网站:败欧洲网站
indiegogo:indiegogo
旺季过后,卖家需要注意哪些问题?:旺季过后,卖家需要注意哪些问题?
亚马逊选品攻略,三大案例深度解析亚马逊选品规则:亚马逊选品攻略,三大案例深度解析亚马逊选品规则
首发 | Google Free Listings已扩展至全球 48 个国家!:首发 | Google Free Listings已扩展至全球 48 个国家!