【.NET Core框架】依赖注入(DependencyInjection)
简介
简单说,就是将对象的创建和销毁工作交给DI容器来进行,调用方只需要接收注入的对象实例即可。
涉及到的nuget包:
Microsoft.Extensions.DependencyInjection.Abstractions:抽象定义;
Microsoft.Extensions.DependencyInjection:依赖容器实现;
注册服务
- Add
- TryAdd{Lifetime}
当你将同样的服务注册了多次时,如:
services.AddSingleton<IMyService, MyService>();
services.AddSingleton<IMyService, MyService>();
那么当使用IEnumerable<{Service}>解析服务时,就会产生多个MyService实例的副本。
为此,框架提供了TryAdd{Lifetime}扩展方法,当DI容器中已存在指定类型的服务时,则不进行任何操作;反之,则将该服务注入到DI容器中。
TryAdd:通过参数ServiceDescriptor将服务类型、实现类型、生命周期等信息传入进去
TryAddTransient:对应AddTransient
TryAddScoped:对应AddScoped
TryAddSingleton:对应AddSingleton
TryAddEnumerable:这个和TryAdd的区别是,TryAdd仅根据服务类型来判断是否要进行注册,而TryAddEnumerable则是根据服务类型和实现类型一同进行判断是否要进行注册,常常用于注册同一服务类型的多个不同实现。举个例子吧:
// 注册了 IMyService - MyService1
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());
// 注册了 IMyService - MyService2
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService2>());
// 未进行任何操作,因为 IMyService - MyService1 在上面已经注册了
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());
DependencyInjection源码
ServiceDescriptor
public class ServiceDescriptor
{
public ServiceLifetime Lifetime { get; }
public Type ServiceType { get; }
public Type ImplementationType { get; }
public object ImplementationInstance { get; }
public Func<IServiceProvider, object> ImplementationFactory { get; }
}
ServiceDescriptor包含接口类型、服务实例类型、服务实例对象、服务实例对象工厂和生命周期
ServiceLifetime是枚举类型:
public enum ServiceLifetime
{
Singleton,
Scoped,
Transient
}
当我们调用ServiceProvider的GetService方法的时候,它会根据我们提供的服务类型找到对应的ServiceDecriptor对象。如果该ServiceDecriptor对象的ImplementationInstance属性返回一个具体的对象,该对象将直接用作被提供的服务实例。如果ServiceDecriptor对象的ImplementationFactory返回一个具体的委托,该委托对象将直接用作创建服务实例的工厂。如果这两个属性均为Null,ServiceProvider才会根据ImplementationType属性返回的类型调用相应的构造函数创建被提供的服务实例。
ServiceCollection
ServiceCollection就是ServiceDescriptor的集合,看接口:
public interface IServiceCollection : IList<ServiceDescriptor>{}
ServiceCollection源码:
public class ServiceCollection : IServiceCollection
{
// ServiceDescriptor缓存集合,ServiceDescriptor对象缓存到这个属性中
private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();
// 注册到当前ServiceCollection对象中的ServiceDescriptor数量
public int Count => _descriptors.Count;
public bool IsReadOnly => false;
// 设置索引器
public ServiceDescriptor this[int index]
{
get=> _descriptors[index];
set=> _descriptors[index] = value;
}
// 清空所有注册到此ServiceCollection上的ServiceDescriptor对象
public void Clear() => _descriptors.Clear();
// 查询此ServiceCollection是否包含指定ServiceDescriptor对象
public bool Contains(ServiceDescriptor item)=> _descriptors.Contains(item);
// 拷贝ServiceDescriptor
public void CopyTo(ServiceDescriptor[] array, int arrayIndex) =>_descriptors.CopyTo(array, arrayIndex);
// 从此ServiceCollection移除指定ServiceDescriptor
public bool Remove(ServiceDescriptor item)=>_descriptors.Remove(item);
// 获取此ServiceCollection的迭代器
public IEnumerator<ServiceDescriptor> GetEnumerator()=> _descriptors.GetEnumerator();
public int IndexOf(ServiceDescriptor item) => _descriptors.IndexOf(item);
public void Insert(int index, ServiceDescriptor item) => _descriptors.Insert(index, item);
public void RemoveAt(int index)=> _descriptors.RemoveAt(index);
}
ServiceCollectionServiceExtensions:ServiceCollection的扩展类
// 使用基类和派生类类型实例化ServiceDescriptor对象,然后进行缓存,
private static IServiceCollection Add(IServiceCollection collection,Type serviceType,Type implementationType,ServiceLifetime lifetime)
{
var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
collection.Add(descriptor);
return collection;
}
// 使用基类型和工厂实例化ServiceDescriptor对象,然后进行缓存
private static IServiceCollection Add(IServiceCollection collection,Type serviceType,Func<IServiceProvider, object> implementationFactory,ServiceLifetime lifetime)
{
var descriptor = new ServiceDescriptor(serviceType, implementationFactory, lifetime);
collection.Add(descriptor);
return collection;
}
// 使用基类型和具体实例对象实例化ServiceDescriptor对象,然后进行缓存
// 此方法只适用于Singleton生命周期
public static IServiceCollection AddSingleton(this IServiceCollection services,Type serviceType,object implementationInstance)
{
var serviceDescriptor = new ServiceDescriptor(serviceType, implementationInstance);
services.Add(serviceDescriptor);
return services;
}
ServiceCollectionContainerBuilderExtensions:
这个扩展类是创建IServiceProvider
的,在这个扩展类中只具有BuildServiceProvider()
方法,这个方法也就是我们用来获取ServiceProvider
类型,ServiceProvider
是获取服务对象的类型
public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
// 使用默认的ServiceProviderOptions实例
=>BuildServiceProvider(services, ServiceProviderOptions.Default);
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes)
=>services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes });
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
=> new ServiceProvider(services, options);
IServiceProvider(服务提供者)
public interface IServiceProvider
{
object GetService(Type serviceType);
}
IServiceProvider
接口的实现类是ServiceProvider
,我们不能直接创建该对象,只能间接地通过调用IServiceCollection接口的扩展方法BuildServiceProvider得到它。
IServiceProvider除了GetService方法可以获取服务,还有以下扩展方法:
public static class ServiceProviderExtensions
{
public static T GetService<T>(this IServiceProvider provider);
public static object GetRequiredService(this IServiceProvider provider, Type serviceType);
public static T GetRequiredService<T>(this IServiceProvider provider);
}
提供一个服务实例的集合:
如果我们在调用GetService方法的时候将服务类型指定为IEnumerable
public static class ServiceProviderExtensions
{
public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
}
依赖注入容器不仅是服务实例的提供者,同时还需要维护服务实例的生命周期
服务范围(ServiceScope)
ServiceScope为某个ServiceProvider对象圈定了一个“作用域”,枚举类型ServiceLifetime中的Scoped选项指的就是这么一个ServiceScope。在依赖注入的应用编程接口中,ServiceScope通过一个名为IServiceScope的接口来表示。
如下面的代码片段所示,继承自IDisposable接口的IServiceScope具有一个唯一的只读属性ServiceProvider返回确定这个服务范围边界的ServiceProvider。表示ServiceScope由它对应的工厂ServiceScopeFactory来创建,后者体现为具有如下定义的接口IServiceScopeFactory。
如下面的代码片段所示,IServiceProvider的扩展方法CreateScope正是利用提供的IServiceScopeFactory服务实例来创建作为服务范围的IServiceScope对象。
public interface IServiceScope : IDisposable
{
IServiceProvider ServiceProvider { get; }
}
public interface IServiceScopeFactory
{
IServiceScope CreateScope();
}
public static class ServiceProviderServiceExtensions
{
public static IServiceScope CreateScope(this IServiceProvider provider) => provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}
若要充分理解ServiceScope和ServiceProvider之间的关系,我们需要简单了解一下ServiceProvider的层级结构。除了直接通过一个ServiceCollection对象创建一个独立的ServiceProvider对象之外,一个ServiceProvider还可以根据另一个ServiceProvider对象来创建,如果采用后一种创建方式,我们指定的ServiceProvider与创建的ServiceProvider将成为一种“父子”关系。
1: internal class ServiceProvider : IServiceProvider, IDisposable
2: {
3: private readonly ServiceProvider _root;
4: internal ServiceProvider(ServiceProvider parent)
5: {
6: _root = parent._root;
7: }
8: //其他成员
9: }
虽然在ServiceProvider在创建过程中体现了ServiceProvider之间存在着一种树形化的层级结构,但是ServiceProvider对象本身并没有一个指向“父亲”的引用,它仅仅会保留针对根节点的引用。如上面的代码片段所示,针对根节点的引用体现为ServiceProvider类的字段_root。当我们根据作为“父亲”的ServiceProvider创建一个新的ServiceProvider的时候,父子均指向同一个“根”。
由于ServiceProvider自身是一个内部类型,我们不能采用调用构造函数的方式根据一个作为“父亲”的ServiceProvider创建另一个作为“儿子”的ServiceProvider,但是这个目的可以间接地通过创建ServiceScope的方式来完成。如下面的代码片段所示,我们首先创建一个独立的ServiceProvider并调用其GetService
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: IServiceProvider serviceProvider1 = new ServiceCollection().BuildServiceProvider();
6: IServiceProvider serviceProvider2 = serviceProvider1.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
7:
8: object root = serviceProvider2.GetType().GetField("_root", BindingFlags.Instance| BindingFlags.NonPublic).GetValue(serviceProvider2);
9: Debug.Assert(object.ReferenceEquals(serviceProvider1, root));
10: }
11: }
如果读者朋友们希望进一步了解ServiceScope的创建以及它和ServiceProvider之间的关系,我们不妨先来看看作为IServiceScope接口默认实现的内部类型ServiceScope的定义。如下面的代码片段所示,ServiceScope仅仅是对一个ServiceProvider对象的简单封装而已。值得一提的是,当ServiceScope的Dispose方法被调用的时候,这个被封装的ServiceProvider的同名方法同时被执行。
1: {
2: private readonly ServiceProvider _scopedProvider;
3: public ServiceScope(ServiceProvider scopedProvider)
4: {
5: this._scopedProvider = scopedProvider;
6: }
7:
8: public void Dispose()
9: {
10: _scopedProvider.Dispose();
11: }
12:
13: public IServiceProvider ServiceProvider
14: {
15: get {return _scopedProvider; }
16: }
17: }
IServiceScopeFactory接口的默认实现类型是一个名为ServiceScopeFactory的内部类型。如下面的代码片段所示,ServiceScopeFactory的只读字段“_provider”表示当前的ServiceProvider。当CreateScope方法被调用的时候,这个ServiceProvider的“子ServiceProvider”被创建出来,并被封装成返回的ServiceScope对象。
1: internal class ServiceScopeFactory : IServiceScopeFactory
2: {
3: private readonly ServiceProvider _provider;
4: public ServiceScopeFactory(ServiceProvider provider)
5: {
6: _provider = provider;
7: }
8:
9: public IServiceScope CreateScope()
10: {
11: return new ServiceScope(new ServiceProvider(_provider));
12: }
13: }
三种生命周期管理模式
只有在充分了解ServiceScope的创建过程以及它与ServiceProvider之间的关系之后,我们才会对ServiceProvider支持的三种生命周期管理模式(Singleton、Scope和Transient)具有深刻的认识。就服务实例的提供方式来说,它们之间具有如下的差异:
- Singleton:ServiceProvider创建的服务实例保存在作为根节点的ServiceProvider上,所有具有同一根节点的所有ServiceProvider提供的服务实例均是同一个对象。
- Scoped:ServiceProvider创建的服务实例由自己保存,所以同一个ServiceProvider对象提供的服务实例均是同一个对象。
- Transient:针对每一次服务提供请求,ServiceProvider总是创建一个新的服务实例。
创建生命周期作用域
public HomeController(IServiceScopeFactory scopeFactory)
{
using (var scope = scopeFactory.CreateScope())
{
var provider = scope.ServiceProvider;
var env = provider.GetRequiredService<IWebHostEnvironment>();
}
}
ASP.NET Core默认注册了哪些服务
HostBuilder在创建ServiceCollection之后,会注册一些默认的服务。这些服务和我们自行注册的服务并没有任何区别
IHostingEnvironment
IWebHostingEnvironment
ILoggerFactory
ILogger<>
IApplicationBuilderFactory
IHttpContextFactory
IOptions<>
DiagnosticSource
DiagnosticListener
IStartupFilter
ObjectPoolProvider
IStartup
对泛型的支持
ServiceProvider提供的服务实例不仅限于普通的类型,它对泛型服务类型同样支持。在针对泛型服务进行注册的时候,我们可以将服务类型设定为携带具体泛型参数的“关闭泛型类型”(比如IFoobar<IFoo,IBar>),除此之外服务类型也可以是包含具体泛型参数的“开放泛型类型”(比如IFoo<,>)。前者实际上还是将其视为非泛型服务来对待,后者才真正体现了“泛型”的本质。
比如我们注册了某个泛型服务接口IFoobar<,>与它的实现类Foobar<,>之间的映射关系,当我们指定一个携带具体泛型参数的服务接口类型IFoobar<IFoo,IBar>并调用ServiceProvider的GetService方法获取对应的服务实例时,ServiceProvider会针对指定的泛型参数类型(IFoo和IBar)来解析与之匹配的实现类型(可能是Foo和Baz)并得到最终的实现类型(Foobar<Foo,Baz>)。
几种依赖注入方式
- Startup构造函数可注入三个服务:IConfiguration 、IHostEnvironment 、IWebHostEnvironment
- Startup类中的Configure方法可以注入服务
- 在中间件类型的Invoke/InvokeAsync方法中注入
- 在Action中注入
public void Index([FromServices]IFoobar foobar)
{
}
- 在视图中注入
@inject IFoobar Foobar
ActivatorUtilities
如何通过容器创建未注册到容器的服务?
如果有个服务没有注册到容器,但是它依赖的服务已注册到容器,我们可以使用ActivatorUtilities
创建此服务
var service1 = ActivatorUtilities.CreateInstance<Service1>(serviceProvider, parameters: new object[0]);
service1 = ActivatorUtilities.CreateInstance(serviceProvider, typeof(Service1)) as Service1;
var service2 = ActivatorUtilities.CreateInstance<Service2>(serviceProvider, service1);
service2 = ActivatorUtilities.CreateInstance(serviceProvider, typeof(Service2), service1) as Service2;
var factory1 = ActivatorUtilities.CreateFactory(typeof(Service1), Type.EmptyTypes);
service1 = factory1(serviceProvider, new object[0]) as Service1;
var factory2 = ActivatorUtilities.CreateFactory(typeof(Service2), new Type[] { typeof(Service1) });
service2 = factory2(serviceProvider, new object[] { service1 }) as Service2;
以下两个类未注册到容器
public class Service1
{
private IConfiguration _configuration = null;
public Service1(IConfiguration configuration)
{
_configuration = configuration;
}
}
public class Service2
{
private IConfiguration _configuration = null;
private Service1 _service1 = null;
public Service2(IConfiguration configuration, Service1 service1)
{
_configuration = configuration;
_service1 = service1;
}
}
扩展方法Replace
Replace方法可替换掉之前注入的服务,比如我们可以替换框架中用到的服务,达到修改源码的目的
public static class ServiceCollectionExtensions
{
public static IServiceCollection Replace<TService, TImplementation>(this IServiceCollection services)
where TImplementation : TService
{
return services.Replace<TService>(typeof(TImplementation));
}
public static IServiceCollection Replace<TService>(this IServiceCollection services, Type implementationType)
{
return services.Replace(typeof(TService), implementationType);
}
public static IServiceCollection Replace(this IServiceCollection services, Type serviceType, Type implementationType)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (serviceType == null)
{
throw new ArgumentNullException(nameof(serviceType));
}
if (implementationType == null)
{
throw new ArgumentNullException(nameof(implementationType));
}
if (!services.TryGetDescriptors(serviceType, out var descriptors))
{
throw new ArgumentException($"No services found for {serviceType.FullName}.", nameof(serviceType));
}
foreach (var descriptor in descriptors)
{
var index = services.IndexOf(descriptor);
services.Insert(index, descriptor.WithImplementationType(implementationType));
services.Remove(descriptor);
}
return services;
}
private static bool TryGetDescriptors(this IServiceCollection services, Type serviceType, out ICollection<ServiceDescriptor> descriptors)
{
return (descriptors = services.Where(service => service.ServiceType == serviceType).ToArray()).Any();
}
private static ServiceDescriptor WithImplementationType(this ServiceDescriptor descriptor, Type implementationType)
{
return new ServiceDescriptor(descriptor.ServiceType, implementationType, descriptor.Lifetime);
}
}
案例
一个服务多个不同实现依赖注入
实现类:
public interface IPerson
{
string Get();
}
public class PersonA:IPerson
{
public string Get() { return "PersonA"; }
}
public class PersonB : IPerson
{
public string Get() { return "PersonB"; }
}
ConfigureService:
services.AddSingleton<PersonA>();
services.AddSingleton<PersonB>();
services.AddSingleton(provider =>
{
Func<string, IPerson> accesor = (key) =>
{
if (key == "a")
{
return provider.GetService<PersonA>();
}
else if (key == "b")
{
return provider.GetService<PersonB>();
}
else
{
throw new ArgumentException("key error");
}
};
return accesor;
});
注入
public HomeController(Func<string,IPerson> func)
{
var personA = func("a");
var personB = func("b");
Console.WriteLine(personA.Get());
Console.WriteLine(personB.Get());
string a = "";
}
参考
https://www.cnblogs.com/Tmc-Blog/p/9893349.html 优雅使用DI
https://www.cnblogs.com/artech/p/di-4-asp-net-core.html 蒋金楠