一.前言

开局先唠嗑一下,许久未曾更新博客,一直在调整自己的状态,去年是我的本命年,或许是应验了本命年的多灾多难,过得十分不顺,不论是生活上还是工作上。还好当我度过了所谓的本命年后,许多事情都在慢慢变好,我将会开始恢复更新博客,争取恢复到以前的速度上(因为工作比较忙,所以这个过程可能需要一段时间)。

二.关于属性注入

说到属性注入,我们就不得不提一下 DI(Dependency Injection),即依赖注入,用过 ASP.NET Core 的同学相信对这个词不会陌生。ASP.NET Core 自带了一个IOC容器,且程序运行也是基于这个容器建立起来的,在 Startup 里的 ConfigureServices 方法就是向容器注册类型。最直白的讲,我们在 ASP.NET Core 中,想使用某个类型的时候可以不用自己去 new,可以由容器通过构造方法来注入具体的实现类型,而我们一般在构造方法上定义的依赖类型都是接口,而不是去依赖具体的实现,这里就体现了 SOLID 原则中的依赖倒置原则(DIP)。这也是IOC(Inversion of Control),即控制反转,不直接依赖具体实现,将依赖交给容器去控制。上述几者是具有一定的关联关系的,DIP 是一种软件设计原则,IOC 是 DIP 的具体实现方式,DI 是 IOC 的一种实现方式。

在依赖注入时,我们最常用的便是通过构造方法注入,还有另一种方式那便是属性注入

关于属性注入,如果在网上搜索,大部分内容都是不推荐使用,或者说慎重使用的,因为属性注入会造成类型的依赖关系隐藏,测试不友好等,我也同意这种说法,属性注入可以使用,但是要谨慎,不能盲目使用。我的原则:在封装框架(搭架子)时可以使用,但不能大范围使用,只有必须使用属性注入来达到效果的地方才会使用,用来提高使用框架时的编码效率,来达到一些便利,脱离框架层面,编写业务代码时,不得使用。

在 ASP.NET Core 中,自带的容器是不支持属性注入的,但是可以通过替换容器,如:Autofac 等来实现。今天我分享的方法不是使用替换容器,而是通过几行代码来实现属性注入的效果,我称为“伪属性注入”。

三.属性注入解决的痛点

以下介绍的痛点是我在实际编码过程中遇到的一些,如果还有其他的,欢迎在评论和我交流

我所遇到的痛点,我归纳为三条:

1.减少常用的类型的重复注入代码,使构造方法看起来更为简洁,提高阅读性。

2.减少或消除因构造方法注入造成子类继承后的 base 调用链。

3.并非是满足第一条或第二条就需要使用属性注入来解决,只有当第一、二条发生的情况到达一定的数量。

第一条:

以日志 ILogger<T> 为例,我们在 Controller 或者 应用服务层(Application Service)等编写业务的地方可能会常用,那么我们可能会在大部分的 Controller 或者 Application Service 的构造方法里写一句注入,例:

这里只是以日志来举例,我们还能遇到和日志这种相同的类型,每个 Controller 等都要注入一堆这种共同的类型,代码编写起来也比较麻烦,如果多了以后还影响代码阅读。

有何解决办法,那就是定义一个基类,然后通过属性提供给子类,以 Controller 为例:

第二条:

在上面的Controller基类注入 ILogger,然后设置了 Logger 属性,这样子类就可以使用 Logger 属性来使用日志。

这样做每次都要调用 base 将依赖对象传递给基类,如果继承关系有多层,将会造成更大的影响。

注意:本文演示只以日志来举例,如果只有一个ILogger我觉得还可以忍受,实际情况中并非只有一个,比如本地化等等。博主不提倡有上面演示情况的就用属性注入,当到达一定数量才使用,比如在 Controller 或者应用服务这种数量多的对象以及当这些对象需要的共同的注入类型达到一定数量。

四. 伪属性注入核心思想

依托于 ASP.NET Core 自带的容器,在 Resolve Service 时,为需要“属性注入”的属性进行赋值,可以使用 自带容器提供的 ImplementationFactory 来实现。

五. 为 Controller 实现伪属性注入

Controller 的实现较为特殊,Controller 默认是不会通过自带容器来 Resolve&Activate 的,是通过MVC自身管理的,但是微软提供了这样的方法:

