ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要相应的服务提供支持,ASP.NET Core自身提供了一个DI容器来实现针对服务的注册和消费。换句话说,不只是ASP.NET Core底层框架使用的服务是由这个DI容器来注册和提供,应用级别的服务的注册和提供也需要以来这个DI容器,所以正如本文标题所说的——学习ASP.NET Core,你必须了解无处不在的“依赖注入”。

目录一、依赖注入简介

二、依赖注入在管道构建过程中的应用

三、依赖服务的注册与注入

四、让Startup的ConfigureServices方法返回一个ServiceProvider

五、ASP.NET Core默认注册了哪些服务

六、ASP.NET Core MVC中的依赖注入

一、依赖注入简介


说到依赖注入(Dependency Injection,以下简称DI),就必须说IoC(Inverse of Control),很多人将这两这混为一谈,其实这是两个完全不同的概念,或者是不同“层次”的两个概念,我曾在《控制反转(IoC)》和《依赖注入(DI)》对这两个概念做过详细介绍。ASP.NET Core使用的DI框架由“Micorosoft.Extensions.DependencyInjection”这个NuGet包来承载,我们也可以非ASP.NET Core应用或者你自己的框架上单独使用它,对于这个DI框架的设计、实现以及编程相关的内容,我在系列文章《ASP.NET Core 中的依赖注入 [共7篇]》对此有过详细的介绍。

DI框架具有两个核心的功能,即服务的注册和提供,这两个功能分别由对应的对象来承载, 它们分别是ServiceCollectionServiceProvider。如下图所示,我们将相应的服务以不同的生命周期模式(Transient、Scoped和Singleton)注册到ServiceCollection对象之上,在利用后者创建的ServiceProvider根据注册的服务类型提取相应的服务对象。

二、依赖注入在管道构建过程中的使用


在ASP.NET Core管道的构架过程中主要涉及三个对象/类型,作为宿主的WebHost和他的创建者WebHostBuilder,以及注册到WebHostBuilder的Startup类型。 如下的代码片段体现了启动ASP.NET Core应用采用的典型编程模式:我们首先创建一个WebHostBuilder对象,并将采用Server和Startup类型注册到它之上。在调用Build方法创建WebHost之前,我们还可以调用相应的方式做其他所需的注册工作。当我们调用WebHost的Run方法之后,后者会利用注册的Startup类型来构建完整的管道。那么在管道的构建过程中,DI是如何被应用的呢?

1: new WebHostBuilder()

2:     .UseKestrel()

3:     .UseStartup<Startup>()

4:     .Xxx

5:     .Build()

6:     .Run()

DI在管道ASP.NET Core管道构建过程中的应用基本体现下面这个序列图中。当我们调用WebHostBuilder的Build方法创建对应的WebHost的时候,前者会创建一个ServiceCollection对象,并将一系列预定义的服务注册在它之上。接下来WebHostBuilder会利用这个ServiceCollection对象创建出对应的ServieProvider,这个ServiceProvider和ServiceCollection对象会一并传递给最终创建WebHost对象。当我们调用WebHost的Run方法启动它的时候,如果注册的Startup是一个实例类型,它会利用这个ServiceProvider以构造器注入的方式创建对应的Startup对象。说的具体一点,我们注册的Startup类型的构造函数是允许定义参数的,但是参数类型必须是预先注册到ServiceCollection中的服务类型。

注册的Startup方法可以包含一个可选的ConfigureServices方法,这个方法具有一个类型为IServiceCollection接口的参数。WebHost会将WebHostBuilder传递给它的ServiceCollection作为参数调用这个ConfigureServices方法,而我们则利用这个方法将注册的中间件和应用所需的服务注册到这个ServiceCollection对象上。在这之后,所有需要的服务(包括框架和应用注册的服务)都注册到这个ServiceCollection上面,WebHost会利用它创建一个新的ServiceProvider。WebHost会利用这个ServiceProvider对象以方法注入的方式调用Startup对象/类型的Configure方法,最终完成你对整个管道的建立。换句话会说,定义在Startup类型中旨在用于注册Middleware的Configure方法除了采用IApplicationBuilder作为第一个参数之外,它依然可以采用注册的任何一个服务类型作为后续参数的类型。

服务的注册除了是现在注册的Startup类型的ConfigureServices方法之外,实际上还具有另一个实现方式,那就是调用WebHostBuilder具有如下定义的ConfigureServices方法。当WebHostBuilder创建出ServiceCollection对象并完成了默认服务的注册后,我们通过调用这个方法所传入的所有Action<IServiceCollection>对象将最终应用到这个ServiceCollection对象上。

1: public interface IWebHostBuilder

