前言

从.Net Core 开始,.Net 平台内置了一个轻量,易用的 IOC 的框架,供我们在应用程序中使用,社区内还有很多强大的第三方的依赖注入框架如:

内置的依赖注入容器基本可以满足大多数应用的需求,除非你需要的特定功能不受它支持否则不建议使用第三方的容器。

我们今天介绍的主角Scrutor是内置依赖注入的一个强大的扩展,Scrutor有两个核心的功能:一是程序集的批量注入 Scanning,二是 Decoration 装饰器模式,今天的主题是Scanning

学习Scrutor前我们先熟悉一个.Net依赖注入的万能用法。

  1. builder.Services.Add(
  2. new ServiceDescriptor(/*"ServiceType"*/typeof(ISampleService), /*"implementationType"*/typeof(SampleService), ServiceLifetime.Transient)
  3. );

第一个参数ServiceType通常用接口表示,第二个implementationType接口的实现,最后生命周期,熟悉了这个后面的逻辑理解起来就容易些。

Scrutor官方仓库和本文完整的源代码在文末

Scanning

Scrutor提供了一个IServiceCollection的扩展方法作为批量注入的入口,该方法提供了Action<ITypeSourceSelector>委托参数。

  1. builder.Services.Scan(typeSourceSelector => { });

我们所有的配置都是在这个委托内完成的,Setup by Setup 剖析一下这个使用过程。

第一步 获取 types

typeSourceSelector 支持程序集反射获取类型和提供类型参数

程序集选择

ITypeSourceSelector有多种获取程序集的方法来简化我们选择程序集


  1. typeSourceSelector.FromAssemblyOf<Program>();//根据泛型反射获取所在的程序集

  1. typeSourceSelector.FromCallingAssembly();//获取开始发起调用方法的程序集

  1. typeSourceSelector.FromEntryAssembly();//获取应用程序入口点所在的程序集

  1. typeSourceSelector.FromApplicationDependencies();//获取应用程序及其依赖项的程序集

  1. typeSourceSelector.FromDependencyContext(DependencyContext.Default);//根据依赖关系上下文(DependencyContext)中的运行时库(Runtime Library)列表。它返回一个包含了所有运行时库信息的集合。

  1. typeSourceSelector.FromAssembliesOf(typeof(Program));//根据类型获取程序集的集合

  1. typeSourceSelector.FromAssemblies(Assembly.Load("dotNetParadise-Scrutor.dll"));//提供程序集支持Params或者IEnumerable

第二步 从 Types 中选择 ImplementationType

简而言之就是从程序中获取的所有的 types 进行过滤,比如获取的 ImplementationType 必须是非抽象的,是类,是否只需要 Public等,还可以用 ImplementationTypeFilter 提供的扩展方法等


  1. builder.Services.Scan(typeSourceSelector =>
  2. {
  3. typeSourceSelector.FromEntryAssembly().AddClasses();
  4. });

AddClasses()方法默认获取所有公开非抽象的类


还可以通过 AddClasses 的委托参数来进行更多条件的过滤

比如定义一个 Attribute,忽略IgnoreInjectAttribute

  1. namespace dotNetParadise_Scrutor;
  2. [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
  3. public class IgnoreInjectAttribute : Attribute
  4. {
  5. }
  1. builder.Services.Scan(typeSourceSelector =>
  2. {
  3. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  4. {
  5. iImplementationTypeFilter.WithoutAttribute<IgnoreInjectAttribute>();
  6. });
  7. });

利用 iImplementationTypeFilter 的扩展方法很简单就可以实现

在比如 我只要想实现IApplicationService接口的类才可以被注入

  1. namespace dotNetParadise_Scrutor;
  2. /// <summary>
  3. /// 依赖注入标记接口
  4. /// </summary>
  5. public interface IApplicationService
  6. {
  7. }
  1. builder.Services.Scan(typeSourceSelector =>
  2. {
  3. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  4. {
  5. iImplementationTypeFilter.WithoutAttribute<IgnoreInjectAttribute>().AssignableTo<IApplicationService>();
  6. });
  7. });

