4.5 扩展
.NET Core的服务承载系统无缝集成了依赖注入框架,但是目前还有很多开源的依赖注入框架,比较常用有Castle、StructureMap、Spring.NET、AutoFac、Unity和Ninject等,应如何实现与第三方依赖注入框架的整合?
4.5.1 适配
.NET Core 具有一个承载(Hosting)系统(详见第 10 章),该系统承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已。承载系统总是采用依赖注入的方式消费它在服务承载过程中所需的服务。对于承载系统来说,原始的服务注册总是体现为一个 IServiceCollection集合,最终的依赖注入容器则体现为一个 IServiceProvider对象,如果要将第三方依赖注入框架整合进来,就需要利用它们解决从 IServiceCollection 集合到IServiceProvider对象的适配问题。
具体来说,我们可以在 IServiceCollection集合和 IServiceProvider对象之间设置一个针对某个第三方依赖注入框架的 ContainerBuilder对象:先利用包含原始服务注册的 IServiceCollection集合创建一个ContainerBuilder对象,再利用该对象构建作为依赖注入容器的IServiceProvider对象(见图4-15)。
图4-15 IServiceCollection—ContainerBuilder—IServiceProvider
4.5.2 IServiceProviderFactory<TContainerBuilder>
如图 4-15 所示,两种转换是利用一个 IServiceProviderFactory<TContainerBuilder>对象完成的。如下面的代码片段所示,IServiceProviderFactory<TContainerBuilder>接口定义了两个方法:CreateBuilder 方法利用指定的 IServiceCollection 集合创建对应的 ContainerBuilder 对象;CreateServiceProvider 方法则进一步利用这个 ContainerBuilder 对象创建作为依赖注入容器的IServiceProvider对象。
.NET Core的承载系统总是利用注册的IServiceProviderFactory<TContainerBuilder>服务创建最终作为依赖注入容器的 IServiceProvider 对象。承载系统默认注册的是如下所示的 Default ServiceProviderFactory类型。如下面的代码片段所示,DefaultServiceProviderFactory对象会直接调用指定IServiceCollection集合的BuildServiceProvider方法创建对应的IServiceProvider对象。
4.5.3 整合第三方依赖注入框架
为了使读者对利用注册的 IServiceProviderFactory<TContainerBuilder>服务整合第三方依赖注入框架有更加深刻的理解,本节将演示一个具体的实例。第 3章创建了一个名为 Cat的 Mini版依赖注入框架,下面将提供一个具体的 IServiceProviderFactory<TContainerBuilder>实现类型完成对它的整合。
首先,创建一个名为 CatBuilder 的类型作为对应的 ContainerBuilder。由于需要涉及针对服务范围的创建,所以我们在 CatBuilder 类中定义了如下两个内嵌的私有类型。其中,表示服务范围的 ServiceScope对象实际上就是对一个 IServiceProvider对象的封装;而 ServiceScopeFactory类型表示创建该对象的工厂,它是对一个Cat对象的封装。
一个 CatBuilder对象是对一个 Cat对象的封装,它的 BuildServiceProvider方法会直接返回这个 Cat 对象,并作为最终提供的依赖注入容器。CatBuilder 对象在初始化过程中添加了针对IServiceScopeFactory 接口的服务注册,具体注册的是根据作为当前子容器的 Cat 对象创建的ServiceScopeFactory对象。为了实现程序集范围内的批量服务注册,可以为CatBuilder类型定义一个Register方法。
如下面的代码片段所示,CatServiceProviderFactory 类型实现了 IServiceProviderFactory<CatBuilder>接口。在实现的 CreateBuilder 方法中,我们创建了一个 Cat 对象,并将指定IServiceCollection 集合包含的服务注册(ServiceDescriptor 对象)转换成兼容 Cat 的服务注册(ServiceRegistry对象),然后将其应用到创建的 Cat对象上。我们最终利用这个 Cat对象创建出返回的 CatBuilder 对象。实现的 CreateServiceProvider 方法返回的是通过调用 CatBuilder 对象的CreateServiceProvider方法得到的IServiceProvider对象。
Cat对象具有与.NET Core依赖注入框架一致的服务生命周期表达方式,所以将服务注册从ServiceDescriptor类型转化成 ServiceRegistry类型时,可以实现直接完成两种生命周期模式的转换,具体的转换可以在AsCatLifetime扩展方法中实现。
其次,演示如何利用CatServiceProviderFactory创建作为依赖注入容器的IServiceProvider对象。我们定义了如下接口和对应的实现类型,其中,Foo、Bar、Baz 和 Qux 类型分别实现了对应的接口 IFoo、IBar、IBaz与 IQux,Qux类型上标注了一个 MapToAttribute特性,并注册了与对应接口 IQux之间的映射。为了反映 Cat框架对服务实例生命周期的控制,我们让这些类型派生于同一个基类Base。Base实现了IDisposable接口,可以在其构造函数和实现的Dispose方法中输出相应的文本,以确定对应的实例何时被创建和释放。
在如下所示的演示程序中,我们先创建了一个ServiceCollection集合,并采用3种不同的生命周期模式分别添加了针对 IFoo 接口、IBar 接口和 IBaz 接口的服务注册。然后根据ServiceCollection 集合创建了一个 CatServiceProviderFactory 对象,并调用其 CreateBuilder 方法创建出对应的CatBuilder对象。最后调用CatBuilder对象的Register方法完成了针对当前入口程序集的批量服务注册,其目的在于添加针对IQux/Qux的服务注册。
在调用 CatServiceProviderFactory 对象的 CreateServiceProvider 方法来创建出作为依赖注入容器的 IServiceProvider对象之后,我们先后两次调用了本地方法 GetServices。GetServices方法会利用这个IServiceProvider对象来创建一个服务范围,并利用此服务范围内的IServiceProvider提供两组服务实例。利用CatServiceProviderFactory创建的IServiceProvider对象最终通过调用其Dispose 方法进行释放。该程序运行之后在控制台上输出的结果如图 4-16 所示,输出结果体现的服务生命周期与演示程序体现的生命周期是完全一致的。(S412)
图4-16 利用CatServiceProviderFactory创建IServiceProvider对象
[1] .NET Core提供的很多基础框架和组件在设计的时候都会考虑“抽象和实现”的分离,并且将这种分离直接体现在部署的NuGet包上。例如,如果要开发一款名为Foobar的组件,微软会将一些抽象的接口和必要的类型定义在NuGet包“Foobar.Abstractions”中,而将具体的实现类型定义在 NuGet 包“Foobar”中。如果另一个组件或者框架需要使用Foobar组件,或者它需要对Foobar组件进行扩展与定制,只需要添加针对NuGet包“Foobar.Abstractions”的依赖即可。这种做法体现了“最小依赖”的设计原则,我们在进行组件或者框架开发的时候可以采用这种做法。
[2] 如果项目采用的SDK为“Microsoft.NET.Sdk.Web”,那么ASP.NET Core框架会自动添加基础NuGet包的依赖,所以无须指定对应的框架引用。