[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

时间:2022-05-10 01:50:53

.NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已。承载系统总是采用依赖注入的方式来消费它在服务承载过程所需的服务。对于承载系统来说,原始的服务注册总是体现为一个IServiceCollection集合,最终的依赖注入容器则体现为一个IServiceProvider对象,如果要将第三方依赖注入框架整合进来,就需要利用它们解决从IServiceCollection集合到IServiceProvider对象之间的适配问题。

一、IServiceCollection =>ContainerBuilder=>IServiceProvider

具体来说,我们可以在IServiceCollection集合和IServiceProvider对象之间设置一个针对某个第三方依赖注入框架的ContainerBuilder对象。我们先利用包含原始服务注册的IServiceCollection集合来创建一个ContainerBuilder对象,再利用该对象来构建作为依赖注入容器的IServiceProvider对象。

[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

二、 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框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

[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]:与第三方依赖注入框架的适配