[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
通过《利用容器提供服务》我们知道作为依赖注入容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创建的,IServiceCollection对象是一个存放服务注册信息的集合。在《一个迷你版DI框架》中创建的Cat框架中的服务注册是通过类型ServiceRegistry表示的,在.NET Core依赖注入框架中,与之对应的类型是ServiceDescriptor。
一、IServiceCollection
ServiceDescriptor是对某个服务注册项的描述,作为依赖注入容器的IServiceProvider对象正是利用该对象提供的描述信息才得以提供我们需要的服务实例。服务描述总是注册到通过ServiceType属性表示的服务类型上,ServiceDescriptor的Lifetime表示采用的生命周期模式。
public class ServiceDescriptor
{
public Type ServiceType { get; }
public ServiceLifetime Lifetime { get; } public Type ImplementationType { get; }
public Func<IServiceProvider, object> ImplementationFactory { get; }
public object ImplementationInstance { get; } public ServiceDescriptor(Type serviceType, object instance);
public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}
ServiceDescriptor的其他三个属性体现了服务实例的三种提供方式,并分别对应着三个构造函数。如果我们指定了服务的实现类型(对应于ImplementationType属性),那么最终的服务实例将通过调用定义在该类型中的某一个构造函数来创建。如果指定的是一个Func<IServiceProvider, object>对象(对应于ImplementationFactory属性),那么该委托对象将作为提供服务实例的工厂。如果我们直接指定一个现成的对象(对应的属性为ImplementationInstance),那么该对象就是最终提供的服务实例。
如果我们采用提供的现成服务实例来创建ServiceDescriptor对象,对应服务注册自然会采用Singleton生命周期模式。对于通过其他两个构造函数创建的ServiceDescriptor对象来说,需要显式指定采用的生命周期模式。相较于ServiceDescriptor,我们在Cat框架中定义的ServiceRegistry显得更加简单,因为我们直接提供了一个类型为Func<Cat,Type[], object>的对象来提供对应的服务实例。
除了调用上面介绍的三个构造函数来创建对应的ServiceDescriptor对象之外,我们还可以利用定义在ServiceDescriptor类型中的一系列静态方法来创建该对象。如下面的代码片段所示,ServiceDescriptor提供了如下两个名为Describe的方法重载来创建对应的ServiceDescriptor对象。
public class ServiceDescriptor
{
public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime);
public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}
当我们调用上面两个Describe方法来创建ServiceDescriptor对象的时候总是需要指定采用的生命周期模式,为了让对象创建变得更加简单,ServiceDescriptor中还定义了一系列针对具体生命周期模式的静态工厂方法。如下所示的是针对Singleton模式的一组静态工厂方法重载的定义,针对其他两种模式的Scoped和Transient方法具有类似的定义。
public class ServiceDescriptor
{
public static ServiceDescriptor Singleton <TService, TImplementation>() where TService: class where TImplementation: class, TService;
public static ServiceDescriptor Singleton <TService, TImplementation>( Func<IServiceProvider, TImplementation> implementationFactory) where TService: class where TImplementation: class, TService;
public static ServiceDescriptor Singleton<TService>( Func<IServiceProvider, TService> implementationFactory) where TService: class;
public static ServiceDescriptor Singleton<TService>( TService implementationInstance) where TService: class;
public static ServiceDescriptor Singleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);
public static ServiceDescriptor Singleton(Type serviceType, object implementationInstance);
public static ServiceDescriptor Singleton(Type service, Type implementationType);
}
二、Add方法
依赖注入框架将服务注册存储在一个通过IServiceCollection接口表示的集合之中。如下面的代码片段所示,一个IServiceCollection对象本质上就是一个元素类型为ServiceDescriptor的列表。在默认情况下我们使用的是实现该接口的ServiceCollection类型。
public interface IServiceCollection : IList<ServiceDescriptor> {}
public class ServiceCollection : IServiceCollection {}
我们在应用启动时针对服务的注册本质上就是创建相应的ServiceDescriptor对象并将其添加到指定IServiceCollection对象中的过程。考虑到服务注册是一个高频调用的操作,所以依赖注入框架为IServiceCollection接口定义了一系列扩展方法完成服务注册的工作,比如下面的这两个Add方法可以将指定的一个或者多个ServiceDescriptor对象添加到IServiceCollection集合中。
public static class ServiceCollectionDescriptorExtensions
{
public static IServiceCollection Add(this IServiceCollection collection, ServiceDescriptor descriptor);
public static IServiceCollection Add(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors);
}
三、Add{Lifetime}方法
依赖注入框架还针对具体生命周期模式为IServiceCollection接口定义了一系列的扩展方法,它们会根据提供的输入创建出对应的ServiceDescriptor对象,并将其添加到指定的IServiceCollection对象中。如下所示的是针对Singleton模式的AddSingleton方法重载的定义,针对其他两个生命周期模式的AddScoped和AddTransient方法具有类似的定义。
public static class ServiceCollectionServiceExtensions
{
public static IServiceCollection AddSingleton<TService>( this IServiceCollection services) where TService: class;
public static IServiceCollection AddSingleton<TService, TImplementation>( this IServiceCollection services) where TService: class where TImplementation: class, TService;
public static IServiceCollection AddSingleton<TService>( this IServiceCollection services, TService implementationInstance) where TService: class;
public static IServiceCollection AddSingleton<TService, TImplementation>( this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory) where TService: class where TImplementation: class, TService;
public static IServiceCollection AddSingleton<TService>( this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService: class;
public static IServiceCollection AddSingleton( this IServiceCollection services, Type serviceType);
public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);
public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, object implementationInstance);
public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Type implementationType);
}
四、TryAdd & TryAdd{Lifetime}方法
虽然针对同一个服务类型可以添加多个ServiceDescriptor对象,但这种情况只有在应用需要使用到同一类型的多个服务实例的情况下才有意义,比如我们可以注册多个ServiceDescriptor来提供同一个主题的多个订阅者。如果我们总是根据指定的服务类型来提取单一的服务实例,这种情况下一个服务类型只需要一个ServiceDescriptor对象就够了。对于这种场景我们可能会使用如下两个名为TryAdd的扩展方法,该方法会根据指定ServiceDescriptor提供的服务类型判断对应的服务注册是否存在,只有在指定类型的服务注册不存在的情况下,我们提供的ServiceDescriptor才会被添加到指定的IServiceCollection对象中。
public static class ServiceCollectionDescriptorExtensions
{
public static void TryAdd(this IServiceCollection collection, ServiceDescriptor descriptor);
public static void TryAdd(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors);
}
扩展方法TryAdd同样具有基于三种生命周期模式的版本,如下所示的是针对Singleton模式的TryAddSingleton方法的定义。在指定服务类型对应的ServiceDescriptor不存在的情况下,这些方法会采用提供的实现类型、服务实例创建工厂或者服务实例来创建生命周期模式为Singleton的ServiceDescriptor对象,并将其添加到指定的IServiceCollection对象中。针对其他两种生命周期模式的TryAddScoped和TryAddTransient方法具有类似的定义。
public static class ServiceCollectionDescriptorExtensions
{
public static void TryAddSingleton<TService>(this IServiceCollection collection) where TService: class;
public static void TryAddSingleton<TService, TImplementation>( this IServiceCollection collection) where TService: class where TImplementation: class, TService;
public static void TryAddSingleton(this IServiceCollection collection, Type service);
public static void TryAddSingleton<TService>(this IServiceCollection collection, TService instance) where TService: class;
public static void TryAddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService: class;
public static void TryAddSingleton(this IServiceCollection collection, Type service, Func<IServiceProvider, object> implementationFactory);
public static void TryAddSingleton(this IServiceCollection collection, Type service, Type implementationType);
}
五、TryAddEnumerable方法
除了上面介绍的扩展方法TryAdd和TryAdd{Lifetime}之外,IServiceCollection接口还具有如下两个名为TryAddEnumerable的扩展方法。当TryAddEnumerable方法在决定将指定的ServiceDescriptor添加到IServiceCollection对象之前,它也会做存在性检验。与TryAdd和TryAdd{Lifetime}方法不同的是,该方法在判断执行的ServiceDescriptor是否存在是同时考虑服务类型和实现类型。
public static class ServiceCollectionDescriptorExtensions
{
public static void TryAddEnumerable(this IServiceCollection services, ServiceDescriptor descriptor);
public static void TryAddEnumerable(this IServiceCollection services, IEnumerable<ServiceDescriptor> descriptors);
}
被TryAddEnumerable方法用来判断存在性的实现类型不只是ServiceDescriptor的ImplementationType属性。如果ServiceDescriptor是通过一个指定的服务实例创建的,那么该实例的类型会用来判断对应的服务注册是否存在。如果ServiceDescriptor是通过提供的服务实例工厂来创建的,那么代表服务实例创建工厂的Func<in T, out TResult>对象的第二个参数类型将被用于判断ServiceDescriptor的存在性。扩展方法TryAddEnumerable的实现逻辑可以通过如下这段程序来验证。
var services = new ServiceCollection(); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Count == ); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Count == ); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(new Foo()));
Debug.Assert(services.Count == ); Func<IServiceProvider, Foo> factory4Foo = _ => new Foo();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(factory4Foo));
Debug.Assert(services.Count == ); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Bar>());
Debug.Assert(services.Count == ); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(new Baz()));
Debug.Assert(services.Count == ); Func<IServiceProvider, Gux> factory4Gux = _ => new Gux();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(factory4Gux));
Debug.Assert(services.Count == );
如果通过上述策略得到的实现类型为Object,那么TryAddEnumerable会因为实现类型不明确而抛出一个ArgumentException类型的异常。这主要发生在提供的ServiceDescriptor对象是由服务实例工厂创建的情况,所以上面实例中用来创建ServiceDescriptor的工厂类型分别为Func<IServiceProvider, Foo>和Func<IServiceProvider, Gux>,而不是Func<IServiceProvider, object>。
var service = ServiceDescriptor.Singleton<IFoobarbazgux>(_ => new Foo());
new ServiceCollection().TryAddEnumerable(service);
假设我们采用如上所示的方式利用一个Lamda表达式来创建一个ServiceDescriptor对象,对于创建的ServiceDescriptor来说,其服务实例工厂是一个Func<IServiceProvider, object>对象,所以当我们将它作为参数调用TryAddEnumerable方法时会抛出如下图所示的ArgumentException异常,并提示“Implementation type cannot be 'App.IFoobarbazgux' because it is indistinguishable from other services registered for 'App.IFoobarbazgux'.”

