• 仅针对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;
}
}

好了,可以看效果了

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

ASP.NET Core默认容器实现Controller的属性注入的更多相关文章

  1. ASP.NET Core 奇淫技巧之伪属性注入

    一.前言 开局先唠嗑一下,许久未曾更新博客,一直在调整自己的状态,去年是我的本命年,或许是应验了本命年的多灾多难,过得十分不顺,不论是生活上还是工作上.还好当我度过了所谓的本命年后,许多事情都在慢慢变 ...

  2. 聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer

    跨平台是ASP.NET Core一个显著的特性,而KestrelServer是目前微软推出了唯一一个能够真正跨平台的Server.KestrelServer利用一个名为KestrelEngine的网络 ...

  3. ASP.NET Core默认注入方式下如何注入多个实现(多种方式) - sky 胡萝卜星星 - CSDN博客

    原文:ASP.NET Core默认注入方式下如何注入多个实现(多种方式) - sky 胡萝卜星星 - CSDN博客 版权声明:本文为starfd原创文章,转载请标明出处. https://blog.c ...

  4. 避免在ASP.NET Core 3.0中为启动类注入服务

    本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...

  5. [ASP.NET Core开发实战]基础篇02 依赖注入

    ASP.NET Core的底层机制之一是依赖注入(DI)设计模式,因此要好好掌握依赖注入的用法. 什么是依赖注入 我们看一下下面的例子: public class MyDependency { pub ...

  6. Asp.Net Core 进阶(三)—— IServiceCollection依赖注入容器和使用Autofac替换它

    Asp.Net Core 提供了默认的依赖注入容器 IServiceCollection,它是一个轻量级的依赖注入容器,所以功能不多,只是提供了基础的一些功能,要实现AOP就有点麻烦,因此在实际工作当 ...

  7. ASP.NET Core MVC 中的 [Controller] 和 [NonController]

    前言 我们知道,在 MVC 应用程序中,有一部分约定的内容.其中关于 Controller 的约定是这样的. 每个 Controller 类的名字以 Controller 结尾,并且放置在 Contr ...

  8. 在ASP.NET Core MVC中子类Controller拦截器要先于父类Controller拦截器执行

    我们知道在ASP.NET Core MVC中Controller上的Filter拦截器是有执行顺序的,那么如果我们在有继承关系的两个Controller类上,声明同一种类型的Filter拦截器,那么是 ...

  9. ASP.NET Core :容器注入(二):生命周期作用域与对象释放

    //瞬时生命周期 ServiceCollection services = new ServiceCollection(); services.AddTransient<TestServiceI ...

随机推荐

  1. hdfs读写删除过程解析

    一.hdfs文件读取过程 hdfs有一个FileSystem实例,客户端通过调用这个实例的open()方法就可以打开系统中希望读取的文件,hdfs通过rpc协议调用Nadmenode获取block的位 ...

  2. 利用python 5分钟制作一款小游戏

    1.安装pygame 在命令行cmd中输入:pip install pygame ( 注:如果安装不成功,需要输入:python -m pip install --user --upgrade pip ...

  3. Java8接口的默认方法

    项目实战 实现上图接口的实现类有很多,其中有些实现类已经在生成环境了,现在需要新增几个实现类,都需要有回调方法,所以在接口中添加了一个回调的默认方法,如果使用接口的普通方法就得改所有实现了接口的实现类 ...

  4. 数据仓库组件:HBase集群环境搭建和应用案例

    本文源码:GitHub || GitEE 一.Hbase简介 1.基础描述 Hadoop原生的特点是解决大规模数据的离线批量处理场景,HDFS具备强大存储能力,但是并没有提供很强的数据查询机制.HBa ...

  5. 人生苦短我用Python,本文助你快速入门

    目录 前言 Python基础 注释 变量 数据类型 浮点型 复数类型 字符串 布尔类型 类型转换 输入与输出 运算符 算术运算符 比较运算符 赋值运算符 逻辑运算符 if.while.for 容器 列 ...

  6. Java实现RS485串口通信

    前言 前段时间赶项目的过程中,遇到一个调用RS485串口通信的需求,赶完项目因为楼主处理私事,没来得及完成文章的更新,现在终于可以整理一下当时的demo,记录下来. 首先说一下大概需求:这个项目是机器 ...

  7. Python模块化编程与装饰器

    Python的模块化编程 我们首先以一个例子来介绍模块化编程的应用场景,有这样一个名为requirements.py的python3文件,其中两个函数的作用是分别以不同的顺序来打印一个字符串: # r ...

  8. Linux 入门教程:00 Background

    Linux 为何物? 就是一个操作系统. Linux 历史: 操作系统始于二十世纪五十年代,当时的操作系统能运行批处理程序.批处理程序不需要用户的交互,它从文件或者穿孔卡片读取数据,然后输出到另外一个 ...

  9. (十四)json、pickle与shelve模块

    任何语言,都有自己的数据类型,那么不同的语言怎么找到一个通用的标准? 比如,后端用Python写的,前端是js,那么后端如果传一个dic字典给前端,前端肯定不认. 所以就有了序列化这个概念. 什么是序 ...

  10. 在项目中应该使用Boolean还是使用boolean?

    起因 在公司看代码时,看到了使用Boolean对象来完成业务逻辑判断的操作.和我的习惯不一致,于是引起了一些反思. boolean和Boolean的差别咱就不说了,我们仅探讨使用boolean与Boo ...