ASP.NET Core 1.0基礎之依賴注入
來自: http://www.cnblogs.com/yanbinliu/p/5170737.html
來源https://docs.asp.net/en/latest/fundamentals/dependency-injection.htmlASP.NET Core 1.0在設計上原生就支持和有效利用依賴注入。在Startup類中,應用可以通過將框架內嵌服務注入到方法中來使用他們;另一方面,你也可以配置服務來注入使用。默認的服務容器只提供了最小的特性集合,所以并不打算取代其他的IoC容器。
什么是依賴注入DI
依賴注入是為了達到解耦對象和其依賴的一項技術。一個類為了完成自身某些操作所需的對象是通過某種方式提供的,而不是使用靜態引用或者直接實例化。通常情況下,類通過構造器來聲明其依賴,遵循 顯式依賴原則 。這種方式稱作構造器注入。
當以DI思想來設計類時,這些類更加松耦合,因為他們不直接硬編碼的依賴其合作者。這遵循了依賴倒置原則,即高層模塊不應依賴底層模塊,兩者都應依賴抽象。類在構建時所需是抽象(如接口interface),而不是具體的實現。把依賴抽離成接口,把這些接口的實現作為參數也是 策略設計模式 的例子。
當一個系統使用DI來設計時,很多類通過構造器或者屬性來添加依賴,這樣就很方便有一個專門的類來創建這些類以及他們相關的依賴。這樣的類稱之為“容器”或者“IoC容器”或“DI容器”。一個容器本質上是一個工廠,來給請求者提供類型實例。如果給定類型聲明了自身依賴,容器也配置了來提供這些依賴類型,那么它會創建這些依賴作為請求實例的一部分。通過這種方式可以為 類提供復雜的依賴圖,而不需要任何硬編碼的對象依賴。除了創建依賴對象外,容器一般還管理應用內的對象生命周期。
ASP.NET Core 1.0提供了一個簡單的內置容器(以IServiceProvider為代表),默認支持構造器注入,這樣ASP.NET可以通過DI使某些服務可用。ASP.NET把它所管理的類型稱之為服務。本文的剩下部分,服務即指ASP.NET IoC容器所管理的類型。你可以在Startup類中的ConfigureServices 方法中配置內置的容器服務。
使用框架提供的服務
ConfigureServices方法負責定義應用使用的服務,包括平臺特性服務如EF和ASP.NET MVC。最初提供給ConfigureServices的IServiceCollection只有少數服務。默認web模板提供了怎么通過擴展方法來添加額外服務到容器的例子,如AddEntityFramework, AddIdentity, 和AddMVC。
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddEntityFramework() .AddSqlServer() .AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); // Add application services. services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>();
}</pre>
ASP.NET提供的特性和中間件遵循使用一個AddService擴展方法的約定,來注冊該特性使用的所需的所有服務。
Note:你可以在Startup方法中請求某些framework-provided服務,詳見應用啟動 Application Startup
當然,除了配置框架提供的各種服務,你也可以配置自己定義的服務。
注冊自定義服務
在默認web模板中,有如下兩個服務被添加到IServiceCollection中
// Add application services. services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>();AddTransient方法將抽象類型映射為實體服務,對于每個請求這都單獨實例化,這稱作服務的生命周期。額外生命周期選項如下。對于每個注冊的服務選擇合適的生命周期是很重要的。是對每個請求類都提供一個新的實例化服務?還是在給定web請求內只實例化一次?還是在應用周期內只有單例?
在本文的例子中,有個簡單的CharacterController來顯示Character姓名,在Index方法中顯示已存儲的Character(如果沒有則創建)。雖然注冊了EF服務,但本例持久化沒有使用數據庫。具體的數據獲取服務抽象到了ICharacterRepository接口實現中,這遵從了 倉儲模式 。在構造器中請求ICharacterRepository參數,并將其賦給私有變量,來根據需要獲取Character。
using System.Linq; using DependencyInjectionSample.Interfaces; using DependencyInjectionSample.Models; using Microsoft.AspNet.Mvc;namespace DependencyInjectionSample.Controllers { public class CharactersController : Controller { private readonly ICharacterRepository _characterRepository;
public CharactersController(ICharacterRepository characterRepository) { _characterRepository = characterRepository; } // GET: /characters/ public IActionResult Index() { var characters = _characterRepository.ListAll(); if (!characters.Any()) { _characterRepository.Add(new Character("Darth Maul")); _characterRepository.Add(new Character("Darth Vader")); _characterRepository.Add(new Character("Yoda")); _characterRepository.Add(new Character("Mace Windu")); characters = _characterRepository.ListAll(); } return View(characters); } }</pre>
接口ICharacterRepository只簡單定義了兩個方法,Controller通過其來操作Charcter實例。
using System.Collections.Generic; using DependencyInjectionSample.Models;namespace DependencyInjectionSample.Interfaces { public interface ICharacterRepository { IEnumerable<Character> ListAll(); void Add(Character character); } }</pre>
接口有具體類型CharacterRepository來實現,在運行時被使用。
Note:CharacterRepository類只是使用DI的普通例子,你可以對應用所有的服務使用DI,而不僅僅是“倉儲”和數據獲取類。
</div>
using System.Collections.Generic; using System.Linq; using DependencyInjectionSample.Interfaces;namespace DependencyInjectionSample.Models { public class CharacterRepository : ICharacterRepository { private readonly ApplicationDbContext _dbContext;
public CharacterRepository(ApplicationDbContext dbContext) { _dbContext = dbContext; } public IEnumerable<Character> ListAll() { return _dbContext.Characters.AsEnumerable(); } public void Add(Character character) { _dbContext.Characters.Add(character); _dbContext.SaveChanges(); } }
}</pre>
請注意,CharacterRepository在其構造器中請求了ApplicationDbContext實例。這種鏈式的依賴注入是很常見的,被依賴本身又有自己的依賴。容器來負責已樹形的方式來解析所有這些依賴,并返回解析完成的服務。
Note:創建請求對象,以及其依賴,其依賴的依賴,有時這被稱之為 對象圖 。同樣的,需要解析的對象集合稱之為 依賴樹 或者 依賴圖 。
在本例中,ICharacterRepository和ApplicationDbContext都必須在ConfigureServices中注冊。ApplicationDbContext是通過擴展方法AddEntityFramework來配置,它包括添加DbContext (AddDbContext
)的一個擴展。倉儲的注視在在ConfigureServices方法的結尾。 services.AddTransient<ISmsSender, AuthMessageSender>(); services.AddScoped<ICharacterRepository, CharacterRepository>();// Show different lifetime options services.AddTransient<IOperationTransient, Operation>();</pre>
EF contexts需要使用scoped生命周期來添加到服務容器。如果你使用了上面的helper方法,這是已經處理好的。使用EF的倉儲服務應該使用同樣的生命周期。
警告: 主要不安全的來源是通過單例來解析Scoped生命周期服務服務。這樣做的后果,很有可能在處理后續請求時使用的服務的狀態是錯誤的。
服務生命周期和注冊選項
ASP.NET 服務可以配置如下生命周期:
- Transient: Transient服務在每次被請求時都會被創建。這種生命周期比較適用于輕量級的無狀態服務。
- Scoped: Scoped生命周期的服務是每次web請求被創建。
- Singleton: Singleton生命能夠周期服務在第一被請求時創建,在后續的每個請求都會使用同一個實例。如果你的應用需要單例服務,推薦的做法是交給服務容器來負責單例的創建和生命周期管理,而不是自己來走這些事情。
- Instance: 你也可以選擇直接添加實例到服務容器。如果這樣做,該實例會被后續的所有請求所使用(這樣就會創建一個scoped-Singleton實例)。Instance和Singleton的一個主要區別在于,Instance服務是由ConfigureServices創建,然后Singleton服務是lazy-loaded,在第一個被請求時才會被創建。
服務可以通過若干種方式注冊到容器。我們已經看到,對于給定類型通過指定具體類型來注冊服務的實現。除此之外,也可以指定一個工廠,用來按需創建實例。第三種方法是直接指定要使用的類型實例,在這種方式下,容器自身不會嘗試去創建實例。
為了演示這四種不同的生命周期和注冊選項,考慮一個簡單接口,代表這一個或多個任務操作,并且含有一個唯一標識符OperationId。根據我們如何配置服務的生命周期,容器對請求類或者提供同一個實例或者不同實例。為了弄明白生命周期是如何請求的,對于每一個生命周期類型我們都創建一個類型。
using System;namespace DependencyInjectionSample.Interfaces { public interface IOperation { Guid OperationId { get; } }
public interface IOperationTransient : IOperation { } public interface IOperationScoped : IOperation { } public interface IOperationSingleton : IOperation { } public interface IOperationInstance : IOperation { }
}</pre>
我們通過一個類來實現這些接口,接受一個Guid作為構造器參數,或者使用new Guid來提供(如果沒有提供的話)。接下來在ConfigureServices中,根據類型的生命周期來添加到容器中
services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddInstance<IOperationInstance>(new Operation(Guid.Empty)); services.AddTransient<OperationService, OperationService>();注意到對于Instance生命周期的實例,我們是自己提供了已知的Guid.Empty標識符,這樣我們能在該實例被使用時識別它。我們也注冊了一個OperationService,它依賴其他Operation類型。這樣我們就能弄清楚在一個請求內,對于每個類型我們是得到同樣的實例還是一個新的實例。
using DependencyInjectionSample.Interfaces;namespace DependencyInjectionSample.Services { public class OperationService { public IOperationTransient TransientOperation { get; private set; } public IOperationScoped ScopedOperation { get; private set; } public IOperationSingleton SingletonOperation { get; private set; } public IOperationInstance InstanceOperation { get; private set; }
public OperationService(IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationInstance instanceOperation) { TransientOperation = transientOperation; ScopedOperation = scopedOperation; SingletonOperation = singletonOperation; InstanceOperation = instanceOperation; } }
}</pre>
為了演示對應用的單個請求內和不同請求內的對象生命周期,樣例包含一個OperationController依賴每種類型的Operation以及OperationService。Index方法顯示所有的服務Id。
using DependencyInjectionSample.Interfaces; using DependencyInjectionSample.Services; using Microsoft.AspNet.Mvc;namespace DependencyInjectionSample.Controllers { public class OperationsController : Controller { private readonly OperationService _operationService; private readonly IOperationTransient _transientOperation; private readonly IOperationScoped _scopedOperation; private readonly IOperationSingleton _singletonOperation; private readonly IOperationInstance _instanceOperation;
public OperationsController(OperationService operationService, IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationInstance instanceOperation) { _operationService = operationService; _transientOperation = transientOperation; _scopedOperation = scopedOperation; _singletonOperation = singletonOperation; _instanceOperation = instanceOperation; } public IActionResult Index() { ViewBag.Transient = _transientOperation; ViewBag.Scoped = _scopedOperation; ViewBag.Singleton = _singletonOperation; ViewBag.Instance = _instanceOperation; ViewBag.Service = _operationService; return View(); } }
}</pre>
然后有兩個請求到達controller action。
![]()
</div>
觀察請求內和請求間的OperationId哪個變化。
- Transient 服務的對象總是不同的。每個controller和service都提供一個新的實例
- Scoped的對象在一個request內是一樣的,而不同的request間是不一樣的。
- Singleton對象是一直保持不表的。
- Instance對象,對于每一個對象和request都是一樣的,也即是在ConfigureServices中所指定的對象。
請求服務和應用服務
HttpContext中的一個ASP.NET請求中的可用服務分為兩個集合, ApplicationServices 和 RequestServices 。
![]()
Request services作為應用的一部分是你可以配置和request的。而Application Services則是局限于在在應用啟動(Startup)時可用的服務。Scoped的服務是作為Request Services的一部分而不是Applocation Services的一部分。當對象指定依賴時,是由RequestServices中的類型所提供,而不是ApplicationServices。
一般來將你不應該直接使用這些屬性,而是傾向于通過類的構造器來請求這些類型,讓框架來注入這些依賴。這樣產生的類更容易 測試 和更松耦合。
Note:需要重點記住的是,應用幾乎總是會使用RequestServices,任何情況下你都不應該直接使用這些屬性。而是通過構造器來請求所需服務。
自定義依賴注入服務
你可以設計自己的服務并通過依賴注入到需求方。這樣可以避免使用有狀態的靜態方法調用(會導致code smell,即 static cling )和服務內對依賴類的直接實例化。當選擇是否通過New來實例化一個類型或者通過依賴注入來氫氣,記住“New is Glue”也許是點幫助的。通過遵循 Solid面向對象設計原則 ,設計的類自然就會small, well-factored,和easily tested。
如果你發現類有了太多需要注入的依賴怎么辦?一般來說,這是類承擔了太多職責的標志,很有可能違反了SRP(單一職責原則)。檢查是否能把其中的某些職責轉到新的類。記住,Controller類應該只關注UI,所以業務規則和數據獲取實現應該放在合適的 關注點分離 的類中。
至于數據獲取,你可以注入EF DbContext類型到Controller中(假設你已經在Startup類中配置了EF)。然而,一般避免在UI項目中直接依賴DbContext類型,而是依賴抽象,如Repository接口,在接口的實現中限制EF的相關知識。這樣會減少項目和數據訪問策略的耦合,使得測試代碼更加容易。
取代默認服務容器
內建的服務容器只滿足框架最基本的需求,大部分的consumer 應用基于此構建。然而,如果開發者希望取代內建的容器,使用自己偏好的容器,也是可以很容器做到的。ConfigureServices方法一般返回void,但是如果返回類型簽名改為IServiceProvider,就可以配置和返回別的容器。有很多 IoC .NET 容器。當它們可用時,本文會添加這些容器的DNX實現。在本例中,使用Autofac包。
首先在project.json中添加合適的容器包。
"dependencies" : { "Autofac": "4.0.0-rc1", "Autofac.Extensions.DependencyInjection": "4.0.0-rc1" },然后在ConfigureServices中配置容器,并返回IServiceProvider:
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); // add other framework services// Add Autofac var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterModule<DefaultModule>(); containerBuilder.Populate(services); var container = containerBuilder.Build(); return container.Resolve<IServiceProvider>(); }</pre>
Note:當使用第三方DI容器時,需要改變ConfigreServices的返回簽名,改成IServiceProvider而不是void。
最后在DefaultModule中配置Autofac。
public class DefaultModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<CharacterRepository>().As<ICharacterRepository>(); } }然后在運行時,Autofac將會解析類型和注入的依賴
Package(Nuget) ProjectSite Autofac.Dnx http://autofac.org StructureMap.Dnx http://structuremap.github.io 推薦規范
當使用依賴注入時,記住如下推薦規范:
- DI是針對于有復雜依賴的對象。Controllers, services, adapters和 repositories都是一些可以添加依賴的對象的例子。
- 避免直接在DI中存儲數據和配置。例如,用戶購物車不應添加到服務容器中。配置應該使用 Options Model 中文鏈接。類似的,避免“data holder” objects that only exist to allow access to some other object。如果可能盡量通過DI來獲取實際的item。
- 避免靜態獲取服務
- 在應用代碼中避免service location
- 避免靜態獲取HttpContext
</div>Note:如上所有推薦規范,你可能遇到必須忽略某條的情形。但是這種情形很少,而且基本都是框架本身內部的情形。
記住,依賴注入是static/global對象獲取模式的一個替代方式。如果你把DI和靜態對象接入混用,你可能不能體會到DI的優勢。
本文由用戶 Chasity15A 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!相關資訊