團(tuán)隊(duì)開發(fā)框架實(shí)戰(zhàn)—ASP.NET Core的依賴注入
ASP.NET Core 1.0在設(shè)計(jì)上原生就支持和有效利用依賴注入。在Startup類中,應(yīng)用可以通過將框架內(nèi)嵌服務(wù)注入到方法中來使用他們;另一方面,你也可以配置服務(wù)來注入使用。默認(rèn)的服務(wù)容器只提供了最小的特性集合闷供,所以并不打算取代其他的IoC容器。
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ActDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SqlServerConnection")));
services.AddMvc();
services.AddTransient<IUnitOfWork, ActUnitOfWork>();
services.AddTransient<IRepositoryBase<Role>, ActRepositoryBase<Role>>();
services.AddTransient<IRoleAppService, RoleAppService>();
AutoMapper.Configuration.Configure();
}
什么是依賴注入DI
依賴注入是為了達(dá)到解耦對(duì)象和其依賴的一項(xiàng)技術(shù)统诺。一個(gè)類為了完成自身某些操作所需的對(duì)象是通過某種方式提供的歪脏,而不是使用靜態(tài)引用或者直接實(shí)例化。通常情況下粮呢,類通過構(gòu)造器來聲明其依賴婿失,遵循 顯式依賴原則 。這種方式稱作構(gòu)造器注入啄寡。
當(dāng)以DI思想來設(shè)計(jì)類時(shí)豪硅,這些類更加松耦合,因?yàn)樗麄儾恢苯佑簿幋a的依賴其合作者挺物。這遵循了依賴倒置原則舟误,即高層模塊不應(yīng)依賴底層模塊,兩者都應(yīng)依賴抽象姻乓。類在構(gòu)建時(shí)所需是抽象(如接口interface),而不是具體的實(shí)現(xiàn)眯牧。把依賴抽離成接口蹋岩,把這些接口的實(shí)現(xiàn)作為參數(shù)也是 策略設(shè)計(jì)模式 的例子。
當(dāng)一個(gè)系統(tǒng)使用DI來設(shè)計(jì)時(shí)学少,很多類通過構(gòu)造器或者屬性來添加依賴剪个,這樣就很方便有一個(gè)專門的類來創(chuàng)建這些類以及他們相關(guān)的依賴。這樣的類稱之為“容器”或者“IoC容器”或“DI容器”版确。一個(gè)容器本質(zhì)上是一個(gè)工廠扣囊,來給請(qǐng)求者提供類型實(shí)例。如果給定類型聲明了自身依賴绒疗,容器也配置了來提供這些依賴類型侵歇,那么它會(huì)創(chuàng)建這些依賴作為請(qǐng)求實(shí)例的一部分。通過這種方式可以為 類提供復(fù)雜的依賴圖吓蘑,而不需要任何硬編碼的對(duì)象依賴惕虑。除了創(chuàng)建依賴對(duì)象外坟冲,容器一般還管理應(yīng)用內(nèi)的對(duì)象生命周期。
ASP.NET Core 1.0提供了一個(gè)簡單的內(nèi)置容器(以IServiceProvider為代表)溃蔫,默認(rèn)支持構(gòu)造器注入健提,這樣ASP.NET可以通過DI使某些服務(wù)可用。ASP.NET把它所管理的類型稱之為服務(wù)伟叛。本文的剩下部分私痹,服務(wù)即指ASP.NET IoC容器所管理的類型。你可以在Startup類中的ConfigureServices 方法中配置內(nèi)置的容器服務(wù)统刮。
Note:Martin Fowler寫過一篇很詳細(xì)的依賴反轉(zhuǎn)的 文章 紊遵。微軟對(duì)此也有很棒的描述 連接 。
總結(jié)一下IOC和DI
IOC:Inversion of Control网沾,即“控制反轉(zhuǎn)”癞蚕,他不是什么新的技術(shù),而是一種設(shè)計(jì)思想辉哥。
通常我們是這么理解桦山,我們一般的設(shè)計(jì)思想是在對(duì)象內(nèi)部直接控制,而IOC是將設(shè)計(jì)好的對(duì)象交給容器控制醋旦,而不是傳統(tǒng)的在對(duì)象內(nèi)部直接控制恒水。
打個(gè)比方:我們租房子,在我們和房主之間插入了一個(gè)中間人(房介)饲齐,我們只需要跟房介提出我們的要求钉凌,比如房子要三室一廳、臥室向陽捂人、房東是女的(_ )御雕、樓層不要太低、遮光不要太長等等等等滥搭,然后房介就會(huì)按照我們的要求給我們提供一個(gè)房產(chǎn)信息酸纲,我們滿意就跟租賃、入住瑟匆,如果我們不滿意(拋出異常)闽坡,房介就會(huì)幫我們做后續(xù)處理。整個(gè)過程不再是由我們控制愁溜,而是由房介這么一個(gè)容器去控制疾嗅。所有的類都會(huì)在spring容器中登記,告訴spring你是個(gè)什么東西冕象,你需要什么東西代承,然后spring會(huì)在系統(tǒng)運(yùn)行到適當(dāng)?shù)臅r(shí)候,把你要的東西主動(dòng)給你渐扮,同時(shí)也把你交給其他需要你的東西次泽。所有的類的創(chuàng)建穿仪、銷毀都由 spring來控制,也就是說控制對(duì)象生存周期的不再是引用它的對(duì)象意荤,而是spring啊片。對(duì)于某個(gè)具體的對(duì)象而言,以前是它控制其他對(duì)象玖像,現(xiàn)在是所有對(duì)象都被spring控制紫谷,所以這叫控制反轉(zhuǎn)。
DI:IOC的一個(gè)重點(diǎn)是在系統(tǒng)運(yùn)行中捐寥,動(dòng)態(tài)的向某個(gè)對(duì)象提供它所需要的其他對(duì)象笤昨。這一點(diǎn)是通過DI(Dependency Injection,依賴注入)來實(shí)現(xiàn)的握恳。比如對(duì)象A需要操作數(shù)據(jù)庫瞒窒,以前我們總是要在A中自己編寫代碼來獲得一個(gè)Connection對(duì)象,有了 spring我們就只需要告訴spring乡洼,A中需要一個(gè)Connection崇裁,至于這個(gè)Connection怎么構(gòu)造,何時(shí)構(gòu)造束昵,A不需要知道拔稳。在系統(tǒng)運(yùn)行時(shí),spring會(huì)在適當(dāng)?shù)臅r(shí)候制造一個(gè)Connection锹雏,然后像打針一樣巴比,注射到A當(dāng)中,這樣就完成了對(duì)各個(gè)對(duì)象之間關(guān)系的控制礁遵。A需要依賴 Connection才能正常運(yùn)行轻绞,而這個(gè)Connection是由spring注入到A中的,依賴注入的名字就這么來的佣耐。那么DI是如何實(shí)現(xiàn)的呢铲球? 有一個(gè)重要特征是反射(reflection),它允許程序在運(yùn)行的時(shí)候動(dòng)態(tài)的生成對(duì)象晰赞、執(zhí)行對(duì)象的方法、改變對(duì)象的屬性选侨,spring就是通過反射來實(shí)現(xiàn)注入的掖鱼。
使用框架提供的服務(wù)
ConfigureServices方法負(fù)責(zé)定義應(yīng)用使用的服務(wù),包括平臺(tái)特性服務(wù)如EF和ASP.NET MVC援制。最初提供給ConfigureServices的IServiceCollection只有少數(shù)服務(wù)戏挡。默認(rèn)web模板提供了怎么通過擴(kuò)展方法來添加額外服務(wù)到容器的例子,如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>();
}
ASP.NET提供的特性和中間件遵循使用一個(gè)AddService擴(kuò)展方法的約定褐墅,來注冊(cè)該特性使用的所需的所有服務(wù)拆檬。
Note:你可以在Startup方法中請(qǐng)求某些framework-provided服務(wù),詳見應(yīng)用啟動(dòng) Application Startup
當(dāng)然妥凳,除了配置框架提供的各種服務(wù)竟贯,你也可以配置自己定義的服務(wù)。
注冊(cè)自定義服務(wù)
在默認(rèn)web模板中逝钥,有如下兩個(gè)服務(wù)被添加到IServiceCollection中
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
AddTransient方法將抽象類型映射為實(shí)體服務(wù)屑那,對(duì)于每個(gè)請(qǐng)求這都單獨(dú)實(shí)例化,這稱作服務(wù)的生命周期艘款。額外生命周期選項(xiàng)如下持际。對(duì)于每個(gè)注冊(cè)的服務(wù)選擇合適的生命周期是很重要的。是對(duì)每個(gè)請(qǐng)求類都提供一個(gè)新的實(shí)例化服務(wù)哗咆?還是在給定web請(qǐng)求內(nèi)只實(shí)例化一次蜘欲?還是在應(yīng)用周期內(nèi)只有單例?
在本文的例子中晌柬,有個(gè)簡單的CharacterController來顯示Character姓名姥份,在Index方法中顯示已存儲(chǔ)的Character(如果沒有則創(chuàng)建)。雖然注冊(cè)了EF服務(wù)空繁,但本例持久化沒有使用數(shù)據(jù)庫殿衰。具體的數(shù)據(jù)獲取服務(wù)抽象到了ICharacterRepository接口實(shí)現(xiàn)中,這遵從了 倉儲(chǔ)模式 盛泡。在構(gòu)造器中請(qǐng)求ICharacterRepository參數(shù)闷祥,并將其賦給私有變量,來根據(jù)需要獲取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);
}
}
接口ICharacterRepository只簡單定義了兩個(gè)方法凯砍,Controller通過其來操作Charcter實(shí)例。
using System.Collections.Generic;
using DependencyInjectionSample.Models;
namespace DependencyInjectionSample.Interfaces
{
public interface ICharacterRepository
{
IEnumerable<Character> ListAll();
void Add(Character character);
}
}
接口有具體類型CharacterRepository來實(shí)現(xiàn)拴竹,在運(yùn)行時(shí)被使用悟衩。
Note:CharacterRepository類只是使用DI的普通例子,你可以對(duì)應(yīng)用所有的服務(wù)使用DI栓拜,而不僅僅是“倉儲(chǔ)”和數(shù)據(jù)獲取類座泳。
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();
}
}
}
請(qǐng)注意,CharacterRepository在其構(gòu)造器中請(qǐng)求了ApplicationDbContext實(shí)例幕与。這種鏈?zhǔn)降囊蕾囎⑷胧呛艹R姷奶羰疲灰蕾嚤旧碛钟凶约旱囊蕾嚒H萜鱽碡?fù)責(zé)已樹形的方式來解析所有這些依賴啦鸣,并返回解析完成的服務(wù)潮饱。
Note:創(chuàng)建請(qǐng)求對(duì)象,以及其依賴诫给,其依賴的依賴香拉,有時(shí)這被稱之為 對(duì)象圖 啦扬。同樣的,需要解析的對(duì)象集合稱之為 依賴樹 或者 依賴圖 凫碌。
ICharacterRepository和ApplicationDbContext都必須在ConfigureServices中注冊(cè)扑毡。ApplicationDbContext是通過擴(kuò)展方法AddEntityFramework來配置,它包括添加DbContext (AddDbContext )的一個(gè)擴(kuò)展证鸥。倉儲(chǔ)的注視在在ConfigureServices方法的結(jié)尾僚楞。
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddScoped<ICharacterRepository, CharacterRepository>();
// Show different lifetime options
services.AddTransient<IOperationTransient, Operation>();
EF contexts需要使用scoped生命周期來添加到服務(wù)容器。如果你使用了上面的helper方法枉层,這是已經(jīng)處理好的泉褐。使用EF的倉儲(chǔ)服務(wù)應(yīng)該使用同樣的生命周期。
警告: 主要不安全的來源是通過單例來解析Scoped生命周期服務(wù)服務(wù)鸟蜡。這樣做的后果膜赃,很有可能在處理后續(xù)請(qǐng)求時(shí)使用的服務(wù)的狀態(tài)是錯(cuò)誤的。