services.AddControllers().AddControllersAsServices();

可以通过调用 AddControllersAsServices() 方法来让 Controller 使用自带容器,其主要源代码如下

根据第四小节的思想,我们需要 Controller Resolve 时,来对属性进行赋值,那么我们需要改造 Controller 激活器。

  1. 定义 Controller 基类

  1. Controller 继承基类

  1. 改造 Controller 激活器

可以看到我们改造的代码也就几行。

  1. 替换默认 Controller 激活器
services.AddControllers().AddControllersAsServices();
services.Replace(ServiceDescriptor.Transient<IControllerActivator, XcServiceBasedControllerActivator>()); //替换默认 Controller 激活器
  1. 运行测试

测试正常,如需其他属性的“属性注入”,参考日志这样做就行了。

六. 为 Application Service 实现伪属性注入

只是以 Application Service 来作为讲解,同理可举一反三到其他地方。Application Service 属于领域驱动分层架构中的一层,如不了解,可自行查找资料。

  1. 定义应用服务基类接口
public interface IAppService
{
ILogger Logger { get; set; }
} public class AppService:IAppService
{
public ILogger Logger { get; set; }
}
  1. 定义具体服务,以 User 服务为例
public interface IUserAppService:IAppService
{
void Create();
} public class UserAppService : AppService,IUserAppService
{
public void Create()
{
Logger.LogInformation("来自 Application Service 的日志");
}
}
  1. 定义特殊的注册服务的方法,以便实现 Resolve 为 Logger 赋值
public static class ServiceExtensions
{
public static IServiceCollection AddApplicationService<TService, TImpl>(this IServiceCollection services) where TService:IAppService where TImpl:AppService
{
services.AddApplicationService(typeof(TService), typeof(TImpl));
return services;
}
// 可以反射程序集调用此方法实现批量自动注册应用服务
public static IServiceCollection AddApplicationService(this IServiceCollection services, Type serviceType,Type implType)
{
services.AddTransient(serviceType, sp =>
{
//获取服务实现的实例
var implInstance = ActivatorUtilities.CreateInstance(sp, implType); ; if (implInstance is AppService obj)
{
//为 Logger 赋值
obj.Logger= sp.GetRequiredService<ILoggerFactory>().CreateLogger(implType);
} return implInstance;
});
return services;
}
  1. 注册测试服务

  1. Controller 注入测试服务

  1. 运行测试

七.结束

其实到本文写完,我都在想,要不要封装一个组件,发布到 Nuget 来方便的使用文中我所描述的“伪属性注入”,最后反复想了想,还是觉得不做。如果要使用完全的属性注入可以替换使用第三方容器,本文所述旨在不想引入第三方容器,且想在部分地方来达到属性注入的效果,因为属性注入这个东西也不推荐大范围使用。

本文来源于我在工作中的一些灵感总结,我在看 ControllerActivator 源码时的突发奇想,最近工作虽然忙,但是知识确实积攒了不少,在后面与大家一一分享。

姊妹篇:ASP.NET Core 奇淫技巧之动态WebApi

ASP.NET Core 奇淫技巧之伪属性注入的更多相关文章

  1. ASP.NET Core 奇淫技巧之动态WebApi

    一.前言 接触到动态WebApi(Dynamic Web API)这个词的已有几年,是从ABP框架里面接触到的,当时便对ABP的这个技术很好奇,后面分析了一波,也尝试过从ABP剥离一个出来作为独立组件 ...

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

    仅针对Controller的属性注入: 使用默认容器,不依赖第三方库: 故事背景   闲来无事给项目做优化,发现大多数Controller里面都会用到Logger和AutoMapper,每个Contr ...

  3. [asp.net mvc 奇淫巧技] 04 - 你真的会用Action的模型绑定吗?

    在QQ群或者一些程序的交流平台,经常会有人问:我怎么传一个数组在Action中接收.我传的数组为什么Action的model中接收不到.或者我在ajax的data中设置了一些数组,为什么后台还是接收不 ...

  4. [asp.net mvc 奇淫巧技] 06 - 也许你的项目同一个用户的请求都是同步的