类似功能还有很多,如可以根据命名空间也可以根据Type的属性用lambda表达式对ImplementationType进行过滤


上面的一波操作实际上就是为了构造一个IServiceTypeSelector对象,选出来的ImplementationType对象保存了到了ServiceTypeSelectorTypes属性中供下一步选择。

除了提供程序集的方式外还可以直接提供类型的方式比如

创建接口和实现

  1. public interface IForTypeService
  2. {
  3. }
  4. public class ForTypeService : IForTypeService
  5. {
  6. }
  1. builder.Services.Scan(typeSourceSelector =>
  2. {
  3. typeSourceSelector.FromTypes(typeof(ForTypeService));
  4. });

这种方式提供类型内部会调用AddClass()方法把符合条件的参数保存到ServiceTypeSelector

第三步确定注册策略

AddClass之后可以调用UsingRegistrationStrategy()配置注册策略是 AppendSkipThrowReplace

下面是各个模式的详细解释

  • RegistrationStrategy.Append :类似于builder.Services.Add
  • RegistrationStrategy.Skip:类似于builder.Services.TryAdd
  • RegistrationStrategy.Throw:ServiceDescriptor 重复则跑异常
  • RegistrationStrategy.Replace: 替换原有服务

这样可以灵活地控制注册流程

  1. builder.Services.Scan(typeSourceSelector =>
  2. {
  3. typeSourceSelector.FromEntryAssembly().AddClasses().UsingRegistrationStrategy(RegistrationStrategy.Skip);
  4. });

不指定则为默认的 Append 即 builder.Services.Add

第四步 配置注册的场景选择合适的ServiceType

ServiceTypeSelector提供了多种方法让我们从ImplementationType中匹配ServiceType

  • AsSelf()
  • As<T>()
  • As(params Type[] types)
  • As(IEnumerable<Type> types)
  • AsImplementedInterfaces()
  • AsImplementedInterfaces(Func<Type, bool> predicate)
  • AsSelfWithInterfaces()
  • AsSelfWithInterfaces(Func<Type, bool> predicate)
  • AsMatchingInterface()
  • AsMatchingInterface(Action<Type, IImplementationTypeFilter>? action)
  • As(Func<Type, IEnumerable<Type>> selector)
  • UsingAttributes()

AsSelf 注册自身

  1. public class AsSelfService
  2. {
  3. }
  1. {
  2. builder.Services.Scan(typeSourceSelector =>
  3. {
  4. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  5. {
  6. iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsSelf").WithoutAttribute<IgnoreInjectAttribute>();
  7. }).AsSelf();
  8. });
  9. Debug.Assert(builder.Services.Any(_ => _.ServiceType == typeof(AsSelfService)));
  10. }

等效于builder.Services.AddTransient<AsSelfService>();


As 批量为 ImplementationType 指定 ServiceType

  1. public interface IAsService
  2. {
  3. }
  4. public class AsOneService : IAsService
  5. {
  6. }
  7. public class AsTwoService : IAsService
  8. {
  9. }
  1. {
  2. builder.Services.Scan(typeSourceSelector =>
  3. {
  4. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  5. {
  6. iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.As").WithoutAttribute<IgnoreInjectAttribute>();
  7. }).As<IAsService>();
  8. });
  9. Debug.Assert(builder.Services.Any(_ => _.ServiceType == typeof(IAsService)));
  10. foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsService)))
  11. {
  12. Debug.WriteLine(asService.ImplementationType!.Name);
  13. }
  14. }

As(params Type[] types)和 As(IEnumerable types) 批量为ImplementationType指定多个 ServiceType,服务必须同时实现这里面的所有的接口

