ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法
在.NET Core的依赖注入框架中,服务注册的信息将会被封装成ServiceDescriptor对象,而这些对象都会存储在IServiceCollection接口类型表示的集合中,另外,IServiceCollection接口类型默认使用的实现类型为ServiceCollection。这样来看,实际上服务注册这回事,它就是一个将创建的ServiceDescriptor对象添加到IServiceCollection接口类型集合中的过程。
通常一个项目,都是由大量的对象相互协作而构建成的应用程序。所以对于使用依赖注入框架的应用程序而言,需要进行大量的服务注册,并且其中会存在不同形式的注册需求。.NET Core为此提供了大量不同形式的服务注册方法,为了方便使用,它将这些方法都定义为了IServiceCollection接口的扩展方法,这些扩展方法主要分布在
ServiceCollectionServiceExtensions和ServiceCollectionDescriptorExtensions这两个类中。这两个类型都可以实现服务的注册,但是具有如下不同的侧重点:
- ServiceCollectionServiceExtensions:提供以生命周期模式命名,无需指定生命周期参数的注册方法;
- ServiceCollectionDescriptorExtensions:主要以ServiceDescriptor对作为参数进行服务注册,并包含避免同一类型服务重复注册的方法和维护IServiceCollection集合的方法;
个人觉得前者更加简单易用,后者则配置灵活且功能丰富,下面将围绕这两个类型中的服务注册方法进行介绍。
1.ServiceCollectionServiceExtensions
该类型中关于服务注册方法的命名,通常是以固定字符“Add”作为前缀,“Add”后面则是会加上不同的生命周期模式名称。该类型中的注册方法也属于实际开发场景中较为常用的一种,因为使用这种方式的好处是:无需在服务注册时传入一个具体的生命周期,而是根据服务对生命周期模式的需求,选择与模式名称相同的方法即可。
例如,需要创建生命周期模式为Singleton的服务,那么与之对应的注册方法就是AddSingleton。并且可以根据AddSingleton方法的重载选择任意一种提供服务实例的方式。一般最常用的重载形式是,使用对应泛型方法传入服务类型和实现类型,例如下面的使用方式:
1 var collection = new ServiceCollection();
2 collection.AddSingleton<IFooBar, FooBar>();
该类型的注册方法主要都是根据3种生命周期模式而设计,并结合ServiceDescriptor类型中3种提供服务实例的方式进行扩展,从而延申出了很多重载形式,更加方便的为应用程序提供服务注册。如果想要具体了解每个生命周期模式的不同重载可以参考官方说明:
2.ServiceCollectionDescriptorExtensions
相比ServiceCollectionServiceExtensions类型中的方法而言,虽然ServiceCollectionDescriptorExtensions类型从名称上只有一词之差,但是它的方法涉及范围更加丰富。该类型不仅具备服务的注册,还具备了对服务注册信息的“增删改”,并且包含针对服务注册重复性判断的方法,接下来则针对该类型中的方法进行一个介绍。
2.1.Add
该方法需要我们将服务注册的信息创建为一个ServiceDescriptor对象,并将该对象作为参数传入Add方法,以此形式作为服务注册。该方法有两种重载形式,一种是添加单个ServiceDescriptor对象,另一种是可以添加多个ServiceDescriptor对象的集合,对于该方法的调用示例可以参考如下代码:
1 //服务注册信息集合
2 var serviceCollection = new ServiceCollection();
3
4 //添加“单个”服务注册信息对象
5 ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IFooBar),typeof(FooBar), ServiceLifetime.Singleton);
6 serviceCollection.Add(descriptor);
7
8 //添加“多个”服务注册信息对象
9 List<ServiceDescriptor> descriptorList = new List<ServiceDescriptor>()
10 {
11 new ServiceDescriptor(typeof(IAnimal),typeof(Dog), ServiceLifetime.Singleton),
12 new ServiceDescriptor(typeof(IEmployee),typeof(Programmer), ServiceLifetime.Singleton)
13 };
14 serviceCollection.Add(descriptorList);
2.2.服务重复注册
.NET Core依赖注入框架中支持对同一服务类型进行多次注册。因为后续介绍的某些方法会针对这种重复多次的注册方式有相应的处理逻辑,所以在介绍那些方法之前,首先来介绍下服务重复注册这个行为有哪些特点。
1.对于同一个服务类型进行多次注册,使用GetService获取实例时,只会获取一个最近注册的服务实例。例如下图的代码示例中,分别针对同一服务类型IAnimal进行了多次注册,其中实现类型包含:Dog、Cat、Pig,但最后获取的实例只有Pig一个。
2.对于同一个服务类型进行多次注册,如果要获取该服务类型注册的所有服务实例,则可以使用GetServices方法来获取。例如下图的代码示例中,分别针对同一服务类型Base进行了多次注册,在通过调用GetServices方法后,将服务类型Base注册的所有服务实例都存储到了一个集合中。
2.3.TryAdd形式的方法
之所以在在此处介绍的标题为TryAdd形式的方法,是因为除了TryAdd方法本身之外,还有以TryAdd作为方法名称前缀的方法,分别是:TryAdd{生命周期模式}和TryAddEnumerable,并且3个方法都是围绕同一个主题,即避免服务重复注册。它们在调用的时候都会去检查服务类型是否已经注册过,如果已经注册了,则放弃当前方法的注册。TryAdd方法本身和TryAdd{生命周期模式}处理的逻辑基本是一致的,TryAddEnumerable处理会略有不同,下面针对这3类TryAdd形式的方法进行逐一介绍。
TryAdd
该方法需要我们将服务注册的信息创建为一个ServiceDescriptor对象,类似于Add方法,只不过在Add的基础上加了服务重复注册的判断逻辑,使用方式如下图示例:
该示例执行的结果:最终只会获取到第一次注册的服务示例,因为当第二次调用TryAdd时就发现了已经存在相同服务类型的注册,此时它会放弃注册行为。
TryAdd{生命周期模式}
TryAdd{生命周期模式}类型的方法可以看作是TryAdd方法的一种延申形式,处理逻辑和TryAdd方法一致。但是使用起来更加的便捷,因为它是根据3种生命周期模式扩展而来的版本,使用该类型方法注册时无需指定生命周期模式参数,而是根据生命周期需求选择对应的方法名来决定生命周期模式。
以上是TryAdd{生命周期模式}类方法的使用示例,可以看出相比TryAdd方法而言,该方法更加方便,我们无需创建ServiceDescriptor对象和指定生命周期模式。该示例以Singleton模式为例,其他两种模式的调用与此一致,此处就不一一演示了。
TryAddEnumerable
对于TryAddEnumerable方法判断服务重复性的逻辑而言,不在是和前两者的方法一样:仅仅使用服务类型这一个条件来判断重复性。TryAddEnumerable方法在检查重复性时会同时考虑“服务类型”和“实现类型”。如果发现某个服务注册信息对象的“服务类型”和“实现类型”,与当前即将要注册服务信息的“服务类型”和“实现类型”相同时,TryAddEnumerable方法会放弃当前的注册,以避免出现对于这种情况的重复注册。下面基于这个逻辑通过代码示例来证明这一点。
对于上面的示例而言,都是针对同一服务类型使用TryAddEnumerable方法的注册。其中对于第三次进行的Dog的注册而言,此时的ServiceCollection服务注册集合中已经存在了一个相同服务类型的注册,并且已存在的这个服务注册的实现类型也与之相同,所以第三次进行的注册被TryAddEnumerable方法认定为一个重复性的注册,故没有添加到ServiceCollection集合中。而Cat注册时虽然已经存在了相同服务类型的IAnimal,但是没有服务类型和实现类型同时相同的Cat注册,即不满足TryAddEnumerable的重复性判断条件,所以该服务注册会被添加到ServiceCollection服务注册集合中。
3.维护性方法
.NET Core依赖注入框架对于服务注册的行为,实际上就是将ServiceDescriptor(服务注册信息描述对象)添加到IServiceCollection集合中的过程。所以对于一个集合而言除了用于服务注册的添加方法之外,还有一些维护性的方法,用于对IServiceCollection中的服务注册进行:删除、替换、清空等操作,这些方法的来源主要来自两点:
- 由IList<ServiceDescriptor>接口继承而来;
- ServiceCollectionDescriptorExtensions类定义的扩展方法;
下面通过代码示例对一些常用的方法进行演示,并通过注释对方法使用的细节进行强调:
1 var serviceCollection = new ServiceCollection();
2 serviceCollection.AddSingleton<IAnimal, Dog>();
3 serviceCollection.AddSingleton<Base, Pig>();
4
5 //替换:将旧的ServiceDescriptor对象删除,在新增一个新的ServiceDescriptor对象
6 var catDescriptor = ServiceDescriptor.Scoped<IAnimal, Cat>();
7 serviceCollection.Replace(catDescriptor);
8
9 //删除:删除指定服务类型的注册
10 serviceCollection.RemoveAll<Base>();
11
12 //清空ServiceCollection集合中所有的服务注册信息对象
13 serviceCollection.Clear();
4.自动注册
.NET Core依赖注入框架虽然为我们提供了丰富的服务注册方法,但是对于大型的项目而言,服务注册将成为一种高频次且重复性的枯燥工作,那么对于这样的一种情形而言,有没有一种自动化的注册方式呢?答案是有的,就是借助使用一个第三方开源的NetCore.AutoRegisterDi组件来实现服务的自动注册。下面将通过一个示例来演示如何使用NetCore.AutoRegisterDi组件。
1.从NuGet下载NetCore.AutoRegisterDi
通过NuGet下载组件的方式有很多,你可以用不同的下载,本示例通过利用VS中可视化的NuGet界面进行下载。
2.建立特征
NetCore.AutoRegisterDi组件自动化注册的前提是,要求服务注册的实现类型具有某一种特征,该组件会基于这种特征查找出与该特征匹配的所有类型,然后对这些符合特征的类型进行服务注册,最终注入到依赖它的类型中。本示例中服务注册的实现类型所采用的特征是:所有服务实现类型名称都是以“Service”结尾。
1 using System;
2
3 namespace DependencyInjectionDemo
4 {
5 //服务类型
6 public interface IComputer
7 {
8 string SayHi(); //打招呼
9 }
10
11 //服务实现类型
12 public class ComputerService : IComputer
13 {
14 public string SayHi()
15 {
16 return "你好,我是戴尔电脑。";
17 }
18 }
19
20 }
3.定义注入形式
本示例中是将注册的服务提供给MVC中的控制器对象,控制器对象对依赖的服务提供以构造函数形式的注入方式,如下代码所示。
1 public class HomeController : Controller
2 {
3 private IComputer _computer;
4
5 public HomeController(IComputer computer)
6 {
7 _computer = computer;
8 }
9
10 public IActionResult Index()
11 {
12 ViewBag.Msg = _computer.SayHi();
13 return View();
14 }
15
16 }
4.配置自动注册
以ASP.NET Core的项目为例,我们需要在Startup类的ConfigureServices方法中添加如下的代码:
1 //获取服务实现类型所在的程序集,一般都在实体层
2 var modelAssembly = Assembly.Load("DependencyInjectionDemo");
3
4 //服务自动注册
5 services.RegisterAssemblyPublicNonGenericClasses(modelAssembly)
6 .Where(c => c.Name.EndsWith("Service"))
7 .AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
其中RegisterAssemblyPublicNonGenericClasses方法用于获取某个程序集中的所有类型(在实际的项目中我们的服务实现类型一般都定义在实体层),在RegisterAssemblyPublicNonGenericClasses方法调用之后,根据为“服务实现类型”定义的特征来获取相应的类型,此处我们使用Where方法结合特征(以Service结尾的类)筛选出相应的类型。AsPublicImplementedInterfaces方法表示,将针对前面两个方法筛选出的所有公共实现类型,使用指定的生命周期模式结合服务类型进行服务注册。
在完成以上的步骤后,后续对于应用程序的服务注册,都将由NetCore.AutoRegisterDi组件自动完成,那么这样一来,对于后续使用依赖注入框架而言,我们仅仅只需要为依赖的服务定义构造函数的注入形式即可。
学习感悟:学习就像是烧开水,烧到30度、60度、90度,烧的次数在多,没有烧到100度沸点则都是白费。