学习ASP.NET Core,你必须了解无处不在的“依赖注入”
ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要相应的服务提供支持,ASP.NET Core自身提供了一个DI容器来实现针对服务的注册和消费。换句话说,不只是ASP.NET Core底层框架使用的服务是由这个DI容器来注册和提供,应用级别的服务的注册和提供也需要以来这个DI容器,所以正如本文标题所说的——学习ASP.NET Core,你必须了解无处不在的“依赖注入”。
一、依赖注入简介说到依赖注入(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根据注册的服务类型提取相应的服务对象。 二、依赖注入在管道构建过程中的使用在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对象上。 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)时,浏览器会将注入的两个服务对象的类型以下图的方式展现出来。 四、让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中 ,我们依然将这两个服务的注册类型和真实类型之间的匹配关系作为响应内容,所以我们访问这个应用依然会得到如下所示的输出结果。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- asp.net-mvc – MVC – 在同一页面上与多个实体进行模型绑定
- asp.net – 我应该使用WebMatrix构建一个真实世界的网站吗?
- asp.net-mvc-4 – MVC 4中客户URL的自定义OpenIdClient
- ASP.NET MVC的客户端验证:jQuery的验证
- ASP.NET MVC Web应用程序与ASP.NET Web应用程序
- .net – .ToTitleCase不适用于所有大写字符串
- asp.net-mvc – MVC @model的含义
- asp.net-mvc-3 – 用Response调用的Ajax.BeginForm OnFailu
- asp.net-mvc-3 – asp.net mvc 3 razor从IEnumerable获取一
- asp.net-web-api – 在WebAPI中的TaskScheduler.Unobserved
- asp.net – 在Visual Studio编辑器中配置<%%>块
- asp.net – Internet Explorer的操作中止和延迟问
- asp.net-mvc – MVC企业领域 – 好还是坏?
- asp.net – 如何在公历中显示阿拉伯语日期?
- asp.net按钮点击w / javascript“你确定吗?”在
- asp.net-mvc – 将DropDownListFor绑定到字典
- asp.net – 模拟CSRF攻击
- asp.net-mvc – 如何在ELMAH中连接自定义电子邮件
- 将mvc 5应用程序连接到Azure中的ACS?
- asp.net – 有没有比升级到Visual Studio 2010 U