2: {

3:     IWebHostBuilder ConfigureServiecs(Action<IServiceCollection> configureServices);

4: }

值得一提的是,Startup类型的ConfigureServices方法是允许具有一个IServiceProvider类型的返回值,如果这个方法返回一个具体的ServiceProrivder,那么WebHost将不会利用ServiceCollection来创建ServiceProvider,而是直接使用这个返回的ServiceProvider来调用Startup对象/类型的Configure方法。这实际上是一个很有用的扩展点,我们使用它可以实现针对其它DI框架的集成。

三、依赖服务的注册与注入


接下来我们通过一个实例来演示如何利用Startup类型的ConfigureServices来注册服务,以及发生在Startup类型上的两种依赖注入形式。如下面的代码片段所示,我们定义了两个服务接口(IFoo和IBar)和对应的实现类型(Foo和Bar)。其中其中服务Foo是通过调用WebHostBuilder的ConfigureServices方法进行注册的,而另一个服务Bar的注册则发生在Startup的ConfigureServices方法上。对于Startup来说,它具有一个类型为IFoo的只读属性,该属性在构造函数利用传入的参数进行初始化,不用是这体现了针对Startup的构造器注入。Startup的Configure方法除了ApplicationBuilder作为第一个参数之外,还具有另一个类型为IBar的参数,我们利用它来演示方法注入。

1: public interface IFoo { }

2: public interface IBar { }

3: public class Foo : IFoo { }

4: public class Bar : IBar { }

5:

6: public class Program

7: {

8:     public static void Main(string[] args)

9:     {

10:         new WebHostBuilder()

11:             .ConfigureServices(services=>services.AddSingleton<IFoo, Foo>())

12:             .UseKestrel()

13:             .UseStartup<Startup>()

14:             .Build()

15:             .Run();

16:     }

17: }

18: public class Startup

19: {

20:     public IFoo Foo { get; private set; }

21:     public Startup(IFoo foo)

22:     {

23:         this.Foo = foo;

24:     }

25:     public void ConfigureServices(IServiceCollection services)

26:     {

27:         services.AddTransient<IBar, Bar>();

28:     }

29:

30:     public void Configure(IApplicationBuilder app, IBar bar)

31:     {

32:         app.Run(async context =>

33:         {

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

35:             await context.Response.WriteAsync($"IFoo=>{this.Foo}<br/>");

36:             await context.Response.WriteAsync($"IBar=>{bar}");

37:         });

38:     }

39: }

