在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注的目光转移到编程层面。在ASP.NET Core应用中基于依赖注入的编程主要涉及到两个方面,它们分别是将服务注册到ServiceCollection中,和采用注入的方式利用ServiceProvider提供我们所需的服务。我们先来讨论ASP.NET Core应用中如何进行服务注册。[本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、服务注册
    系统自动注册的服务
    手工注册的服务
二、以注入的形式提取服务
    启动类型的构造函数和Configure方法种注入服务
    中间件类型的构造函数和Invoke方法中注入服务
    Controller类型的构造函数中注入服务
    View中注入服务
三、与第三方DI框架的整合

一、服务注册

就注册的主体来划分,ASP.NET Core应用中注册的服务大体可以分为两种类型,一种是WebHostBuilder在创建WebHost之前自动注册的服务,这些服务确保了后续管道能够顺利构建并能提供基本的请求处理能力。另一种则是用户根据自身的需要注册的,如果系统自动注册的服务不符合我们的需求,我们也可以注册自己的服务来覆盖它。

系统自动注册的服务

那么系统在构建ASP.NET Core管道的时候到底自行注册了那些服务呢?对于这个问题,我们不用去查看相关的源代码如何编写,而只需要编写如下一个简单的程序就可以将这些服务输出来。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         Console.WriteLine("{0,-30}{1,-15}{2}", "Service Type", "Lifetime", "Implementation");

   6:         Console.WriteLine("-------------------------------------------------------------");

   7:  

   8:         new WebHostBuilder()

   9:             .UseKestrel()

  10:             .Configure(app => { })

  11:             .ConfigureServices(svcs =>

  12:             {

  13:                 IServiceProvider serviceProvider = svcs.BuildServiceProvider();

  14:                 foreach (var svc in svcs)

  15:                 {

  16:                     if (null != svc.ImplementationType)

  17:                     {

  18:                         Console.WriteLine("{0,-30}{1,-15}{2}", svc.ServiceType.Name, svc.Lifetime, svc.ImplementationType.Name);

  19:                         continue;

  20:                     }

  21:                     object instance = serviceProvider.GetService(svc.ServiceType);

  22:                     Console.WriteLine("{0,-30}{1,-15}{2}", svc.ServiceType.Name, svc.Lifetime, instance.GetType().Name);

  23:                 }

  24:  

  25:             })

  26:             .Build();

  27:     }

  28: }

如上面的代码片断所示,我们利用WebHostBuilder创建了一个WebHost对象。在此之前,我们调用扩展方法UseKestrel注册了一个KestrelServer类型的服务器,指定一个空的Action<IApplicationBuilder>对象作为参数调用了它的Configure方法,我们只得到这样的方法调用会创建了一个DelegateStartup对象。我们直接利用ConfigureServices方法得到所有自动注册的服务,并打印出每个服务的注册类型、生命周期模式和实现类型。当我们运行这个程序之后,控制台上将打印出如下图所示的服务列表。对于列出的这些服务,我们是不是看到很多熟悉的身影?

手工注册的服务

如果具体的项目需要采用依赖注入的方式来完成一些业务功能的实现,那就需要在应用初始化的过程中手工注册相应的服务。初次之外,我们也可以采用手工注册服务的方式来覆盖系统自动注册的服务。总的来说,我们可以采用种方式实现对服务的手工注册,其中一种就是按照如下的形式调用WebHostBuilder的ConfigureServices方法来注册服务,而另一种则是将服务注册实现在启动类的ConfigureServices方法中。

注册方式1:

   1: new WebHostBuilder()

   2:     .ConfigureServices(svcs => svcs

   3:         .AddTransient<IFoo, Foo>()

   4:         .AddScoped<IBar, IBar>()

   5:         .AddSingleton<IBaz, Baz>())

   6:     …

注册方式2:

   1: public class Startup

   2: {   

   3:     public void ConfigureServices(IServiceCollection svcs)

   4:     {

   5:         svcs.AddTransient<IFoo, Foo>()

   6:             .AddScoped<IBar, IBar>()

   7:             .AddSingleton<IBaz, Baz>();

   8:     }

   9:     …

  10: }

通过前面的介绍,我们知道这两种方式真正执行服务注册的时机是不同的。第一种形式的服务注册发生在WebHostBuilder创建WebHost之前,包含这些服务的ServiceCollection以及由此创建的ServiceProvider将直接提供给后续创建的WebHost。而第二种形式的服务注册则发生在WebHost初始化过程中,实际上是借助一个ConventionBasedStartup对象来完成的。

二、以注入的形式提取服务

