- ASP.NET Core 3 框架揭秘(上下册)
- 蒋金楠
- 2206字
- 2025-02-23 06:04:53
4.2 服务注册
由上面的实例演示可知,作为依赖注入容器的 IServiceProvider 对象是通过调用IServiceCollection接口的BuildServiceProvider扩展方法创建的,IServiceCollection对象是一个存放服务注册信息的集合。第3章创建的Cat框架中的服务注册是通过类型ServiceRegistry表示的,在.NET Core依赖注入框架中,与ServiceRegistry对应的类型是ServiceDescriptor。
4.2.1 ServiceDescriptor
ServiceDescriptor是对某个服务注册项的描述,作为依赖注入容器的IServiceProvider对象正是利用该对象提供的描述信息才得以提供我们需要的服务实例。服务描述总是注册到通过ServiceType属性表示的服务类型上,ServiceDescriptor的Lifetime表示采用的生命周期模式。

ServiceDescriptor的其他3个属性体现了服务实例的3种提供方式,并且分别对应3个构造函数。如果指定了服务的实现类型(对应 ImplementationType 属性),那么最终的服务实例将通过调用定义在该类型中的某个构造函数来创建。如果指定的是一个 Func<IServiceProvider,object>对象(对应 ImplementationFactory属性),那么该委托对象将作为提供服务实例的工厂。如果直接指定一个现成的对象(对应的属性为 ImplementationInstance),那么该对象就是最终提供的服务实例。
如果采用现成的服务实例来创建ServiceDescriptor对象,对应的服务注册就会采用Singleton生命周期模式。对于通过其他两个构造函数创建的 ServiceDescriptor 对象来说,需要显式指定采用的生命周期模式。相较于ServiceDescriptor,在Cat框架中定义的ServiceRegistry显得更加简单,因为我们直接提供了一个类型为Func<Cat,Type[],object>的对象来提供对应的服务实例。
除了可以调用上面介绍的3个构造函数来创建对应的ServiceDescriptor对象,还可以利用定义在 ServiceDescriptor 类型中的一系列静态方法来创建该对象。如下面的代码片段所示,ServiceDescriptor提供了两个名为Describe的方法重载来创建对应的ServiceDescriptor对象。

如果调用上面两个Describe方法来创建ServiceDescriptor对象,就需要指定采用的生命周期模式,为了使对象创建变得更加简单,ServiceDescriptor 类型中还定义了一系列针对具体生命周期模式的静态工厂方法。如下所示的代码片段是针对 Singleton 模式的一组静态工厂方法重载的定义,针对其他两种模式Scoped和Transient的方法具有类似的定义。


4.2.2 IServiceCollection
依赖注入框架将服务注册存储在一个通过 IServiceCollection 接口表示的集合之中。如下面的代码片段所示,一个IServiceCollection对象本质上就是一个元素类型为ServiceDescriptor的列表。在默认情况下使用的是实现该接口的ServiceCollection类型。

应用启动时,针对服务的注册本质上就是创建相应的ServiceDescriptor对象并将其添加到指定 IServiceCollection 对象中的过程。考虑到服务注册是一个高频调用的操作,所以依赖注入框架为 IServiceCollection接口定义了一系列扩展方法来完成服务注册。如下所示的两个 Add方法可以将指定的一个或者多个ServiceDescriptor对象添加到IServiceCollection集合中。

依赖注入框架还针对具体生命周期模式为 IServiceCollection 的接口定义了一系列的扩展方法,它们会根据提供的输入创建对应的 ServiceDescriptor 对象,并将其添加到指定的IServiceCollection对象中。如下所示的代码片段是针对 Singleton模式的 AddSingleton方法重载的定义,针对其他两个生命周期模式的AddScoped方法和AddTransient方法具有类似的定义。