上面的实例再改进一下

  1. public interface IAsOtherService
  2. {
  3. }
  4. public interface IAsSomeService
  5. {
  6. }
  7. public class AsOneMoreTypesService : IAsOtherService, IAsSomeService
  8. {
  9. }
  10. public class AsTwoMoreTypesService : IAsSomeService, IAsOtherService
  11. {
  12. }
  1. {
  2. builder.Services.Scan(typeSourceSelector =>
  3. {
  4. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  5. {
  6. iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsMoreTypes").WithoutAttribute<IgnoreInjectAttribute>();
  7. }).As(typeof(IAsSomeService), typeof(IAsOtherService));
  8. });
  9. List<Type> serviceTypes = [typeof(IAsSomeService), typeof(IAsOtherService)];
  10. Debug.Assert(serviceTypes.All(serviceType => builder.Services.Any(service => service.ServiceType == serviceType)));
  11. foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsSomeService) || _.ServiceType == typeof(IAsOtherService)))
  12. {
  13. Debug.WriteLine(asService.ImplementationType!.Name);
  14. }
  15. }

AsImplementedInterfaces 注册当前 ImplementationType 和实现的接口

  1. public interface IAsImplementedInterfacesService
  2. {
  3. }
  4. public class AsImplementedInterfacesService : IAsImplementedInterfacesService
  5. {
  6. }
  1. //AsImplementedInterfaces 注册当前ImplementationType和它实现的接口
  2. {
  3. builder.Services.Scan(typeSourceSelector =>
  4. {
  5. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  6. {
  7. iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsImplementedInterfaces").WithoutAttribute<IgnoreInjectAttribute>();
  8. }).AsImplementedInterfaces();
  9. });
  10. Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsImplementedInterfacesService)));
  11. foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsImplementedInterfacesService)))
  12. {
  13. Debug.WriteLine(asService.ImplementationType!.Name);
  14. }
  15. }

AsSelfWithInterfaces 同时注册为自身类型和所有实现的接口

  1. public interface IAsSelfWithInterfacesService
  2. {
  3. }
  4. public class AsSelfWithInterfacesService : IAsSelfWithInterfacesService
  5. {
  6. }
  1. {
  2. builder.Services.Scan(typeSourceSelector =>
  3. {
  4. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  5. {
  6. iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsSelfWithInterfaces").WithoutAttribute<IgnoreInjectAttribute>();
  7. }).AsSelfWithInterfaces();
  8. });
  9. //Self
  10. Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(AsSelfWithInterfacesService)));
  11. //Interfaces
  12. Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsSelfWithInterfacesService)));
  13. foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(AsSelfWithInterfacesService) || _.ServiceType == typeof(IAsSelfWithInterfacesService)))
  14. {
  15. Debug.WriteLine(service.ServiceType!.Name);
  16. }
  17. }

AsMatchingInterface 将服务注册为与其命名相匹配的接口,可以理解为一定约定假如服务名称为 ClassName,会找 IClassName 的接口作为 ServiceType 注册

  1. public interface IAsMatchingInterfaceService
  2. {
  3. }
  4. public class AsMatchingInterfaceService : IAsMatchingInterfaceService
  5. {
  6. }
  1. //AsMatchingInterface 将服务注册为与其命名相匹配的接口,可以理解为一定约定假如服务名称为 ClassName,会找 IClassName 的接口作为 ServiceType 注册
  2. {
  3. builder.Services.Scan(typeSourceSelector =>
  4. {
  5. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  6. {
  7. iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsMatchingInterface").WithoutAttribute<IgnoreInjectAttribute>();
  8. }).AsMatchingInterface();
  9. });
  10. Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsMatchingInterfaceService)));
  11. foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IAsMatchingInterfaceService)))
  12. {
  13. Debug.WriteLine(service.ServiceType!.Name);
  14. }
  15. }

UsingAttributes 特性注入,这个还是很实用的在Scrutor提供了ServiceDescriptorAttribute来帮助我们方便的对Class进行标记方便注入

  1. public interface IUsingAttributesService
  2. {
  3. }
  4. [ServiceDescriptor<IUsingAttributesService>()]
  5. public class UsingAttributesService : IUsingAttributesService
  6. {
  7. }
  1. builder.Services.Scan(typeSourceSelector =>
  2. {
  3. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  4. {
  5. iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.UsingAttributes").WithoutAttribute<IgnoreInjectAttribute>();
  6. }).UsingAttributes();
  7. });
  8. Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IUsingAttributesService)));
  9. foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IUsingAttributesService)))
  10. {
  11. Debug.WriteLine(service.ServiceType!.Name);
  12. }