依赖注入的最终目录在于实现以注入的形式来消费预先注册的服务。在一个ASP.NET Core应用中,我们在很多地方都可以采用这种编程方式,我们在前一章中对此也有所提及。经过我的总结,我们常用的依赖注入编程主要应用在如下几个方面:

  • 启动类型的构造函数和Configure方法中定义相应参数以注入的形式获取注册的服务。

  • 中间件类型的构造函数和Invoke方法定义任何参数以注入的形式获取注册的服务。
  • ASP.NET Core MVC应用中Controller类型的构造函数中定义任何参数以注入的形式获取注册的服务。
  • ASP.NET Core MVC应用的View中通过@inject指令直接获取注册的服务。

启动类型的构造函数和Configure方法种注入服务

当我们在定义启动类型的时候,通过调用WebHostBuilder的ConfigureServices方法注册的服务可以在启动类的构造函数中进行注入,而启动类的Configure方法不但可以注入调用WebHostBuilder的ConfigureServices方法注册的服务,也可以注入自身ConfigureServices方法注册的服务。如下所示的代码片断展示了一个比较典型的例子。

   1: new WebHostBuilder()

   2:     .UseKestrel()

   3:     .ConfigureServices(svcs => svcs

   4:         .AddSingleton<IFoo, Foo>()

   5:         .AddSingleton<IBar, Bar>())

   6:     .UseStartup<Startup>()

   7:     …

   8:  

   9: public class Startup

  10: {

  11:     public Startup(IFoo foo, IBar bar)

  12:     {

  13:         Debug.Assert(typeof(Foo).IsInstanceOfType(foo));

  14:         Debug.Assert(typeof(Bar).IsInstanceOfType(bar));

  15:     }

  16:  

  17:     public void ConfigureServices(IServiceCollection svcs)

  18:     {

  19:         svcs.AddTransient<IBaz, Baz>()

  20:             .AddTransient<IGux, Gux>();

  21:     }

  22:  

  23:     public void Configure(IApplicationBuilder app, IFoo foo, IBar bar, IBaz baz, IGux gux)

  24:     {

  25:         Debug.Assert(typeof(Foo).IsInstanceOfType(foo));

  26:         Debug.Assert(typeof(Bar).IsInstanceOfType(bar));

  27:         Debug.Assert(typeof(Baz).IsInstanceOfType(baz));

  28:         Debug.Assert(typeof(Gux).IsInstanceOfType(gux));

  29:     }        

  30: }

中间件类型的构造函数和Invoke方法中注入服务

当我们按照约定定义中间件类型的时候,我们可以在构造函数定义相应的参数来注入通过任何形式注册的服务。如下面的代码片断所示,中间件类型的构造函数和Invoke方法都定义了相应的参数来以注入的形式和获取通过调用WebHostBuilder的ConfigureServices方法注册的两个服务。

   1: new WebHostBuilder()

   2:     .UseKestrel()

   3:     .ConfigureServices(svcs => svcs

   4:         .AddSingleton<IFoo, Foo>()

   5:         .AddSingleton<IBar, Bar>())

   6:     .Configure(app=>app.UseMiddleware<FoobarMiddleware>())

   7:     ...

   8:  

   9: public class FoobarMiddleware

  10: {

  11:     private RequestDelegate _next;

  12:     public FoobarMiddleware(RequestDelegate next, IFoo foo, IBar bar)

  13:     {

  14:         _next = next;

  15:         Debug.Assert(typeof(Foo).IsInstanceOfType(foo));

  16:         Debug.Assert(typeof(Bar).IsInstanceOfType(bar));

  17:     }

  18:  

  19:     public async Task Invoke(HttpContext context, IFoo foo, IBar bar)

  20:     {

  21:         Debug.Assert(typeof(Foo).IsInstanceOfType(foo));

  22:         Debug.Assert(typeof(Bar).IsInstanceOfType(bar));

  23:         await _next(context);

  24:     }

  25: }

Controller类型的构造函数中注入服务

在ASP.NET Core MVC应用中,我们经常在Controller类型的构造函数定义相应的参数来以注入的方式获取预先注册的服务。如下所示的这个HomeController就采用构造器注入的方式获取通过调用WebHostBuilder的ConfigureServices方法注册的两个服务。

   1: new WebHostBuilder()

   2:     .UseKestrel()

   3:     .ConfigureServices(svcs => svcs

   4:         .AddSingleton<IFoo, Foo>()

   5:         .AddSingleton<IBar, Bar>()

   6:         .AddMvc())

   7:     .Configure(app => app.UseMvc())

   8:     ...

   9:  

  10: public class HomeController

  11: {

  12:     public HomeController(IFoo foo, IBar bar)

  13:     {

  14:         Debug.Assert(typeof(Foo).IsInstanceOfType(foo));

  15:         Debug.Assert(typeof(Bar).IsInstanceOfType(bar));

  16:     }

  17:     ...

  18: }

View中注入服务

