开局先唠嗑一下,许久不曾更新博客,一直在调整本身的状态,去年是个人本命年,或许是应验了本命年的多灾多难,过得十分不顺,不管是生活上仍是工做上。还好当我度过了所谓的本命年后,许多事情都在慢慢变好,我将会开始恢复更新博客,争取恢复到之前的速度上(由于工做比较忙,因此这个过程可能须要一段时间)。html
说到属性注入,咱们就不得不提一下 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 的一种实现方式。架构
在依赖注入时,咱们最经常使用的即是经过构造方法注入,还有另外一种方式那即是属性注入。app
关于属性注入,若是在网上搜索,大部份内容都是不推荐使用,或者说慎重使用的,由于属性注入会形成类型的依赖关系隐藏,测试不友好等,我也赞成这种说法,属性注入可使用,可是要谨慎,不能盲目使用。个人原则:在封装框架(搭架子)时可使用,但不能大范围使用,只有必须使用属性注入来达到效果的地方才会使用,用来提升使用框架时的编码效率,来达到一些便利,脱离框架层面,编写业务代码时,不得使用。框架
在 ASP.NET Core 中,自带的容器是不支持属性注入的,可是能够经过替换容器,如:Autofac 等来实现。今天我分享的方法不是使用替换容器,而是经过几行代码来实现属性注入的效果,我称为“伪属性注入”。测试
如下介绍的痛点是我在实际编码过程当中遇到的一些,若是还有其余的,欢迎在评论和我交流ui
我所遇到的痛点,我概括为三条:this
1.减小经常使用的类型的重复注入代码,使构造方法看起来更为简洁,提升阅读性。编码
2.减小或消除因构造方法注入形成子类继承后的 base 调用链。设计
3.并不是是知足第一条或第二条就须要使用属性注入来解决,只有当第1、二条发生的状况到达必定的数量。3d
以日志 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 默认是不会经过自带容器来 Resolve&Activate 的,是经过MVC自身管理的,可是微软提供了这样的方法:
services.AddControllers().AddControllersAsServices();
能够经过调用 AddControllersAsServices()
方法来让 Controller 使用自带容器,其主要源代码以下
根据第四小节的思想,咱们须要 Controller Resolve 时,来对属性进行赋值,那么咱们须要改造 Controller 激活器。
能够看到咱们改造的代码也就几行。
services.AddControllers().AddControllersAsServices(); services.Replace(ServiceDescriptor.Transient<IControllerActivator, XcServiceBasedControllerActivator>()); //替换默认 Controller 激活器
测试正常,如需其余属性的“属性注入”,参考日志这样作就好了。
只是以 Application Service 来做为讲解,同理可触类旁通到其余地方。Application Service 属于领域驱动分层架构中的一层,如不了解,可自行查找资料。
public interface IAppService { ILogger Logger { get; set; } } public class AppService:IAppService { public ILogger Logger { get; set; } }
public interface IUserAppService:IAppService { void Create(); } public class UserAppService : AppService,IUserAppService { public void Create() { Logger.LogInformation("来自 Application Service 的日志"); } }
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; }
其实到本文写完,我都在想,要不要封装一个组件,发布到 Nuget 来方便的使用文中我所描述的“伪属性注入”,最后反复想了想,仍是以为不作。若是要使用彻底的属性注入能够替换使用第三方容器,本文所述旨在不想引入第三方容器,且想在部分地方来达到属性注入的效果,由于属性注入这个东西也不推荐大范围使用。
本文来源于我在工做中的一些灵感总结,我在看 ControllerActivator
源码时的突发奇想,最近工做虽然忙,可是知识确实积攒了很多,在后面与你们一一分享。