虽然针对同一个服务类型可以添加多个ServiceDescriptor对象,但这种情况只有在应用需要使用同一类型的多个服务实例的情况下才有意义,所以可以注册多个ServiceDescriptor对象来提供同一个主题的多个订阅者。如果总是根据指定的服务类型来提取单一的服务实例,那么一个服务类型只需要一个ServiceDescriptor对象就够了。对于这种场景我们可能会使用如下两个名为TryAdd 的扩展方法,该方法会根据指定 ServiceDescriptor 提供的服务类型判断对应的服务注册是否存在,只有在指定类型的服务注册不存在的情况下,ServiceDescriptor 才会被添加到指定的IServiceCollection对象之中。

TryAdd 扩展方法同样具有基于 3 种生命周期模式的版本,如下所示的代码片段是针对Singleton模式的 TryAddSingleton方法的定义。在指定服务类型对应的 ServiceDescriptor对象不存在的情况下,这些方法会根据提供的实现类型、服务实例或者服务实例工厂来创建生命周期模式为 Singleton的 ServiceDescriptor对象,并将其添加到指定的 IServiceCollection对象之中。针对其他两种生命周期模式的TryAddScoped方法和TryAddTransient方法具有类似的定义。


除了上面介绍的扩展方法 TryAdd和 TryAdd{Lifetime},IServiceCollection接口还具有如下两个名为 TryAddEnumerable 的扩展方法。当 TryAddEnumerable 方法在决定将指定的ServiceDescriptor添加到 IServiceCollection对象之前,它也会做存在性检验。与 TryAdd方法和TryAdd{Lifetime}方法不同的是,TryAddEnumerable方法在判断执行的 ServiceDescriptor是否存在时同时考虑服务类型和实现类型。

TryAddEnumerable 方法用来判断存在性的实现类型不是只有 ServiceDescriptor 的ImplementationType属性。如果 ServiceDescriptor是通过一个指定的服务实例创建的,那么该实例的类型会用来判断对应的服务注册是否存在。如果ServiceDescriptor是通过提供的服务实例工厂创建的,那么代表服务实例创建工厂的Func<in T,out TResult>对象的第二个参数类型将被用于判断 ServiceDescriptor 的存在性。TryAddEnumerable 扩展方法的实现逻辑可以通过如下代码片段进行验证。

如果通过上述策略得到的实现类型为 Object,那么 TryAddEnumerable方法会因为实现类型不明确而抛出一个 ArgumentException类型的异常。这主要发生在提供的 ServiceDescriptor对象是由服务实例工厂创建的情况下,所以上面实例中用来创建ServiceDescriptor的工厂类型分别为Func<IServiceProvider,Foo>和 Func<IServiceProvider,Qux>,而不是 Func<IServiceProvider,object>。

如果采用如上所示的方式利用一个 Lamda 表达式创建一个 ServiceDescriptor 对象,对于创建的 ServiceDescriptor 来说,其服务实例工厂就是一个 Func<IServiceProvider,object>对象,所以将它作为参数调用 TryAddEnumerable方法时会抛出图 4-5所示的 ArgumentException异常,并提示“Implementation type cannot be'App.IFoobarbazqux'because it is indistinguishable from other services registered for'App.IFoobarbazqux'.”

图4-5 实现类型不明确导致的异常
上面介绍的这些方法最终的目的都是在指定的 IServiceCollection 集合中添加新的ServiceDescriptor 对象,有时需要删除或者替换现有的某个 ServiceDescriptor 对象,这种情况通常发生在需要对当前使用框架中由某个服务提供的功能进行定制的时候。由于IServiceCollection实现了IList<ServiceDescriptor>接口,所以可以调用其Clear方法、Remove方法和RemoveAt方法来清除或者删除现有的ServiceDescriptor对象。除此之外,还可以选择如下这些扩展方法。

RemoveAll 方法和 RemoveAll<T>方法可以帮助我们根据指定的服务类型删除现有的ServiceDescriptor对象。Replace方法会使用指定的ServiceDescriptor替换第一个具有相同服务类型(对应 ServiceType 属性)的 ServiceDescriptor,实际操作是先删除后添加。如果从目前的IServiceCollection 集合中找不到服务类型匹配的 ServiceDescriptor 对象,那么指定的ServiceDescriptor对象会直接添加到 IServiceCollection对象中,这一逻辑也可以利用如下程序进行验证。