如果我们在ASP.NET Core MVC应用的View中以注入的方式进行服务消费,我们有两种解决方案。第一种方案就是先按照上面这种方式将服务注入到Controller中,在将注入的服务通过ViewData或者ViewBag传递到View。另一种方式就是按照如下的方式直接使用@inject指令将注入的服务定义成当前View类型的属性。

   1: new WebHostBuilder()                

   2:     .UseKestrel()

   3:     .UseContentRoot(Directory.GetCurrentDirectory())

   4:     .ConfigureServices(svcs => svcs

   5:         .AddSingleton<IFoo, Foo>()

   6:         .AddSingleton<IBar, Bar>()

   7:         .AddMvc())

   8:     .Configure(app => app.UseMvc())

   9:     ...

  10:  

  11: @using System.Reflection

  12: @using System.Diagnostics

  13: @inject IFoo Foo

  14: @inject IBar Baz

  15: @{ 

  16:     Debug.Assert(typeof(Foo).IsInstanceOfType(this.Foo));

  17:     Debug.Assert(typeof(Bar).IsInstanceOfType(this.Bar));

  18: }

三、与第三方DI框架的整合

我们知道启动类型的ConfigureServices方法是可以返回一个ServiceProvider对象的,并且这个对象将直接作为WebHost的Services属性,成为一个全局单例的服务提供者。这个特性可以帮助我们实现与第三方DI框架的整合(比如Castle、Ninject、Autofac等)。在这里我不想“节外生枝”地引入某一个DI框架,而是自行创建一个简单的DI容器来演示这个主题。这个DI容器通过如下所示的Cat类型(这么名字来源于“机器猫”),它直接实现了IServiceProvider接口,所以一个Cat对象同时也是一个ServiceProvider对象。

   1: public class Cat : IServiceProvider

   2: {

   3:     private static readonly Cat                           _instance = new Cat();

   4:     private ConcurrentDictionary<Type, Func<Cat, object>> _registrations = new ConcurrentDictionary<Type, Func<Cat, object>>();

   5:     private IServiceProvider                              _backup;

   6:  

   7:     private Cat()

   8:     {

   9:         _backup = new ServiceCollection().BuildServiceProvider();

  10:     }

  11:  

  12:     public static Cat Instance

  13:     {

  14:         get { return _instance; }

  15:     }

  16:  

  17:     public Cat Register(IServiceCollection svcs)

  18:     {

  19:         _backup = svcs.BuildServiceProvider();

  20:         return this;

  21:     }

  22:  

  23:     public Cat Register(Type serviceType, Func<Cat, object> instanceAccessor)

  24:     {

  25:         _registrations[serviceType] = instanceAccessor;

  26:         return this;

  27:     }

  28:  

  29:     public object GetService(Type serviceType)

  30:     {

  31:         Func<Cat, object> instanceAccessor;

  32:         return _registrations.TryGetValue(serviceType, out instanceAccessor)? instanceAccessor(this): _backup.GetService(serviceType);

  33:     }

  34: }

如上面的代码片断所示,Cat具有一个类型为ConcurrentDictionary<Type, Func<Cat, object>>类型的字段(_registrations)用来保存注册的服务,而服务的注册体现为服务类型与一个提供服务实例的委托对对象的映射,该映射通过调用第一个Register方法重载进行注册。除此之外,我还为这个类型定义了一个IServiceProvider接口类型的字段(_backup),如果实现的GetService方法不能根据指定的服务类型找到一个对应的Func<Cat, object>对象来提供服务对象,它将使用这个作为“后备”的ServiceProvider来提供这个服务。我们采用单例模式来使用Cat,这个单例对象通过只读属性Instance返回。

针对Cat这个DI容器的整体体现在如下这段程序中。如下面的代码片段所示,我们一共注册了三个服务,其中针对IFoo接口的服务直接注册在Cat单例对象上,针对IBar接口的服务通过调用ConfigureServices方法注册到WebHostBuilder上,而针对IBaz接口的服务则通过启动类的ConfiguresServices进行注册。值得注意的是,启动类的ConfigureServices方法返回的ServiceProvider正是这个Cat单例对象,在这之前我们调用它的Register方法将当前的ServiceCollection进行了注册。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         Cat.Instance.Register(typeof(IFoo), _ => new Foo());

   6:         new WebHostBuilder()

   7:             .UseKestrel()

   8:             .ConfigureServices(svcs => svcs.AddSingleton<IBar, Bar>())

   9:             .UseStartup<Startup>()

  10:             .Build()

  11:             .Run();                

  12:     }

  13: }

  14:  

  15: public class Startup

  16: {

  17:     public IServiceProvider ConfigureServices(IServiceCollection svcs)

  18:     {

  19:         return Cat.Instance.Register(svcs.AddSingleton<IBaz, Baz>());

  20:     }

  21:  

  22:     public void Configure(IApplicationBuilder app, IFoo foo, IBar bar, IBaz baz)

  23:     {

  24:         app.Run(async context =>{

  25:             context.Response.ContentType = "text/html";

  26:             await context.Response.WriteAsync($"IFoo => {foo.GetType().Name}<br/>");

  27:             await context.Response.WriteAsync($"IBar => {bar.GetType().Name}<br/>");

  28:             await context.Response.WriteAsync($"IBaz => {baz.GetType().Name}<br/>");

  29:         });

  30:     }

  31: }

