.NET Core具有一個承載(Hosting)系統,承載需要在后台長時間運行的服務,一個ASP.NET Core應用僅僅是該系統承載的一種服務而已。承載系統總是采用依賴注入的方式來消費它在服務承載過程所需的服務。對於承載系統來說,原始的服務注冊總是體現為一個IServiceCollection集合,最終的依賴注入容器則體現為一個IServiceProvider對象,如果要將第三方依賴注入框架整合進來,就需要利用它們解決從IServiceCollection集合到IServiceProvider對象之間的適配問題。
一、IServiceCollection =>ContainerBuilder=>IServiceProvider
具體來說,我們可以在IServiceCollection集合和IServiceProvider對象之間設置一個針對某個第三方依賴注入框架的ContainerBuilder對象。我們先利用包含原始服務注冊的IServiceCollection集合來創建一個ContainerBuilder對象,再利用該對象來構建作為依賴注入容器的IServiceProvider對象。
二、 IServiceProviderFactory<TContainerBuilder>
如上圖所示的兩種轉換是利用一個IServiceProviderFactory<TContainerBuilder>對象完成的。如下面的代碼片段所示,IServiceProviderFactory<TContainerBuilder>接口定義了兩個方法,其中CreateBuilder方法利用指定的IServiceCollection集合創建出對應的ContainerBuilder對象,而CreateServiceProvider方法則進一步利用這個ContainerBuilder對象創建出作為依賴注入容器的IServiceProvider對象。
public interface IServiceProviderFactory<TContainerBuilder> { TContainerBuilder CreateBuilder(IServiceCollection services); IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder); }
.NET Core的承載系統總是利用注冊的IServiceProviderFactory<TContainerBuilder>服務來創建最終作為依賴注入容器的IServiceProvider對象。承載系統默認注冊的是如下這個DefaultServiceProviderFactory類型。如下面的代碼片段所示,DefaultServiceProviderFactory對象會直接調用指定IServiceCollection集合的BuildServiceProvider方法創建出對應的IServiceProvider對象。
public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection> { public DefaultServiceProviderFactory() : this(ServiceProviderOptions.Default){} public DefaultServiceProviderFactory(ServiceProviderOptions options) =>_options = options; public IServiceCollection CreateBuilder(IServiceCollection services) => services; public IServiceProvider CreateServiceProvider( IServiceCollection containerBuilder) => containerBuilder.BuildServiceProvider(_options); }
三、整合第三方依賴注入框架
為了讓讀者朋友對利用注冊的IServiceProviderFactory<TContainerBuilder>服務整合第三方依賴注入框架具有更加深刻的理解,我們來演示一個具體的實例。我們在《一個Mini版的依賴注入框架》創建了一個名為Cat的“迷你版”依賴注入框架,接下來我們將提供一個具體IServiceProviderFactory<TContainerBuilder>實現類型完成對它的整合。
我們首先創建一個名為CatBuilder的類型作為對應的ContainerBuilder。由於需要涉及針對服務范圍的創建,我們在CatBuilder類中定了如下兩個內嵌的私有類型,其中表示服務范圍的ServiceScope對象實際上就是對一個IServiceProvider對象的封裝,另一個ServiceScopeFactory類型表示創建該對象的工廠,它是對一個Cat對象的封裝。
public class CatBuilder { private class ServiceScope : IServiceScope { public ServiceScope(IServiceProvider serviceProvider) => ServiceProvider = serviceProvider; public IServiceProvider ServiceProvider { get; } public void Dispose()=> (ServiceProvider as IDisposable)?.Dispose(); } private class ServiceScopeFactory : IServiceScopeFactory { private readonly Cat _cat; public ServiceScopeFactory(Cat cat) => _cat = cat; public IServiceScope CreateScope() => new ServiceScope(_cat); } }
一個CatBuilder對象是對一個Cat對象的封裝,它的BuildServiceProvider方法會直接返回這個Cat對象,並作為最終提供的依賴注入容器。CatBuilder在初始化過程中添加了針對IServiceScopeFactory接口的服務注冊,具體注冊的是根據作為當前子容器的Cat對象創建的ServiceScopeFactory對象。為了實現程序集范圍內的批量服務注冊,我們為CatBuilder定義了一個Register方法。
public class CatBuilder { private readonly Cat _cat; public CatBuilder(Cat cat) { _cat = cat; _cat.Register<IServiceScopeFactory>( c => new ServiceScopeFactory(c.CreateChild()), Lifetime.Transient); } public IServiceProvider BuildServiceProvider() => _cat; public CatBuilder Register(Assembly assembly) { _cat.Register(assembly); return this; } ... }
如下所示的CatServiceProviderFactory類型實現了IServiceProviderFactory<CatBuilder>接口。在實現的CreateBuilder方法中,我們創建了一個Cat對象,並將指定IServiceCollection集合包含中的服務注冊(ServiceDescriptor對象)轉換成兼容Cat的服務注冊(ServiceRegistry對象)並應用到創建的Cat對象上。我們最終利用這個Cat對象創建出返回的CatBuilder對象。實現的另一個方法CreateServiceProvider返回的是調用CatBuilder對象的CreateServiceProvider方法得到的IServiceProvider對象。
public class CatServiceProviderFactory : IServiceProviderFactory<CatBuilder> { public CatBuilder CreateBuilder(IServiceCollection services) { var cat = new Cat(); foreach (var service in services) { if (service.ImplementationFactory != null) { cat.Register(service.ServiceType, provider ) => service.ImplementationFactory(provider), service.Lifetime.AsCatLifetime()); } else if (service.ImplementationInstance != null) { cat.Register(service.ServiceType, service.ImplementationInstance); } else { cat.Register(service.ServiceType, service.ImplementationType, service.Lifetime.AsCatLifetime()); } } return new CatBuilder(cat); } public IServiceProvider CreateServiceProvider(CatBuilder containerBuilder) => containerBuilder.BuildServiceProvider(); }
Cat具有.NET Core依賴注入框架一致的服務生命周期表達方式,所以我們在將服務注冊從ServiceDescriptor類型轉化成ServiceRegistry類型時,可以實現直接完成兩種生命周期模式的轉換,具體的轉換實現在如下這個AsCatLifetime擴展方法中。
internal static class Extensions { public static Lifetime AsCatLifetime(this ServiceLifetime lifetime) { return lifetime switch { ServiceLifetime.Scoped => Lifetime.Self, ServiceLifetime.Singleton => Lifetime.Root, _ => Lifetime.Transient, }; } }
接下來我們演示如何利用CatServiceProviderFactory來創建作為依賴注入容器的IServiceProvider對象。我們定義了如下的接口和對應的實現類型,其中Foo、Bar、Baz和Qux類型分別實現了對應的接口IFoo、IBar、IBaz和IQux,其中Qux類型上標注了一個MapToAttribute特性注冊了與對應接口IQux之間的映射。為了反映Cat對服務實例生命周期的控制,我們讓它們派生於同一個基類Base。Base實現了IDisposable接口,我們在其構造函數和實現的Dispose方法中輸出相應的文本以確定對應的實例何時被創建和釋放。
public interface IFoo {} public interface IBar {} public interface IBaz {} public interface IQux {} public interface IFoobar<T1, T2> {} public class Base : IDisposable { public Base() => Console.WriteLine($"Instance of {GetType().Name} is created."); public void Dispose() => Console.WriteLine($"Instance of {GetType().Name} is disposed."); } public class Foo : Base, IFoo{ } public class Bar : Base, IBar{ } public class Baz : Base, IBaz{ } [MapTo(typeof(IQux), Lifetime.Root)] public class Qux : Base, IQux { } public class Foobar<T1, T2>: IFoobar<T1,T2> { public IFoo Foo { get; } public IBar Bar { get; } public Foobar(IFoo foo, IBar bar) { Foo = foo; Bar = bar; } }
在如下所示的演示程序中,我們創建了一個ServiceCollection集合,並采用三種不同的生命周期模式分別添加了針對IFoo、IBar和IBaz接口的服務注冊。我們接下來根據這個ServiceCollection集合創建了一個CatServiceProviderFactory對象,並調用其CreateBuilder方法創建出對應的CatBuilder對象。我們隨后調用了CatBuilder對象的Register方法完成了針對當前入口程序集的批量服務注冊,其目的在於添加針對IQux/Qux的服務注冊。
class Program { static void Main() { var services = new ServiceCollection() .AddTransient<IFoo, Foo>() .AddScoped<IBar>(_ => new Bar()) .AddSingleton<IBaz>(new Baz()); var factory = new CatServiceProviderFactory(); var builder = factory.CreateBuilder(services) .Register(Assembly.GetEntryAssembly()); var container = factory.CreateServiceProvider(builder); GetServices(); GetServices(); Console.WriteLine("\nRoot container is disposed."); (container as IDisposable)?.Dispose(); void GetServices() { using (var scope = container.CreateScope()) { Console.WriteLine("\nService scope is created."); var child = scope.ServiceProvider; child.GetService<IFoo>(); child.GetService<IBar>(); child.GetService<IBaz>(); child.GetService<IQux>(); child.GetService<IFoo>(); child.GetService<IBar>(); child.GetService<IBaz>(); child.GetService<IQux>(); Console.WriteLine("\nService scope is disposed."); } } } }
在調用CatServiceProviderFactory對象的CreateServiceProvider方法創建出作為依賴注入容器的IServiceProvider對象之后,我們先后兩次調用了本地方法GetServices方法。GetServices方法會利用這個IServiceProvider對象創建一個服務范圍,並利用此服務范圍內的IServiceProvider提供兩組服務實例。通過CatServiceProviderFactory創建的IServiceProvider對象在最終通過調用其Dispose方法進行釋放。該程序運行之后會在控制台上輸出如圖4-16所示的結果,輸出結果體現的服務生命周期與演示程序體現的是完全一致的。
[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]:與第三方依賴注入框架的適配