在Startup的Configure方法中,我们调用ApplicationBulder的Run方法注册了一个Middleware,后者将两个注入的服务的类型作为响应的内容。当我们运行这个应用,并利用浏览器访问默认的监听地址(http://localhost:5000)时,浏览器会将注入的两个服务对象的类型以下图的方式展现出来。

四、让Startup的ConfigureServices方法返回一个ServiceProvider


我们说注册的Startup类型的ConfigureServices允许返回一个ServiceProvider,这个特性的重要意义在于它使我们可以实现与第三方DI框架(比如Unity、Castle、Ninject和AutoFac等)的集成。我们照例采用一个实例对此做一个演示,简单起见,我们并不会真正利用某个具体的DI框架来创建这个ServiceProvider,而是直接创建一个新的ServiceCollection来创建它,为此我们对上面这个程序进行了如下的改写。

1: public class Program

2: {

3:     public static void Main(string[] args)

4:     {

5:         new WebHostBuilder()

6:             .UseKestrel()

7:             .UseStartup<Startup>()

8:             .Build()

9:             .Run();

10:     }

11: }

12: public class Startup

13: {

14:     public IServiceProvider ConfigureServices(IServiceCollection services)

15:     {

16:         IServiceCollection newServices = new ServiceCollection();

17:         foreach (ServiceDescriptor service in services)

18:         {

19:             newServices.Add(service);

20:         }

21:

22:         return newServices

23:             .AddSingleton<IFoo, Foo>()

24:             .AddSingleton<IBar, Bar>()

25:             .BuildServiceProvider();

26:     }

27:

28:     public void Configure(IApplicationBuilder app, IFoo foo, IBar bar)

29:     {

30:         app.Run(async context =>

31:         {

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

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

34:             await context.Response.WriteAsync($"IBar=>{bar}");

35:         });

36:     }

37: }

如上面的代码片段所示,在Startup的ConfigureServices方法中,我们通过拷贝注册到现有ServiceCollection的所有ServiceDescriptor生成了一个新的ServiceCollection,两个服务Foo和Bar被注册到后者之上。该方法最终返回由这个新ServiceCollection创建的ServiceProvider。在另一个Configure方法中,我们添加了两个类型分别为IFoo和IBar的参数,并以相同的方式将它们的真实类型名称和注册服务类型的映射关系作为响应内容。程序运行之后,我们利用浏览器进行访问照样会得到一样的结果。

五、ASP.NET Core默认注册了哪些服务


WebHostBuilder在创建ServiceCollection之后,会注册一些默认的服务。这些服务和我们自行注册的服务并没有任何区别,只要我们知道对应的服务类型,就可以通过注入的方式获取并使用它们。那么具体由哪些服务被默认注册了呢?如下所示的是这些服务对应的类型,至于这些服务各自有何用途,我们在这里就先不深究了。

  • IHostingEnvironment

  • ILoggerFactory

  • ILogger<>

  • IApplicationBuilderFactory

  • IHttpContextFactory

  • IOptions<>

  • DiagnosticSource

  • DiagnosticListener

  • IStartupFilter

  • ObjectPoolProvider

  • IStartup

如果我们需要这些预注册的服务,我们可以按照我们熟悉的方式以依赖注入的方式来使用它们。如下面的代码片段所示,我们在Startup的Configure方法中直接采用方法注入的方式来使用这些预定义的服务。

1: public class Program

2: {

3:     public static void Main(string[] args)

4:     {

5:         new WebHostBuilder()

6:             .UseKestrel()

7:             .UseStartup<Startup>()

8:             .Build()

9:             .Run();

10:     }

11: }

12: public class Startup

13: {

14:     public void Configure(

15:         IApplicationBuilder app,

16:         IHostingEnvironment environment,

17:         ILoggerFactory loggerFactory,

18:         IHttpContextFactory httpContextFactory,

19:         DiagnosticSource diagnosticSource,

20:         DiagnosticListener diagnosticListener)

21:     {

22:         app.Run(async context =>

23:         {

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

25:             await context.Response.WriteAsync($"IApplicationBuilder=>{app}<br/>");

26:             await context.Response.WriteAsync($"IHostingEnvironment=>{environment}<br/>");

27:             await context.Response.WriteAsync($"ILoggerFactory=>{loggerFactory}<br/>");

28:             await context.Response.WriteAsync($"IHttpContextFactory=>{httpContextFactory}<br/>");

29:             await context.Response.WriteAsync($"DiagnosticSource=>{diagnosticSource}<br/>");

30:             await context.Response.WriteAsync($"DiagnosticListener=>{diagnosticListener}");

31:         });

32:     }

33: }

由于Configure方法注册的Middleware直接将注入服务的注册类型和真实类型的映射关系作为响应内容,所以我们访问应用会的得到如下所示的输出结果。

六、ASP.NET Core MVC中的依赖注入


对于ASP.NET MVC 5机器以及之前的版本,在默认情况下定义的Controller都具有一个要求,那就是Controller类型必须具有一个无参数的默认构造函数,否则Controller实例将无法激活。对于自身具有依赖注入功能的ASP.NET Core MVC来说,定义Controller将没有了这个限制。对于预注册的服务,我们完全可以采用构造器注入的方式在定义的Controller中使用它们。作为演示,我们对上面这个应用作了如下的改写。

1: public class Program

2: {

3:     public static void Main(string[] args)

4:     {

5:         new WebHostBuilder()

6:             .UseKestrel()

7:             .ConfigureServices(services=>services

8:                 .AddSingleton<IFoo,Foo>()

9:                 .AddSingleton<IBar,Bar>()

10:                 .AddMvc())

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

12:             .Build()

13:             .Run();

14:     }

15: }

16:

17: public class HomeController

18: {

19:     public IFoo Foo { get; private set; }

20:     public IBar Bar { get; private set; }

21:

22:     public HomeController(IFoo foo, IBar bar)

23:     {

24:         this.Foo = foo;

25:         this.Bar = bar;

26:     }

27:

28:     [HttpGet("/")]

29:     public string Index()

30:     {

31:         this.HttpContext.Response.ContentType = "text/html";

32:         return $"IFoo=>{this.Foo}<br/>IBar=>{this.Bar}";

33:     }

34: }

上面这个代码与之前有一个显著的区别,那就是我们根本就没有定义Startup类型,我们将原本实现在它的两个方法(ConfigureServices和Configure)中的功能移植到了WebHostBuilder的同名方法中,这两种形式的编程方式其实是等效的。在调用ConfigureServices方法的时候,我们除了注册MVC相关的服务之外,Foo和Bar这两个服务也一并进行了注册。至于另一个Configure方法,我们直接调用其扩展方法MVC注册与MVC相关的Middleware。

我们定义了一个默认的HomeController,它具有两个类型分别为IFoo和IBar的只读属性,后者在构造函数由传入的参数进行初始化,我们知道这是构造器注入的编程方式。在Action方法Index中 ,我们依然将这两个服务的注册类型和真实类型之间的匹配关系作为响应内容,所以我们访问这个应用依然会得到如下所示的输出结果。


请扫描此二维码或者搜索“大内老A”关注蒋金楠(Artech)微信公众帐号,你将会得到及时的高质量技术文章推送信息。

内容转载自公众号

大内老A
了解更多

学习ASP.NET Core,你必须了解无处不在的“依赖注入”相关推荐

  1. .NET Core ASP.NET Core Basic 1-2 控制反转与依赖注入

    本节内容为控制反转与依赖注入 简介 控制反转IOC 这个内容事实上在我们的C#高级篇就已经有所讲解,控制反转是一种设计模式,你可以这样理解控制反转,假设有一个人他有一部A品牌手机,他用手机进行听歌.打 ...

  2. ASP.NET Core如何在ActionFilterAttribute里做依赖注入

    点击蓝字 关注我 在ASP.NET Core里,我们可以使用构造函数注入很方便地对Controller,ViewComponent等部件做依赖注入.但是如何给过滤器ActionFilterAttrib ...

  3. 控制反转_.NET Core ASP.NET Core Basic 12 控制反转与依赖注入

    本节内容为控制反转与依赖注入 简介 控制反转IOC 这个内容事实上在我们的C#高级篇就已经有所讲解,控制反转是一种设计模式,你可以这样理解控制反转,假设有一个人他有一部A品牌手机,他用手机进行听歌.打 ...

  4. 从明面上学习ASP.NET Core

    一.前言     这篇文章就是从能看到地方去学习Core,没有很深奥,也没有很难懂,现在我们开始吧. 二.构建项目,引发思考     创建项目的步骤真的很简单,你要是不会,我真也没法了,我这是创建的M ...

  5. 学习ASP.NET Core Razor 编程系列九——增加查询功能

    原文:学习ASP.NET Core Razor 编程系列九--增加查询功能 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.N ...

  6. 学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二--添加一个实体 学习ASP.NET ...

  7. 学习ASP.NET Core Razor 编程系列十八——并发解决方案

    原文:学习ASP.NET Core Razor 编程系列十八--并发解决方案 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP. ...

  8. 学习ASP.NET Core Razor 编程系列十三——文件上传功能(一)

    原文:学习ASP.NET Core Razor 编程系列十三--文件上传功能(一) 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习A ...

  9. 学习ASP.NET Core Razor 编程系列三——创建数据表及创建项目基本页面

    原文:学习ASP.NET Core Razor 编程系列三--创建数据表及创建项目基本页面 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 ...

最新文章

  1. seq2seq模型_直观理解并使用Tensorflow实现Seq2Seq模型的注意机制
  2. DataTables 表格固定栏使用方法
  3. 字符串(一):char 数组
  4. 使用Ubuntu 12.04作为日常电脑环境
  5. layui 数据表格下拉框_layui-table-column-select(layui数据表格可搜索下拉框select)
  6. 新松机器人电气三天考核_比技能更比匠心,3天内高手的这些作品令人惊叹……...
  7. [postgresql]postgresql自定义函数查询ETL作业依赖的实例
  8. threejs 三面体_Three.js基础探寻五——正二十面体、圆环面等
  9. 小米笔记本安装系统 声卡驱动安装不上
  10. 易基因 | 精准医学:DNA甲基化图谱在发现和精确诊断神经肿瘤领域的应用
  11. 前端开发神器VS Code安装教程
  12. 【blue bridge cup】笔记
  13. workbench 左侧导航栏消失解决方案
  14. 英语专业找计算机工作好找吗,我是商务英语专业,工作好找吗?
  15. bzoj 1232 [Usaco2008Nov]安慰奶牛cheer
  16. 2020年最新开发者调查报告,你不可不知的内容
  17. [408] NOTES on OS -计算机考研408笔记-操作系统
  18. 今天博客抽风了,我也抽风了
  19. ubuntu 安装gitk
  20. 【使用stripTrailingZeros()函数将BigDecimal去0变为科学计数法7E+1】

热门文章

  1. java.lang.NoClassDefFoundError: org.ksoap2.serialization.SoapObject
  2. 算法面试:精选微软等公司经典的算法面试100题 第26-35题
  3. 如何延迟一个 Task 的执行 ?
  4. .NET 6 中的 ConfigurationManager
  5. 有BUG!!!慎用default文本
  6. dotnet中的counter
  7. EF Core事务提交,分布式事务
  8. 女神节爆猛料!. NET程序员男女比例公布!
  9. WPF 从 .net core 3.1 到 .net 5.0
  10. WeihanLi.Npoi 1.10.0 更新日志