六、RemoveAll和Replace方法
上面介绍的这些方法最终的目的都是添加新的ServiceDescriptor对象到指定的IServiceCollection集合中,有的时候我们还希望删除或者替换现有的某个ServiceDescriptor对象,这种情况通常发生在需要对当前使用框架中由某个服务提供的功能进行定制的时候。由于IServiceCollection实现了IList<ServiceDescriptor>接口,所以我们可以调用其Clear、Remove和RemoveAt方法来清除或者删除现有的ServiceDescriptor对象。除此之外,我们还可以选择如下这些扩展方法。
public static class ServiceCollectionDescriptorExtensions
{
public static IServiceCollection RemoveAll<T>( this IServiceCollection collection);
public static IServiceCollection RemoveAll(this IServiceCollection collection, Type serviceType);
public static IServiceCollection Replace(this IServiceCollection collection, ServiceDescriptor descriptor);
}
RemoveAll和RemoveAll<T>方法帮助我们根据指定的服务类型来删除现有的ServiceDescriptor对象。Replace方法会使用指定的ServiceDescriptor去替换第一个具有相同服务类型(对应ServiceType属性)的ServiceDescriptor,实际操作是先删除后添加。如果从目前的IServiceCollection集合中找不到服务类型匹配的ServiceDescriptor对象,指定的ServiceDescriptor对象会直接添加到IServiceCollection对象中,这一逻辑也可以利用如下的程序来验证。
var services = new ServiceCollection();
services.Replace(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Foo))); services.AddSingleton<IFoobarbazgux, Bar>();
services.Replace(ServiceDescriptor.Singleton<IFoobarbazgux, Baz>());
Debug.Assert(!services.Any(it=>it.ImplementationType == typeof(Foo)));
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Bar)));
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Baz)));
[ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转
[ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
[ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
[ASP.NET Core 3框架揭秘] 依赖注入[4]:一个迷你版DI框架
[ASP.NET Core 3框架揭秘] 依赖注入[5]:利用容器提供服务
[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
[ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册的更多相关文章
- [ASP.NET Core 3框架揭秘] 依赖注入:控制反转
ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务
毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例.虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的.在我们提供的依赖注入 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
.NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
<服务注册>.<服务消费>和<生命周期>主要从实现原理的角度对.NET Core的依赖注入框架进行了介绍,接下来更进一步,看看该框架的总体设计和实现.在过去的多个版 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
包含服务注册信息的IServiceCollection集合最终被用来创建作为依赖注入容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IService ...
- [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架
在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
正如我们在<依赖注入:控制反转>提到过的,很多人将IoC理解为一种"面向对象的设计模式",实际上IoC不仅与面向对象没有必然的联系,它自身甚至算不上是一种设计模式.一般 ...
随机推荐
- Django2.0--创建缓存表
创建缓存表 在项目的虚拟环境下(若有),执行:python manage.py createcachetab
- Java流程控制之(三)嵌套
目录 嵌套循环 for循环嵌套 while循环嵌套 总结 之前谈到各种循环结构,有for循环啊,有while循环啊,可以完成不断重复的动作,相当方便.那么如果好多个循环结合再一次,又是如何实现效果的呢 ...
- (一)OpenStack---M版---双节点搭建---基础环境配置
↓↓↓↓↓↓↓↓视频已上线B站↓↓↓↓↓↓↓↓ >>>>>>传送门 配置如下 本次搭建采用2台4核4G的虚拟机,也可以用2台2核4G 主机名 配置 网络 Contr ...
- webpackd学习的意义
高速发展的前端技术,与浏览器支持的不相匹配.导致前端必须把前端比较先进的技术进行一层编码从而使得浏览器可以加载. 比如前端框架Vue,Angular,React.Less,Sass.TypeScrip ...
- 封装Ajax和跨域
目录 引言 封装ajax 案例:使用自封装ajax 案例:动态加载瀑布流 跨域 引言 对于Ajax现在相信大家已经不会陌生了,无论是原生的XMLHttpRequest方式发送还是通过jQuery框架中 ...
- 折腾笔记-计蒜客T1158-和为给定数AC记
欢迎查看原题 1.简单题目叙述 蒜头君给出若干个整数,询问其中是否有一对数的和等于给定的数. 输入格式 共三行: 第一行是整数 ),表示有 n 个整数. 第二行是 n 个整数.整数的范围是在 0 到 ...
- 行内元素(inline标签)设置了行高为什么不生效,还是表现为父盒子的行高?行内元素行高问题终极解释
最近在看张鑫旭大佬的<css世界>,读到5.2.4 内联元素 line-height 的“大值特性” ,产生了疑惑, 在开发中确实也遇到了同样的问题,深入探究后得出结果,先说结论吧,论证 ...
- jsp 实现查询功能
要求: 实现查询功能 1.数据库代码 create database mvce; use mvce; create table test2( id int not null identity, tna ...
- Java 中 Snack3的使用
网上看了一篇Java 中 Gson的使用,所以也跟着写篇Java 中 Snack3的使用 JSON 是一种文本形式的数据交换格式,从Ajax的时候开始流行,它比XML更轻量.比二进制容易阅读和编写:解 ...
- Quantitative proteomics of Uukuniemi virus-host cell interactions reveals GBF1 as proviral host factor for phleboviruses(乌库涅米病毒-宿主细胞互作的定量蛋白质组学揭示了GBF1是个白蛉病毒的前病毒宿主因子)-解读人:谭亦凡
期刊名:Molecular & Cellular Proteomics 发表时间:(2019年12月) IF:4.828 单位:1德国海德堡大学附属医院2德国汉诺威医科大学3德国亥姆霍茲感染研 ...