第五步 配置生命周期

通过链式调用WithLifetime函数来确定我们的生命周期,默认是 Transient

  1. public interface IFullService
  2. {
  3. }
  4. public class FullService : IFullService
  5. {
  6. }
  1. {
  2. builder.Services.Scan(typeSourceSelector =>
  3. {
  4. typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
  5. {
  6. iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.Full");
  7. }).UsingRegistrationStrategy(RegistrationStrategy.Skip).AsImplementedInterfaces().WithLifetime(ServiceLifetime.Scoped);
  8. });
  9. Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IFullService)));
  10. foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IFullService)))
  11. {
  12. Debug.WriteLine($"serviceType:{service.ServiceType!.Name},LifeTime:{service.Lifetime}");
  13. }
  14. }

总结

到这儿基本的功能已经介绍完了,可以看出来扩展方法很多,基本可以满足开发过程批量依赖注入的大部分场景。

使用技巧总结:

  • 根据程序集获取所有的类型 此时 Scrutor 会返回一个 IImplementationTypeSelector 对象里面包含了程序集的所有类型集合
  • 调用 IImplementationTypeSelectorAddClasses 方法获取 IServiceTypeSelector 对象,AddClass 这里面可以根据条件选择 过滤一些不需要的类型
  • 调用UsingRegistrationStrategy确定依赖注入的策略 是覆盖 还是跳过亦或是抛出异常 默认 Append 追加注入的方式
  • 配置注册的场景 比如是 AsImplementedInterfaces 还是 AsSelf
  • 选择生命周期 默认 Transient

借助ServiceDescriptorAttribute更简单,生命周期和ServiceType都是在Attribute指定好的只需要确定选择程序集,调用UsingRegistrationStrategy配置依赖注入的策略然后UsingAttributes()即可

最后

本文从Scrutor的使用流程剖析了依赖注入批量注册的流程,更详细的教程可以参考Github 官方仓库。在开发过程中看到很多项目还有一个个手动注入的,也有自己写 Interface或者是Attribute反射注入的,支持的场景都十分有限,Scrutor的出现就是为了避免我们在项目中不停地造轮子,达到开箱即用的目的。

本文完整示例源代码

