ASP.NET Core中的依赖注入(3): 服务的注册与提供
在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象。ASP.NET Core中的DI容器体现为一个实现了IServiceProvider接口的对象。
ServiceProvider与ServiceDescriptor
服务的注册与提供
利用ServiceProvider来提供服务
提供一个服务实例的集合
获取ServiceProvider自身对象
对泛型的支持
一、ServiceProvider与ServiceDescriptor
我一直觉得优秀的设计首先应该是简单的设计,至少是看起来简单的设计,这就是我们所谓的大道至简。作为一个服务的提供者,ASP.NET Core中的DI容器最终体现为一个IServiceProvider接口,我们将所有实现了该接口的类型及其实例统称为ServiceProvider。如下面的代码片段所示,该接口简单至极,它仅仅提供了唯一个GetService方法,该方法根据提供的服务类型为你提供对应的服务实例。
1: public interface IServiceProvider
2: {
3: object GetService(Type serviceType);
4: }
ASP.NET Core内部真正使用的是一个实现了IServiceProvider接口的内部类型(该类型的名称为“ServiceProvider”),我们不能直接创建该对象,只能间接地通过调用IServiceCollection接口的扩展方法BuildServiceProvider得到它。IServiceCollection接口定义在“Microsoft.Extensions.DependencyInjection”命名空间下,如果没有特别说明,本系列文章涉及到的与ASP.NET Core依赖注入相关的类型均采用此命名空间。 如下面的代码片段所示,IServiceCollection接口实际上代表一个元素为ServiceDescriptor对象的集合,它直接继承了另一个接口IList<ServiceDescriptor>,而ServiceCollection类实现了该接口。
1: public static class ServiceCollectionExtensions
2: {
3: public static IServiceProvider BuildServiceProvider(this IServiceCollection services);
4: }
5:
6: public interface IServiceCollection : IList<ServiceDescriptor>
7: {}
8:
9: Public class ServiceCollection: IServiceCollection
10: {
11: //省略成员
12: }
体现为DI容器的ServiceProvider之所以能够根据我们给定的服务类型(一般是一个接口类型)提供一个能够开箱即用的服务实例,是因为我们预先注册了相应的服务描述信息,这些指导ServiceProvider正确实施服务提供操作的服务描述体现为如下一个ServiceDescriptor类型。
1: public class ServiceDescriptor
2: {
3: public ServiceDescriptor(Type serviceType, object instance);
4: public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
5: public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
6:
7: public Type ServiceType { get; }
8: public ServiceLifetime Lifetime { get; }
9:
10: public Type ImplementationType { get; }
11: public object ImplementationInstance { get; }
12: public Func<IServiceProvider, object> ImplementationFactory { get; }
13: }
ServiceDescriptor的ServiceType属性代表提供服务的生命类型,由于标准化的服务一般会定义成接口,所以在绝大部分情况下体现为一个接口类型。类型为ServiceLifetime的属性Lifetime体现了ServiceProvider针对服务实例生命周期的控制方式。如下面的代码片段所示,ServiceLifetime是一个美剧类型,定义其中的三个选项(Singleton、Scoped和Transient)体现三种对服务对象生命周期的控制形式,我们将在本节后续部分对此作专门的介绍。
1: public enum ServiceLifetime
2: {
3: Singleton,
4: Scoped,
5: Transient
6: }
对于ServiceDescriptor的其他三个属性来说,它们实际上是辅助ServiceProvider完成具体的服务实例提供操。ImplementationType属性代表被提供服务实例的真实类型,属性ImplementationInstance则直接代表被提供的服务实例,ImplementationFactory则提供了一个创建服务实例的委托对象。ASP.NET Core与依赖注入相关的几个核心类型具有如图10所示的关系。
由于ASP.NET Core中的ServiceProvider是根据一个代表ServiceDescriptor集合的IServiceCollection对象创建的,当我们调用其GetService方法的时候,它会根据我们提供的服务类型找到对应的ServiceDecriptor对象。如果该ServiceDecriptor对象的ImplementationInstance属性返回一个具体的对象,该对象将直接用作被提供的服务实例。如果ServiceDecriptor对象的ImplementationFactory返回一个具体的委托,该委托对象将直接用作创建服务实例的工厂。
如果这两个属性均为Null,ServiceProvider才会根据ImplementationType属性返回的类型调用相应的构造函数创建被提供的服务实例。至于我们在上面一节中提到的三种依赖注入方式,ServiceProvider仅仅支持构造器注入,属性注入和方法注入的支持并未提供。
二、服务的注册与提供
ASP.NET Core针对依赖注入的编程主要体现在两个方面:其一,创建一个ServiceCollection对象并将服务注册信息以ServiceDescriptor对象的形式添加其中;其二,针对ServiceCollection对象创建对应的ServiceProvider并利用它提供我们需要的服务实例。
在进行服务注册的时候,我们可以直接调用相应的构造函数创建ServiceDescriptor对象并将其添加到ServiceCollection对象之中。除此之外,IServiceCollection接口还具有如下三组扩展方法将这两个步骤合二为一。从下面给出的代码片段我们不难看出这三组扩展方法分别针对上面我们提及的三种针对服务实例的生命周期控制方式,泛型参数TService代表服务的声明类型,即ServiceDescriptor的ServiceType属性,至于ServiceDescriptor的其他属性,则通过方法相应的参数来提供。
1: public static class ServiceCollectionExtensions
2: {
3: public static IServiceCollection AddScoped<TService>(this IServiceCollection services) where TService: class;
4: //其他AddScoped<TService>重载
5:
6: public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService: class;
7: //其他AddSingleton<TService>重载
8:
9: public static IServiceCollection AddTransient<TService>(this IServiceCollection services) where TService: class;
10: //其他AddTransient<TService>重载
11: }
对于用作DI容器的ServiceProvider对象来说,我们可以直接调用它的GetService方法根据指定的服务类型获得想用的服务实例。除此之外,服务的提供还可以通过IServiceProvider接口相应的扩展方法来完成。如下面的代码片段所示,扩展方法GetService<T>以泛型参数的形式指定服务的声明类型。至于另外两个扩展方法GetRequiredService和GetRequiredService<T>,如果ServiceProvider不能提供一个具体的服务实例,一个InvalidOperationException异常会被抛出来并提示相应的服务注册信息不足。
1: public static class ServiceProviderExtensions
2: {
3: public static T GetService<T>(this IServiceProvider provider);
4: public static object GetRequiredService(this IServiceProvider provider, Type serviceType);
5: public static T GetRequiredService<T>(this IServiceProvider provider);
6: }
利用ServiceProvider来提供服务
接下来采用实例演示的方式来介绍如何利用ServiceCollection进行服务注册,以及如何利用ServiceCollection创建对应的ServiceProvider来提供我们需要的服务实例。我们创建一个ASP.NET Core控制台程序,并在project.json中按照如下的方式添加针对 “Microsoft.Extensions.DepedencyInjection”这个NuGet包的依赖。
1: {
2: "dependencies": {
3: "Microsoft.Extensions.DependencyInjection": "1.0.0-rc1-final"
4: },
5: ...
6: }
我们接下来定义四个服务接口(IFoo、IBar、IBaz和IGux)以及分别实现它们的四个服务类(Foo、Bar、Baz和Gux)如下面的代码片段所示,IGux具有三个只读属性(Foo、Bar和Baz)均为接口类型,并在构造函数中进行初始化。
1: public interface IFoo {}
2: public interface IBar {}
3: public interface IBaz {}
4: public interface IGux
5: {
6: IFoo Foo { get; }
7: IBar Bar { get; }
8: IBaz Baz { get; }
9: }
10:
11: public class Foo : IFoo {}
12: public class Bar : IBar {}
13: public class Baz : IBaz {}
14: public class Gux : IGux
15: {
16: public IFoo Foo { get; private set; }
17: public IBar Bar { get; private set; }
18: public IBaz Baz { get; private set; }
19:
20: public Gux(IFoo foo, IBar bar, IBaz baz)
21: {
22: this.Foo = foo;
23: this.Bar = bar;
24: this.Baz = baz;
25: }
26: }
现在我们在作为程序入口的Main方法中创建了一个ServiceCollection对象,并采用不同的方式完成了针对四个服务接口的注册。具体来说,对于正对服务接口IFoo和IGux的ServiceDescriptor来说,我们指定了代表服务真实类型的ImplementationType属性,而对于针对服务接口IBar和IBaz的ServiceDescriptor来说,我们初始化的则是分别代表服务实例和服务工厂的ImplementationInstance个ImplementationFactory属性。由于我们调用的是AddSingleton方法,所以四个ServiceDescriptor的Lifetime属性均为Singleton。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: IServiceCollection services = new ServiceCollection()
6: .AddSingleton<IFoo, Foo>()
7: .AddSingleton<IBar>(new Bar())
8: .AddSingleton<IBaz>(_ => new Baz())
9: .AddSingleton<IGux, Gux>();
10:
11: IServiceProvider serviceProvider = services.BuildServiceProvider();
12: Console.WriteLine("serviceProvider.GetService<IFoo>(): {0}",serviceProvider.GetService<IFoo>());
13: Console.WriteLine("serviceProvider.GetService<IBar>(): {0}", serviceProvider.GetService<IBar>());
14: Console.WriteLine("serviceProvider.GetService<IBaz>(): {0}", serviceProvider.GetService<IBaz>());
15: Console.WriteLine("serviceProvider.GetService<IGux>(): {0}", serviceProvider.GetService<IGux>());
16: }
17: }
接下来我们调用ServiceCollection对象的扩展方法BuildServiceProvider得到对应的ServiceProvider对象,然后调用其扩展方法GetService<T>分别获得针对四个接口的服务实例对象并将类型名称其输出到控制台上。运行该程序之后,我们会在控制台上得到如下的输出结果,由此印证ServiceProvider为我们提供了我们期望的服务实例。
1: serviceProvider.GetService<IFoo>(): Foo
2: serviceProvider.GetService<IBar>(): Bar
3: serviceProvider.GetService<IBaz>(): Baz
4: serviceProvider.GetService<IGux>(): Gux
提供一个服务实例的集合
如果我们在调用GetService方法的时候将服务类型指定为IEnumerable<T>,那么返回的结果将会是一个集合对象。除此之外, 我们可以直接调用IServiceProvider如下两个扩展方法GetServeces达到相同的目的。在这种情况下,ServiceProvider将会利用所有与指定服务类型相匹配的ServiceDescriptor来提供具体的服务实例,这些均会作为返回的集合对象的元素。如果所有的ServiceDescriptor均与指定的服务类型不匹配,那么最终返回的是一个空的集合对象。
1: public static class ServiceProviderExtensions
2: {
3: public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
4: public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
5: }
值得一提的是,如果ServiceProvider所在的ServiceCollection包含多个具有相同服务类型(对应ServiceType属性)的ServiceDescriptor,当我们调用GetService方法获取单个服务实例的时候,只有最后一个ServiceDescriptor才是有效的,至于其他的ServiceDescriptor,它们只有在获取服务集合的场景下才有意义。
我们通过一个简单的实例来演示如何利用ServiceProvider得到一个包含多个服务实例的集合。我们在一个控制台应用中定义了如下一个服务接口IFoobar,两个服务类型Foo和Bar均实现了这个接口。在作为程序入口的Main方法中,我们将针针对服务类型Foo和Bar的两个ServiceDescriptor添加到创建的ServiceCollection对象中,这两个ServiceDescriptor对象的ServiceType属性均为IFoobar。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: IServiceCollection serviceCollection = new ServiceCollection()
6: .AddSingleton<IFoobar, Foo>()
7: .AddSingleton<IFoobar, Bar>();
8:
9: IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
10: Console.WriteLine("serviceProvider.GetService<IFoobar>(): {0}", serviceProvider.GetService<IFoobar>());
11:
12: IEnumerable<IFoobar> services = serviceProvider.GetServices<IFoobar>();
13: int index = 1;
14: Console.WriteLine("serviceProvider.GetServices<IFoobar>():");
15: foreach (IFoobar foobar in services)
16: {
17: Console.WriteLine("{0}: {1}", index++, foobar);
18: }
19: }
20: }
21:
22: public interface IFoobar {}
23: public class Foo : IFoobar {}
24: public class Bar : IFoobar {}
在调用ServiceCollection对象的扩展方法BuildServiceProvider得到对应的ServiceProvider对象之后,我们先调用其GetService<T>方法以确定针对服务接口IFoobar得到的服务实例的真实类型就是是Foo还是Bar。接下来我们调用ServiceProvider的扩展方法GetServices<T>获取一组针对服务接口IFoobar的服务实例并将它们的真是类型打印在控制台上。该程序运行后将会在控制台上生成如下的输出结果。
1: serviceProvider.GetService<IFoobar>(): Bar
2: serviceProvider.GetServices<IFoobar>():
3: 1: Foo
4: 2: Bar
获取ServiceProvider自身对象
对于ServiceProvider的服务提供机制来说,还有一个小小的细节值得我们关注,那就是当我们调用GetService或者GetRequiredService方法的时候若将服务类型设定为IServiceProvider,那么得到的对象实际上就是ServiceProvider自身这个对象。与之同理,调用GetServices方法将会返回一个包含自身的集合。如下所示的代码片段体现了ServiceProvider的这个特性。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: IServiceProvider serviceProvider = new ServiceCollection().BuildServiceProvider();
6: Debug.Assert(object.ReferenceEquals(serviceProvider, serviceProvider.GetService<IServiceProvider>()));
7: Debug.Assert(object.ReferenceEquals(serviceProvider, serviceProvider.GetServices<IServiceProvider>().Single()));
8: }
9: }
对泛型的支持
ServiceProvider提供的服务实例不仅限于普通的类型,它对泛型服务类型同样支持。在针对泛型服务进行注册的时候,我们可以将服务类型设定为携带具体泛型参数的“关闭泛型类型”(比如IFoobar<IFoo,IBar>),除此之外服务类型也可以是包含具体泛型参数的“开放泛型类型”(比如IFoo<,>)。前者实际上还是将其视为非泛型服务来对待,后者才真正体现了“泛型”的本质。
比如我们注册了某个泛型服务接口IFoobar<,>与它的实现类Foobar<,>之间的映射关系,当我们指定一个携带具体泛型参数的服务接口类型IFoobar<IFoo,IBar>并调用ServiceProvider的GetService方法获取对应的服务实例时,ServiceProvider会针对指定的泛型参数类型(IFoo和IBar)来解析与之匹配的实现类型(可能是Foo和Baz)并得到最终的实现类型(Foobar<Foo,Baz>)。
我们同样利用一个简单的控制台应用来演示基于泛型的服务注册与提供方式。如下面的代码片段所示,我们定义了三个服务接口(IFoo、IBar和IFoobar<T1,T2>)和实现它们的三个服务类(Foo、Bar个Foobar<T1,T2>),泛型接口具有两个泛型参数类型的属性(Foo和Bar),它们在实现类中以构造器注入的方式被初始化。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: IServiceProvider serviceProvider = new ServiceCollection()
6: .AddTransient<IFoo, Foo>()
7: .AddTransient<IBar, Bar>()
8: .AddTransient(typeof(IFoobar<,>), typeof(Foobar<,>))
9: .BuildServiceProvider();
10:
11: Console.WriteLine("serviceProvider.GetService<IFoobar<IFoo, IBar>>().Foo: {0}", serviceProvider.GetService<IFoobar<IFoo, IBar>>().Foo);
12: Console.WriteLine("serviceProvider.GetService<IFoobar<IFoo, IBar>>().Bar: {0}", serviceProvider.GetService<IFoobar<IFoo, IBar>>().Bar);
13: }
14: }
15:
16: public interface IFoobar<T1, T2>
17: {
18: T1 Foo { get; }
19: T2 Bar { get; }
20: }
21: public interface IFoo {}
22: public interface IBar {}
23:
24: public class Foobar<T1, T2> : IFoobar<T1, T2>
25: {
26: public T1 Foo { get; private set; }
27: public T2 Bar { get; private set; }
28: public Foobar(T1 foo, T2 bar)
29: {
30: this.Foo = foo;
31: this.Bar = bar;
32: }
33: }
34: public class Foo : IFoo {}
35: public class Bar : IBar {}
在作为入口程序的Main方法中,我们创建了一个ServiceCollection对象并采用Transient模式注册了上述三个服务接口与对应实现类型之间的映射关系,对于泛型服务IFoobar<T1,T2>/Foobar<T1,T2>来说,我们指定的是不携带具体泛型参数的开放泛型类型IFoobar<,>/Foobar<,>。利用此ServiceCollection创建出对应的ServiceProvider之后,我们调用后者的GetService方法并指定IFoobar<IFoo,IBar>为服务类型。得到的服务对象将会是一个Foobar<Foo,Bar>对象,我们将它的Foo和Bar属性类型输出于控制台上作为验证。该程序执行之后将会在控制台上产生下所示的输出结果。
1: serviceProvider.GetService<IFoobar<IFoo, IBar>>().Foo: Foo
2: serviceProvider.GetService<IFoobar<IFoo, IBar>>().Bar: Bar
ASP.NET Core中的依赖注入(1):控制反转(IoC)
ASP.NET Core中的依赖注入(2):依赖注入(DI)
ASP.NET Core中的依赖注入(3):服务注册与提取
ASP.NET Core中的依赖注入(4):构造函数的选择与生命周期管理
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【总体设计】
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【解读ServiceCallSite】
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】
ASP.NET Core中的依赖注入(3): 服务的注册与提供的更多相关文章
- ASP.NET Core中的依赖注入(1):控制反转(IoC)
ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...
- ASP.NET Core中的依赖注入(2):依赖注入(DI)
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...
- ASP.NET Core中的依赖注入(4): 构造函数的选择与服务生命周期管理
ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于一个具体的ServiceDescriptor对象来说,如果它的ImplementationI ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】
本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【解读ServiceCallSite 】
通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最 ...
- ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】
到目前为止,我们定义的ServiceProvider已经实现了基本的服务提供和回收功能,但是依然漏掉了一些必需的细节特性.这些特性包括如何针对IServiceProvider接口提供一个Service ...
- ASP.NET Core 中的依赖注入
目录 什么是依赖注入 ASP .NET Core 中使用依赖注入 注册 使用 释放 替换为其它的 Ioc 容器 参考 什么是依赖注入 软件设计原则中有一个依赖倒置原则(DIP),为了更好的解耦,讲究要 ...
- ASP.NET Core 中的 依赖注入介绍
ASP.NET Core 依赖注入 HomeController public class HomeController : Controller { private IStudentReposito ...
- ASP.NET Core 中的依赖注入 [共7篇]
一.控制反转(IoC) ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了 ...
随机推荐
- 【前端性能】高性能滚动 scroll 及页面渲染优化
最近在研究页面渲染及web动画的性能问题,以及拜读<CSS SECRET>(CSS揭秘)这本大作. 本文主要想谈谈页面优化之滚动优化. 主要内容包括了为何需要优化滚动事件,滚动与页面渲染的 ...
- [译]ZOOKEEPER RECIPES-Leader Election
选主 使用ZooKeeper选主的一个简单方法是,在创建znode时使用Sequence和Ephemeral标志.主要思想是,使用一个znode,比如"/election",每个客 ...
- ExtJS 4.2 第一个程序
本篇介绍如何创建一个ExtJS应用程序.并通过创建目录.导入文件.编写代码及分析代码等步骤来解释第一个ExtJS程序. 目录 1. 创建程序 1.1 创建目录建议 1.2 实际目录 1.3 index ...
- 【.net 深呼吸】启动一个进程并实时获取状态信息
地球人和火星人都知道,Process类既可以获取正在运行的进程,也可以启动一个新的进程.在79.77%应用场合,我们只需要让目标进程顺利启动就完事了,至于它执行了啥,有没有出错,啥时候退出就不管了. ...
- 执行 $Gulp 时发生了什么 —— 基于 Gulp 的前端集成解决方案(二)
前言 文章 在windows下安装gulp —— 基于 Gulp 的前端集成解决方案(一) 中,已经完成对 gulp 的安装,由于是window环境,文中特意提到了可以通过安装 gitbash 来代替 ...
- SuperMap iClient for JavaScript 新手入门
地理信息系统(英语:Geographic Information System,缩写:GIS)是一门综合性学科,结合地理学与地图学,已经广泛的应用在不同的领域,是用于输入.存储.查询.分析和显示地理数 ...
- html5的web存储
在html5标准之前,web存储信息需要cookie来完成,但是cookie不适合大量数据存储.因为需要等待服务器响应,所以速度慢/效率低. 本地存储的特点: localstorage是仅存储在用户的 ...
- 掌握javascript中的最基础数据结构-----数组
这是一篇<数据结构与算法javascript描述>的读书笔记.主要梳理了关于数组的知识.部分内容及源码来自原作. 书中第一章介绍了如何配置javascript运行环境:javascript ...
- 微信公众号开发(一)--验证服务器地址的Java实现
现在主流上都用php写微信公众号后台,其实作为后端语言之一的java也可以实现. 这篇文章将对验证服务器地址这一步做出实现. 参考资料:1.慕课网-<初识java微信公众号开发>,2.微信 ...
- html中返回上一页的各种写法【转】
超链接返回上一页代码: <a href="#" onClick="javascript :history.back(-1);">返回上一页</ ...