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

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

image

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

在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中的服务类型。

image

注册的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对象上。

2: {
   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的参数,我们利用它来演示方法注入。

2: interface IBar { }
   4: class Bar : IBar { }
   6: class Program
   7: {
   8:     static void Main(string[] args)
   9:     {
  10:           11:             .ConfigureServices(services=>services.AddSingleton<IFoo,Foo>())
  12:             .UseKestrel()
  13:             .UseStartup<Startup>()
  14:             .Build()
  15:             .Run();
  16:     }
  17: }
  18: class Startup
  19: {
  20:     public IFoo Foo { get; private set; }
  21:     public Startup(IFoo foo)
  22:     {
  23:         this.Foo = foo;
  24:     }    
  25:     void ConfigureServices(IServiceCollection services)
  26:     {
  27:         services.AddTransient<IBar,Bar>();
  28:     }
  29:     
  30:     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)时,浏览器会将注入的两个服务对象的类型以下图的方式展现出来。

image

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

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

5: 6: .UseKestrel()
   8:             .Build()
  10:     }
  12:   13: {   
  15:     {
  17:         foreach (ServiceDescriptor service in services)
  19:             newServices.Add(service);
  21:  
  23:             .AddSingleton<IFoo,Foo>()
  25:             .BuildServiceProvider();
  27:     
  30:         app.Run(async context =>
  32:             context.Response.ContentType =   33:             await context.Response.WriteAsync($"IFoo=>{foo}<br/>");
  36:     }
  13: {     
  15:         IApplicationBuilder app,
  21:     {
  23:         {
  26:             await context.Response.WriteAsync($"IHostingEnvironment=>{environment}<br/>");
  28:             await context.Response.WriteAsync($"IHttpContextFactory=>{httpContextFactory}<br/>");
  30:             await context.Response.WriteAsync($"DiagnosticListener=>{diagnosticListener}");
  32:     }
   7:             .ConfigureServices(services=>services
   9:                 .AddSingleton<IBar,Bar>()
  11:             .Configure(app=>app.UseMvc())
  13:             .Run();
  15: }
  17: class HomeController
  19:     public IBar Bar { get;   22:     public HomeController(IFoo foo,1)">  23:     {
  27:  
  29:     string Index()
  31:         this.HttpContext.Response.ContentType =   32:         return $"IFoo=>{this.Foo}<br/>IBar=>{this.Bar}";
  34: }

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

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

image

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发、订阅和处理的流程。这种实现太简单了,百十行代码就展示了一个基本工作原理。然而,要将这样的解决方案运用到实际生产环境,还有很长的路要走。今天,我们就研究一下在事件处理器中,对象生命周期的管理问题。事实上,不仅仅是在事件处理器
上文已经介绍了Identity Service的实现过程。今天我们继续,实现一个简单的Weather API和一个基于Ocelot的API网关。 回顾 《Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(一)》 Weather API Weather
最近我为我自己的应用开发框架Apworks设计了一套案例应用程序,并以Apache 2.0开源,开源地址是:https://github.com/daxnet/apworks-examples,目的是为了让大家更为方便地学习和使用.NET Core、最新的前端开发框架Angular,以及Apwork
HAL(Hypertext Application Language,超文本应用语言)是一种RESTful API的数据格式风格,为RESTful API的设计提供了接口规范,同时也降低了客户端与服务端接口的耦合度。很多当今流行的RESTful API开发框架,包括Spring REST,也都默认支
在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅、通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现。接下来对于事件驱动型架构的讨论,就需要结合一个实际的架构案例来进行分析。在领域驱动设计的讨论范畴,CQRS架构本身就是事件驱动的,因此,
HAL,全称为Hypertext Application Language,它是一种简单的数据格式,它能以一种简单、统一的形式,在API中引入超链接特性,使得API的可发现性(discoverable)更强,并具有自描述的特点。使用了HAL的API会更容易地被第三方开源库所调用,并且使用起来也很方便
何时使用领域驱动设计?其实当你的应用程序架构设计是面向业务的时候,你已经开始使用领域驱动设计了。领域驱动设计既不是架构风格(Architecture Style),也不是架构模式(Architecture Pattern),它也不是一种软件开发方法论,所以,是否应该使用领域驱动设计,以及什么时候使用
《在ASP.NET Core中使用Apworks快速开发数据服务》一文中,我介绍了如何使用Apworks框架的数据服务来快速构建用于查询和管理数据模型的RESTful API,通过该文的介绍,你会看到,使用Apworks框架开发数据服务是何等简单快捷,提供的功能也非常多,比如对Hypermedia的
在上一讲中,我们已经完成了一个完整的案例,在这个案例中,我们可以通过Angular单页面应用(SPA)进行登录,然后通过后端的Ocelot API网关整合IdentityServer4完成身份认证。在本讲中,我们会讨论在当前这种架构的应用程序中,如何完成用户授权。 回顾 《Angular SPA基于
Keycloak是一个功能强大的开源身份和访问管理系统,提供了一整套解决方案,包括用户认证、单点登录(SSO)、身份联合、用户注册、用户管理、角色映射、多因素认证和访问控制等。它广泛应用于企业和云服务,可以简化和统一不同应用程序和服务的安全管理,支持自托管或云部署,适用于需要安全、灵活且易于扩展的用
3月7日,微软发布了Visual Studio 2017 RTM,与之一起发布的还有.NET Core Runtime 1.1.0以及.NET Core SDK 1.0.0,尽管这些并不是最新版,但也已经从preview版本升级到了正式版。所以,在安装Visual Studio 2017时如果启用了
在上文中,我介绍了如何在Ocelot中使用自定义的中间件来修改下游服务的response body。今天,我们再扩展一下设计,让我们自己设计的中间件变得更为通用,使其能够应用在不同的Route上。比如,我们可以设计一个通用的替换response body的中间件,然后将其应用在多个Route上。 O
不少关注我博客的朋友都知道我在2009年左右开发过一个名为Apworks的企业级应用程序开发框架,旨在为分布式企业系统软件开发提供面向领域驱动(DDD)的框架级别的解决方案,并对多种系统架构风格提供支持。这个框架的开发和维护我坚持了很久,一直到2015年,我都一直在不停地重构这个项目。目前这个项目在
好吧,这个题目我也想了很久,不知道如何用最简单的几个字来概括这篇文章,原本打算取名《Angular单页面应用基于Ocelot API网关与IdentityServer4ʺSP.NET Identity实现身份认证与授权》,然而如你所见,这样的名字实在是太长了。所以,我不得不缩写“单页面应用”几个字
在前面两篇文章中,我介绍了基于IdentityServer4的一个Identity Service的实现,并且实现了一个Weather API和基于Ocelot的API网关,然后实现了通过Ocelot API网关整合Identity Service做身份认证的API请求。今天,我们进入前端开发,设计
Ocelot是ASP.NET Core下的API网关的一种实现,在微服务架构领域发挥了非常重要的作用。本文不会从整个微服务架构的角度来介绍Ocelot,而是介绍一下最近在学习过程中遇到的一个问题,以及如何使用中间件(Middleware)来解决这样的问题。 问题描述 在上文中,我介绍了一种在Angu
在大数据处理和人工智能时代,数据工厂(Data Factory)无疑是一个非常重要的大数据处理平台。市面上也有成熟的相关产品,比如Azure Data Factory,不仅功能强大,而且依托微软的云计算平台Azure,为大数据处理提供了强大的计算能力,让大数据处理变得更为稳定高效。由于工作中我的项目
在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容。下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了。其中绿色的部分就是上文中新实现的部分,包括一个简单的Event Store,一个事件处理器执行上下文的接
在之前《在ASP.NET Core中使用Apworks快速开发数据服务》一文的评论部分,.NET大神张善友为我提了个建议,可以使用Compile As a Service的Roslyn为语法解析提供支持。在此非常感激友哥给我的建议,也让我了解了一些Roslyn的知识。使用Roslyn的一个很大的好处
很长一段时间以来,我都在思考如何在ASP.NET Core的框架下,实现一套完整的事件驱动型架构。这个问题看上去有点大,其实主要目标是为了实现一个基于ASP.NET Core的微服务,它能够非常简单地订阅来自于某个渠道的事件消息,并对接收到的消息进行处理,于此同时,它还能够向该渠道发送事件消息,以便