.Net依赖注入神器Scrutor(上)的更多相关文章

  1. 使用IDEA详解Spring中依赖注入的类型(上)

    使用IDEA详解Spring中依赖注入的类型(上) 在Spring中实现IoC容器的方法是依赖注入,依赖注入的作用是在使用Spring框架创建对象时动态地将其所依赖的对象(例如属性值)注入Bean组件 ...

  2. <Pro .NET MVC4> 三大工具之依赖注入神器——Ninject

    这篇内容是对<Pro .NET MVC4>一书中关于Ninject介绍的总结. Ninject是.NET MVC的一款开源的依赖注入工具. 使用场景:当MVC项目中使用了依赖注入技术来给程 ...

  3. Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  4. WCF 依赖注入-- Attribute

    最近,工作之余学习WCF(Windows Communication Fundation)时,感觉自己还有好多的东西需要学习呀⊙﹏⊙b汗,于是想记录下自己学习WCF的心得,以鼓励自己再接再厉,同时希望 ...

  5. Spring控制反转(IOC)和依赖注入(DI),再记不住就去出家!

    每次看完spring的东西感觉都理解了,但是过了一段时间就忘,可能是不常用吧,也是没理解好,这次记下来. 拿ssh框架中的action,service,dao这三层举例: 控制反转:完成一个更新用户信 ...

  6. 细数Javascript技术栈中的四种依赖注入

    作为面向对象编程中实现控制反转(Inversion of Control,下文称IoC)最常见的技术手段之一,依赖注入(Dependency Injection,下文称DI)可谓在OOP编程中大行其道 ...

  7. 轻松了解Spring中的控制反转和依赖注入(一)

    我们回顾一下计算机的发展史,从最初第一台计算机的占地面积达170平方米,重达30吨,到现如今的个人笔记本,事物更加轻量功能却更加丰富,这是事物发展过程中的一个趋势,在技术领域中同样也是如此,企业级Ja ...

  8. yii2之依赖注入与依赖注入容器

    一.为什么需要依赖注入 首先我们先不管什么是依赖注入,先来分析一下没有使用依赖注入会有什么样的结果.假设我们有一个gmail邮件服务类GMail,然后有另一个类User,User类需要使用发邮件的功能 ...

  9. 简单了解Spring的控制反转和依赖注入

    浅谈控制反转(Inversion of Control,IOC) 我们首先先来了解一下控制二字,也就是在控制"正"转的情况下,在任何一个有请求作用的系统当中,至少需要有两个类互相配 ...

  10. PHP反射机制实现自动依赖注入

    依赖注入又叫控制反转,使用过框架的人应该都不陌生.很多人一看名字就觉得是非常高大上的东西,就对它望而却步,今天抽空研究了下,解开他它的神秘面纱.废话不多说,直接上代码: /* * * * 工具类,使用 ...

随机推荐

  1. 使用HttpServletResponse实现curl接口时控制台输出(续)

    上一篇文章的问题 在上一篇文章 Spring Boot RestController接口如何输出到终端 中讨论了如何使用 HttpSerlvetResponse 写入输出流,使应急接口通过 curl ...

  2. NC19872 [AHOI2005]SHUFFLE 洗牌

    题目链接 题目 题目描述 为了表彰小联为Samuel星球的探险所做出的贡献,小联被邀请参加Samuel星球近距离载人探险活动. 由于Samuel星球相当遥远,科学家们要在飞船中度过相当长的一段时间,小 ...

  3. Python中用With open as 实现对文件的操作

    with open as f在Python中用来读写文件(夹). 基本写法如下: with open(文件名,模式)as f: f.write(内容)#写操作 例:with open ('这个文章.t ...

  4. DS12C887时钟模块, STC89和STC12的代码实现

    DS12C887是时钟芯片DS12C885集成了电池和晶振的版本. 如果拆掉DS12C887的外壳, 能看到里面就是DS12C885. 功能特性 能输出世纪.年.月.日.时.分.秒等时间信息 集成电池 ...

  5. 【Unity3D】缩放、平移、旋转场景

    1 前言 ​ 场景缩放.平移.旋转有两种实现方案,一种是对场景中所有物体进行同步变换,另一种方案是对相机的位置和姿态进行变换. ​ 对于方案一,如果所有物体都在同一个根对象下(其子对象或孙子对象),那 ...

  6. 【C#】基于JsonConvert解析Json数据

    1 解析字典 ​ 1)解析为 JObject private void ParseJson() { // 解析为JObject string jsonStr = "{'name': 'zha ...

  7. 通过weblogic发布服务器某个文件夹

    介绍 客户有一台老服务器,上面安装的是weblogic,现在有个需求是需要将服务器下面某个文件夹下的文件都发布出来供某前端直接访问.之前都是直接利用tomcat的webapps目录直接发布即可,搜索了 ...

  8. Golang从入门到跑路-从基础到微服务学习路线图

    收录的awesome-go项目,学习基础系列,go项目实战,go源码分析,go开发者成长路线图等等,把他们收集起来学习. 地址:https://github.com/jiujuan/go-collec ...

  9. 【Android逆向】破解看雪 test1.apk

    1. 获取apk,并安装至手机 apk 获取地址: https://www.kanxue.com/work-task_read-800624.htm adb install -t test1.apk ...

  10. Conda简单教程

    目录 什么是Conda 安装Conda 虚拟环境管理 模块管理 何时使用Conda 什么是Conda Conda是Python中用于管理包和虚拟环境的一大利器. 使用Conda可以非常便利的使用数据科 ...