    一.感慨 很久前看到一篇博客中有句话大致的意思是:“asp.net 程序性能低下的主要原因是开发人员技术参差不齐”,当时看到这句话不以为然,然而时间过的越久接触的.net 开发人员越多就越认同这句话: ...

  5. [asp.net mvc 奇淫巧技] 01 - 封装上下文 - 在View中获取自定义的上下文

    我们在asp.net 开发中已经封装了最强大的HttpContext,我们可以在HttpContext中可以获取到几乎任何想获取的东西,也可以在HttpContext写入需要返回客户端的信息.但是这些 ...

  6. 优化DP的奇淫技巧

    DP是搞OI不可不学的算法.一些丧心病狂的出题人不满足于裸的DP,一定要加上优化才能A掉. 故下面记录一些优化DP的奇淫技巧. OJ 1326 裸的状态方程很好推. f[i]=max(f[j]+sum ...

  7. 12个实用的 Javascript 奇淫技巧

    这里分享12个实用的 Javascript 奇淫技巧.JavaScript自1995年诞生以来已过去了16个年头,如今全世界无数的网页在依靠她完成各种关键任务,JavaScript曾在Tiobe发布的 ...

  8. NGINX的奇淫技巧 —— 5. NGINX实现金盾防火墙的功能(防CC)

    NGINX的奇淫技巧 —— 5. NGINX实现金盾防火墙的功能(防CC) ARGUS 1月13日 发布 推荐 0 推荐 收藏 2 收藏,1.1k 浏览 文章整理中...... 实现思路 当服务器接收 ...

  9. NGINX的奇淫技巧 —— 3. 不同域名输出不同伺服器标识

    NGINX的奇淫技巧 —— 3. 不同域名输出不同伺服器标识 ARGUS 1月13日 发布 推荐 0 推荐 收藏 6 收藏,707 浏览 大家或许会有这种奇葩的需求...要是同一台主机上, 需要针对不 ...

随机推荐

  1. STM32 一个初始化EXTI的例子

    23 May 2017 » Hardware 注:STM32F407VGT6 with STM32F4 DSP and standard peripherals library v1.8.0 外部中断 ...

  2. 华为的Java面试题,仅供参考。

    IP地址的编码分为哪俩部分? IP地址由两部分组成,网络号和主机号.不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位. 2.用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该 ...

  3. 跟我猜Spring-Boot:bean的创建

    废话在前 最近几年的技术路子很杂,先是node,然后是php,后来是openresty,再后来转到了java,而接触的框架(Framework),也越发的复杂,从最开始的express/koa,到lu ...

  4. 造轮子系列(三): 一个简单快速的html虚拟语法树(AST)解析器

    前言 虚拟语法树(Abstract Syntax Tree, AST)是解释器/编译器进行语法分析的基础, 也是众多前端编译工具的基础工具, 比如webpack, postcss, less等. 对于 ...

  5. react-intl 实现 React 国际化多语言

    效果预览 React Intl 国际化步骤 创建国际化资源文件 根据语言获取国际化资源 引入 react-intl 的 local data 创建 LocaleProvider 国际化上下文组件 创建 ...

  6. 【Amaple教程】2. 模块

    正如它的名字,模块用于amaplejs单页应用的页面分割,所有的跳转更新和代码编写都是以模块为单位的. 定义一个模块 一个模块由<module>标签对包含,内部分为template模板.J ...

  7. JS面试准备二

    1.常用的字符串方法 1. indexOf:查找字符串某一项的初始位置2. slice:截取字符串(包含起始位置,不包含结束位置) 不会根据参数大小,交换参数位置 如果出现-1按倒数第一个数,如果出现 ...

  8. golang切片

    切片与数组 go的数组是这样的 array := [3]int{1,2,3} array := [...]int{1,2,3} go的切片 array := []int{1,2,3} //1 arra ...

  9. 安装skimage和cv2

    因为第一次接触这个,所以当时安装的时候,也不是很清楚,现在明白了,记录一下,下次别入坑了. 1.安装skimage模块 skimage的全称是:scikit-image 如果说是这样安装,提示我不成功 ...

  10. python数据分析工具 | pandas

    pandas是python下强大的数据分析和探索工具,是的python在处理数据时非常快速.简单.它是构建在numpy之上的,包含丰富的数据处理函数,支持时间序列分析功能,支持灵活处理缺失数据. pa ...