我们为启动类的Configure方法定了三个参数以注入的形式获取预先注册的这三个服务对象,并利用注册的中间件将服务的接口类型和真实类型之间的映射作为了响应的内容。我们启动应用并利用浏览器访问目标地址,这个类型映射关系将会按照如图5所示的形式出现在浏览器上。

ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法的更多相关文章

  1. ASP.NET Core中使用自定义MVC过滤器属性的依赖注入

    除了将自己的中间件添加到ASP.NET MVC Core应用程序管道之外,您还可以使用自定义MVC过滤器属性来控制响应,并有选择地将它们应用于整个控制器或控制器操作. ASP.NET Core中常用的 ...

  2. ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

    我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...

  3. ASP.NET Core中的ActionFilter与DI

    一.简介 前几篇文章都是讲ASP.NET Core MVC中的依赖注入(DI)与扩展点的,也许大家都发现在ASP.NET CORE中所有的组件都是通过依赖注入来扩展的,而且面向一组功能就会有一组接口或 ...

  4. ASP.NET Core 中的管道机制

    首先,很感谢在上篇文章 C# 管道式编程 中给我有小额捐助和点赞的朋友们,感谢你们的支持与肯定.希望我的每一次分享都能让彼此获得一些收获,当然如果我有些地方叙述的不正确或不当,还请不客气的指出.好了, ...

  5. [05]ASP.NET Core 中的 Main 方法

    ASP.NET Core 中的 Main 方法 本文作者:梁桐铭- 微软最有价值专家(Microsoft MVP) 文章会随着版本进行更新,关注我获取最新版本 本文出自<从零开始学 ASP.NE ...

  6. ASP.NET Core 中的 Main 方法

    ASP.NET Core 中的 Main 方法 在 ASP.NET Core 项目中,我们有一个名为Program.cs的文件.在这个文件中,我们有一个public static void Main( ...

  7. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  8. ASP.NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

  9. ASP.NET Core中的依赖注入(3): 服务的注册与提供

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...

随机推荐

  1. H5实现摇一摇技术总结

    摇一摇遇到的问题 一.如何对摇晃效果进行反馈 刚开始的处理方式是,摇晃过程中不做任何处理,但后来反馈说这种效果不好,好像就没有摇动一样,如果声音也不响的话,就真的和什么都没发生一样. 后来想了想,加入 ...

  2. 【.net 深呼吸】细说CodeDom(5):类型成员

    前文中,老周已经厚着脸皮介绍了类型的声明,类型里面包含的自然就是类型成员了,故,顺着这个思路,今天咱们就了解一下如何向类型添加成员. 咱们都知道,常见的类型成员,比如字段.属性.方法.事件.表示代码成 ...

  3. Spark踩坑记——Spark Streaming+Kafka

    [TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...

  4. C语言 · 薪水计算

    问题描述 编写一个程序,计算员工的周薪.薪水的计算是以小时为单位,如果在一周的时间内,员工工作的时间不超过40 个小时,那么他/她的总收入等于工作时间乘以每小时的薪水.如果员工工作的时间在40 到50 ...

  5. 【java】Naming.bind和Registry.bind区别

    Naming类和Registry类均在java.rmi包 Naming类通过解析URI绑定远程对象,将URI拆分成主机.端口和远程对象名称,使用的仍是Registry类. public static ...

  6. 【翻译】MongoDB指南/CRUD操作(三)

    [原文地址]https://docs.mongodb.com/manual/ CRUD操作(三) 主要内容: 原子性和事务(Atomicity and Transactions),读隔离.一致性和新近 ...

  7. Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架

    前言: Annotation注解在Android的开发中的使用越来越普遍,例如EventBus.ButterKnife.Dagger2等,之前使用注解的时候需要利用反射机制势必影响到运行效率及性能,直 ...

  8. CRL快速开发框架系列教程五(使用缓存)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  9. 解读发布:.NET Core RC2 and .NET Core SDK Preview 1

    先看一下 .NET Core(包含 ASP.NET Core)的路线图: Beta6: 2015年7月27日 Beta7: 2015年9月2日 Beta8: 2015年10月15日 RC1: 2015 ...

  10. [原] KVM 虚拟化原理探究(2)— QEMU启动过程

    KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...