学习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框架具有两个核心的功能,即服务的注册和提供,这两个功能分别由对应的对象来承载, 它们分别是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
大内老A
了解更多

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 进程间文件锁FileLock详解

转载自 Java 进程间文件锁FileLock详解最近需要在两个进程中对同一个文件进行操作&#xff0c;正好Java 提供了文件锁FileLock类&#xff0c;利用这个类可以控制不同程序(JVM)对同一文件的并发访问&#xff0c;实现进程间文件同步操作。FileLock是java 1.4 版本后出现的一个类…

求瑞年的java程序,java 计算瑞年的方法

任何语言都有可能计算某一年是否为瑞年的方法&#xff0c;也就是说一年有 366 天&#xff0c;每隔4 年就出现一次。最基本的算法如下&#xff1a;if year is divisible by 400 thenis_leap_yearelse if year is divisible by 100 thennot_leap_yearelse if year is divisible b…

购物车的功能——JS源码

此CSS的对应的是“购物车的功能——界面源码”的内容和“购物车的功能——CSS源码”的内容&#xff0c;希望大家不要乱 $(function(){//点击复选框全选或全不选效果$("#allCheckBox").click(function(){var checked$(this).is(":checked");$(".cart_t…

.NET Core应用类型(Portable apps amp; Self-contained apps)

介绍 有许多种方式可以用来考虑构建应用的类型&#xff0c;通常类型用来描述一个特定的执行模型或者基于此的应用。举例说&#xff1a;控制台应用&#xff08;Console Application&#xff09;、Web应用&#xff08;Web Application&#xff09;等等。所有这些类型的应用都可以…

Java NIO系列教程(十二) Java NIO与IO

转载自 Java NIO系列教程&#xff08;十二&#xff09; Java NIO与IO 译文地址 作者&#xff1a;Jakob Jenkov 译者&#xff1a;郭蕾 校对&#xff1a;方腾飞 当学习了Java NIO和IO的API后&#xff0c;一个问题马上涌入脑海&#xff1a; 我应该何时使用IO&#xff0c;何…

php oracle 无查询结果,php - Oracle Insert查询不起作用,也不会抛出任何错误 - 堆栈内存溢出...

嗨&#xff0c;我是Oracle的新手。我试图在PHP中使用oracle将记录插入表中。 但它不起作用&#xff0c;也没有抛出任何错误。以下是我的代码。 请告诉我我做错了什么。$insertSQL "INSERT INTO GL_USR_MAIL_FOLDER(FK_GLUSR_USR_ID, GL_USR_MAIL_FOLDER_NAME) VALUES (:US…

.NET Core 1.0发布:微软开源跨平台大布局序幕

在6月27日的红帽DevNation峰会上&#xff0c;微软正式发布了.NET Core 1.0、ASP.NET 1.0和Entity Framework Core 1.0&#xff0c;这些产品将全部支持Windows、OS X和Linux三种操作系统。其中.NET Core最受瞩目&#xff0c;这是一款跨平台、开源且模块化的.NET平台&#xff0c;…

无缝滚动图片——源码

图片已上传&#xff0c;请到我的资源部去下载&#xff0c;不用积分&#xff0c;——无缝滚动图片http://download.csdn.net/detail/qq_34137397/9665933 </head> <body> <div class"control"><label id"chk_pause"><input type…

Java NIO系列教程(十一) Pipe

转载自 Java NIO系列教程&#xff08;十一&#xff09; Pipe 原文链接 作者&#xff1a;Jakob Jenkov 译者&#xff1a;黄忠 校对&#xff1a;丁一 Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道&#…

配置oracle网络连接命令,配置oracle网络环境

向数据库注册实例的方法有静态注册和动态注册两种。对于静态注册来说&#xff0c;我们可以将一列硬编码在listener.ora文件中。动态注册意味着实例在启动时要定位侦听器并注册到侦听器中。动态注册是向侦听器注册一个实例的首选方法&#xff0c;初始化参数LOCAL_LISTENER会告知…

ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

在上一节&#xff08;ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行&#xff09;中提到ASP.NET Core WebApp 必须含有Startup类,在本节中将重点讲解Startup类以及Middleware(中间件)在Startup类中的使用。 Startup Class Startup Class中含有两个重要方法&#xff1a;Con…

oracle中闪回和回滚,oracle闪回操作详解

Oracle的闪回oracle中为什么会有闪回呢&#xff01;它的作用是什么呢&#xff1f;我们来学习一下闪回吧&#xff01;闪回和回滚异曲同工之妙。一闪回的介绍(1)在Oracle的操作工程中&#xff0c;会不可避免地出现操作失误或者用户失误&#xff0c;例如不小心删除了一个表等&…

Java NIO系列教程(十) Java NIO DatagramChannel

转载自 Java NIO系列教程&#xff08;十&#xff09; Java NIO DatagramChannel 译文链接 作者&#xff1a;Jakob Jenkov 译者&#xff1a;郑玉婷 校对&#xff1a;丁一 Java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议&#xff0c…

在Windows Server 2012 R2 Standard 部署 ASP.NET Core程序

前言&#xff1a; 随着ASP.NET Core 1.0的发布&#xff0c;论坛里相关的文章也越来越多&#xff0c;正好有时间在测试环境上搭建 ASP.NET Core的发布环境&#xff0c;把过程中遇到的问题写给大家&#xff0c;以便有用到的朋友需要。 环境&#xff1a; Windows Server 2012 R2 S…

Github Pages + Jekyll 独立博客一小时快速搭建上线指南

只要一小时&#xff1f;&#xff01; 人生道路上布满了坑&#xff0c;于是有了人生导师。 美丽的地球上布满了坑&#xff0c;于是有了Google Earth。 使用Github Pages搭建独立博客的过程中布满了坑&#xff0c;所以有了这篇指南。 我在自己查找资料搭建的过程中发现了许多大…

Java NIO系列教程(九) ServerSocketChannel

转载自 Java NIO系列教程&#xff08;九&#xff09; ServerSocketChannel 译文链接 作者&#xff1a;Jakob Jenkov 译者&#xff1a;郑玉婷 校对&#xff1a;丁一 Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的Server…

ASP.NET Core 开发-Entity Framework (EF) Core 1.0 Database First

ASP.NET Core 开发-Entity Framework Core 1.0 Database First,ASP.NET Core 1.0 EF Core操作数据库。 Entity Framework Core 1.0 也已经发布了&#xff0c;可以适用于 .NET Core 1.0 及ASP.NET Core 1.0 。 EF Core RC2 时&#xff0c;使用的Code First&#xff1a; http://w…

Oracle 分页语句解释,oracle 分页语句

private static final String QUERYPERPAGESQL "select * from (select m.*, rownum rn from (select * from music order by id) m where rownum < ?) where rn > ?";//该sql语句为每页显示的个数public List queryPerPage(int page) {//page为当前处于第几…

Java NIO系列教程(八) SocketChannel

转载自 Java NIO系列教程&#xff08;八&#xff09; SocketChannel 译文链接 作者&#xff1a;Jakob Jenkov 译者&#xff1a;郑玉婷 校对&#xff1a;丁一 Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel&a…

php 正则获取html标签,php正则取嵌套html标签

$s <<nested tag testhtml;$pattern "/("."]*?)\s*>|"."\s]))?)*\s*\/?>|"."|"."".")/";preg_match_all($pattern, $s, $aMatches, PREG_OFFSET_CAPTURE);function getMatchTags($s, $arr) {$sM…