DI在.NET Core里面被提到了一個(gè)非常重要的位置赁温, 這篇文章主要再給大家普及一下關(guān)于依賴注入的概念坛怪,身邊有工作六七年的同事還個(gè)東西搞不清楚。另外再介紹一下.NET Core的DI實(shí)現(xiàn)以及對實(shí)例生命周期的管理(這個(gè)是經(jīng)常面試會問到的問題)束世。最后再給大家簡單介紹一下在控制臺以及Mvc下如何使用DI酝陈,以及如何把默認(rèn)的Service Container 替換成Autofac床玻。
我錄了一些關(guān)于ASP.NET Core的入門視頻:有興趣的同學(xué)可以去看看毁涉。 http://www.cnblogs.com/jesse2013/p/aspnetcore-videos.html
-
一、什么是依賴注入
1.1 依賴
1.2 什么注入
為什么反轉(zhuǎn)
何為容器
-
二锈死、.NET Core DI
2.1 實(shí)例的注冊
2.2 實(shí)例生命周期之單例
2.3 實(shí)例生命周期之Tranisent
2.4 實(shí)例生命周期之Scoped
-
三贫堰、DI在ASP.NET Core中的應(yīng)用
3.1 在Startup類中初始化
3.2 Controller中使用
3.3 View中使用
3.4 通過HttpContext來獲取
-
四、如何替換其它的Ioc容器
一待牵、什么是依賴注入(Denpendency Injection)
這也是個(gè)老身常談的問題其屏,到底依賴注入是什么? 為什么要用它缨该? 初學(xué)者特別容易對控制反轉(zhuǎn)IOC(Iversion of Control)偎行,DI等概念搞暈。
1.1依賴
當(dāng)一個(gè)類需要另一個(gè)類協(xié)作來完成工作的時(shí)候就產(chǎn)生了依賴贰拿。比如我們在AccountController這個(gè)控制器需要完成和用戶相關(guān)的注冊蛤袒、登錄 等事情。其中的登錄我們由EF結(jié)合Idnetity來完成膨更,所以我們封裝了一個(gè)EFLoginService妙真。這里AccountController就有一個(gè)ILoginService的依賴。
這里有一個(gè)設(shè)計(jì)原則:依賴于抽象荚守,而不是具體的實(shí)現(xiàn)珍德。所以我們給EFLoginService定義了一個(gè)接口练般,抽象了LoginService的行為。
1.2 什么是注入
注入體現(xiàn)的是一個(gè)IOC(控制反轉(zhuǎn)的的思想)锈候。在反轉(zhuǎn)之前 先较,我們先看看正轉(zhuǎn)。
AccountController自己來實(shí)例化需要的依賴果善。
private ILoginService<ApplicationUser> _loginService;
public AccountController()
{
_loginService = new EFLoginService()
}
大師說烙荷,這樣不好。你不應(yīng)該自己創(chuàng)建它虑稼,而是應(yīng)該由你的調(diào)用者給你琳钉。于是你通過構(gòu)造函數(shù)讓外界把這兩個(gè)依賴傳給你。
public AccountController(ILoginService<ApplicationUser> loginService)
{
_loginService = loginService;
}
把依賴的創(chuàng)建丟給其它人蛛倦,自己只負(fù)責(zé)使用歌懒,其它人丟給你依賴的這個(gè)過程理解為注入。
1.3 為什么要反轉(zhuǎn)溯壶?
為了在業(yè)務(wù)變化的時(shí)候盡少改動代碼可能造成的問題及皂。
比如我們現(xiàn)在要把從EF中去驗(yàn)證登錄改為從Redis去讀,于是我們加了一個(gè) RedisLoginService且改。這個(gè)時(shí)候我們只需要在原來注入的地方改一下就可以了验烧。
public AccountController(ILoginService<ApplicationUser> loginService)
{
_loginService = loginService;
}
// 用Redis來替換原來的EF登錄 var controller = new AccountController(new RedisLoginService()); controller.Login(userName, password);
1.4 何為容器
上面我們在使用AccountController的時(shí)候,我們自己通過代碼創(chuàng)建了一個(gè)ILoggingServce的實(shí)例又跛。想象一下碍拆,一個(gè)系統(tǒng)中如果有100個(gè)這樣的地方,我們是不是要在100個(gè)地方做這樣的事情慨蓝? 控制是反轉(zhuǎn)了感混,依賴的創(chuàng)建也移交到了外部。現(xiàn)在的問題是依賴太多礼烈,我們需要一個(gè)地方統(tǒng)一管理系統(tǒng)中所有的依賴弧满,容器誕生了。
容器負(fù)責(zé)兩件事情:
綁定服務(wù)與實(shí)例之間的關(guān)系
獲取實(shí)例此熬,并對實(shí)例進(jìn)行管理(創(chuàng)建與銷毀)
二庭呜、.NET Core DI
2.1 實(shí)例的注冊
前面講清楚DI和Ioc的關(guān)鍵概念之后,我們先來看看在控制臺中對.NET Core DI的應(yīng)用犀忱。在.NET Core中DI的核心分為兩個(gè)組件:IServiceCollection和 IServiceProvider募谎。
IServiceCollection 負(fù)責(zé)注冊
IServiceProvider 負(fù)責(zé)提供實(shí)例
通過默認(rèn)的 ServiceCollection(在Microsoft.Extensions.DependencyInjection命名空間下)有三個(gè)方法:
var serviceCollection = new ServiceCollection()
.AddTransient<ILoginService, EFLoginService>()
.AddSingleton<ILoginService, EFLoginService>()
.AddScoped<ILoginService, EFLoginService>();
這三個(gè)方法都是將我們的實(shí)例注冊進(jìn)去,只不過實(shí)例的生命周期不一樣峡碉。什么時(shí)候生命周期我們下一節(jié)接著講近哟。
ServiceCollection的默認(rèn)實(shí)現(xiàn)是提供一個(gè)ServiceDescriptor的List
public interface IServiceCollection : IList<ServiceDescriptor>
{
}
我們上面的AddTransient、AddSignletone和Scoped方法是IServiceCollection的擴(kuò)展方法鲫寄, 都是往這個(gè)List里面添加ServiceDescriptor吉执。
private static IServiceCollection Add(
IServiceCollection collection,
Type serviceType,
Type implementationType,
ServiceLifetime lifetime)
{
var descriptor =
new ServiceDescriptor(serviceType, implementationType, lifetime);
collection.Add(descriptor);
return collection;
}
2.2 實(shí)例的生命周期之單例
我們上面看到了疯淫,.NET Core DI 為我們提供的實(shí)例生命周其包括三種:
- Transient: 每一次GetService都會創(chuàng)建一個(gè)新的實(shí)例
- Scoped: 在同一個(gè)Scope內(nèi)只初始化一個(gè)實(shí)例 ,可以理解為( 每一個(gè)request級別只創(chuàng)建一個(gè)實(shí)例戳玫,同一個(gè)http request會在一個(gè) scope內(nèi))
- Singleton :整個(gè)應(yīng)用程序生命周期以內(nèi)只創(chuàng)建一個(gè)實(shí)例
對應(yīng)了Microsoft.Extensions.DependencyInjection.ServiceLifetime的三個(gè)枚舉值
public enum ServiceLifetime
{
Singleton,
Scoped,
Transient
}
為了大家能夠更好的理解這個(gè)生命周期的概念我們做一個(gè)測試:
定義一個(gè)最基本的IOperation里面有一個(gè) OperationId的屬性熙掺,IOperationSingleton也是一樣,只不過是另外一個(gè)接口咕宿。
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationSingleton : IOperation { }
public interface IOperationTransient : IOperation{}
public interface IOperationScoped : IOperation{}
我們的 Operation實(shí)現(xiàn)很簡單币绩,可以在構(gòu)造函數(shù)中傳入一個(gè)Guid進(jìn)行賦值,如果沒有的話則自已New一個(gè) Guid府阀。
public class Operation :
IOperationSingleton,
IOperationTransient,
IOperationScoped
{
private Guid _guid;
public Operation() {
_guid = Guid.NewGuid();
}
public Operation(Guid guid)
{
_guid = guid;
}
public Guid OperationId => _guid;
}
在程序內(nèi)我們可以多次調(diào)用ServiceProvider的GetService方法缆镣,獲取到的都是同一個(gè)實(shí)例。
var services = new ServiceCollection();
// 默認(rèn)構(gòu)造
services.AddSingleton<IOperationSingleton, Operation>();
// 自定義傳入Guid空值
services.AddSingleton<IOperationSingleton>(
new Operation(Guid.Empty));
// 自定義傳入一個(gè)New的Guid
services.AddSingleton <IOperationSingleton>(
new Operation(Guid.NewGuid()));
var provider = services.BuildServiceProvider();
// 輸出singletone1的Guid
var singletone1 = provider.GetService<IOperationSingleton>();
Console.WriteLine($"signletone1: {singletone1.OperationId}");
// 輸出singletone2的Guid
var singletone2 = provider.GetService<IOperationSingleton>();
Console.WriteLine($"signletone2: {singletone2.OperationId}");
Console.WriteLine($"singletone1 == singletone2 ? : { singletone1 == singletone2 }");
我們對IOperationSingleton注冊了三次试浙,最后獲取兩次董瞻,大家要注意到我們獲取到的始終都是我們最后一次注冊的那個(gè)給了一個(gè)Guid的實(shí)例,前面的會被覆蓋田巴。
2.3 實(shí)例生命周期之Tranisent
這次我們獲取到的IOperationTransient為兩個(gè)不同的實(shí)例钠糊。
2.4 實(shí)例生命周期之Scoped
.NET Core人IServiceProvider提供CreateScope產(chǎn)生一個(gè)新的ServiceProvider范圍,在這個(gè)范圍下的Scope標(biāo)注的實(shí)例將只會是同一個(gè)實(shí)例壹哺。換句話來說:用Scope注冊的對象抄伍,在同一個(gè)ServiceProvider的 Scope下相當(dāng)于單例。
同樣我們先分別注冊IOperationScoped管宵、IOperationTransient和IOperationSingletone 這三個(gè)實(shí)例截珍,用對應(yīng)的Scoped、Transient啄糙、和Singleton生命周期笛臣。
var services = new ServiceCollection()
.AddScoped<IOperationScoped, Operation>()
.AddTransient<IOperationTransient, Operation>()
.AddSingleton<IOperationSingleton, Operation>();
接下來我們用ServiceProvider.CreateScope方法創(chuàng)建一個(gè)Scope
var provider = services.BuildServiceProvider();
using (var scope1 = provider.CreateScope())
{
var p = scope1.ServiceProvider;
var scopeobj1 = p.GetService<IOperationScoped>();
var transient1 = p.GetService<IOperationTransient>();
var singleton1 = p.GetService<IOperationSingleton>();
var scopeobj2 = p.GetService<IOperationScoped>();
var transient2 = p.GetService<IOperationTransient>();
var singleton2 = p.GetService<IOperationSingleton>();
Console.WriteLine(
$"scope1: { scopeobj1.OperationId }," +
$"transient1: {transient1.OperationId}, " +
$"singleton1: {singleton1.OperationId}");
Console.WriteLine($"scope2: { scopeobj2.OperationId }, " +
$"transient2: {transient2.OperationId}, " +
$"singleton2: {singleton2.OperationId}");
}
接下來
scope1: 200d1e63-d024-4cd3-88c9-35fdf5c00956,
transient1: fb35f570-713e-43fc-854c-972eed2fae52,
singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225
scope2: 200d1e63-d024-4cd3-88c9-35fdf5c00956,
transient2: 2766a1ee-766f-4116-8a48-3e569de54259,
singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225
如果再創(chuàng)建一個(gè)新的Scope運(yùn)行云稚,
scope1: 29f127a7-baf5-4ab0-b264-fcced11d0729,
transient1: 035d8bfc-c516-44a7-94a5-3720bd39ce57,
singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225
scope2: 29f127a7-baf5-4ab0-b264-fcced11d0729,
transient2: 74c37151-6497-4223-b558-a4ffc1897d57,
singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225
大家注意到上面我們一共得到了 4個(gè)Transient實(shí)例隧饼,2個(gè)Scope實(shí)例,1個(gè)Singleton實(shí)例静陈。
這有什么用燕雁?
如果在Mvc中用過Autofac的InstancePerRequest的同學(xué)就知道,有一些對象在一個(gè)請求跨越多個(gè)Action或者多個(gè)Service鲸拥、Repository的時(shí)候拐格,比如最常用的DBContext它可以是一個(gè)實(shí)例。即能減少實(shí)例初始化的消耗刑赶,還能實(shí)現(xiàn)跨Service事務(wù)的功能捏浊。(注:在ASP.NET Core中所有用到EF的Service 都需要注冊成Scoped )
而實(shí)現(xiàn)這種功能的方法就是在整個(gè)reqeust請求的生命周期以內(nèi)共用了一個(gè)Scope。
三撞叨、DI在ASP.NET Core中的應(yīng)用
3.1在Startup類中初始化
ASP.NET Core可以在Startup.cs的 ConfigureService中配置DI金踪,大家看到 IServiceCollection這個(gè)參數(shù)應(yīng)該就比較熟悉了浊洞。
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ILoginService<ApplicationUser>,
EFLoginService>();
services.AddMvc();
)
ASP.NET Core的一些組件已經(jīng)提供了一些實(shí)例的綁定,像AddMvc就是Mvc Middleware在 IServiceCollection上添加的擴(kuò)展方法胡岔。
public static IMvcBuilder AddMvc(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
var builder = services.AddMvcCore();
builder.AddApiExplorer();
builder.AddAuthorization();
AddDefaultFrameworkParts(builder.PartManager);
...
}
3.2 Controller中使用
一般可以通過構(gòu)造函數(shù)或者屬性來實(shí)現(xiàn)注入法希,但是官方推薦是通過構(gòu)造函數(shù)。這也是所謂的顯式依賴靶瘸。
private ILoginService<ApplicationUser> _loginService;
public AccountController(
ILoginService<ApplicationUser> loginService)
{
_loginService = loginService;
}
我們只要在控制器的構(gòu)造函數(shù)里面寫了這個(gè)參數(shù)苫亦,ServiceProvider就會幫我們注入進(jìn)來。這一步是在Mvc初始化控制器的時(shí)候完成的怨咪,我們后面再介紹到Mvc的時(shí)候會往細(xì)里講屋剑。
3.3 View中使用
在View中需要用@inject 再聲明一下,起一個(gè)別名诗眨。
@using MilkStone.Services;
@model MilkStone.Models.AccountViewModel.LoginViewModel
@inject ILoginService<ApplicationUser> loginService
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
@loginService.GetUserName()
</body>
</html>
3.4 通過 HttpContext來獲取實(shí)例
HttpContext下有一個(gè)RequestedService同樣可以用來獲取實(shí)例對象饼丘,不過這種方法一般不推薦。同時(shí)要注意GetService<>這是個(gè)范型方法辽话,默認(rèn)如果沒有添加Microsoft.Extension.DependencyInjection的using肄鸽,是不用調(diào)用這個(gè)方法的。
HttpContext.RequestServices.GetService<ILoginService<ApplicationUser>>();
四油啤、如何替換其它的Ioc容器
Autofac也是不錯(cuò)的選擇典徘,但我們首先要搞清楚為什么要替換掉默認(rèn)的 DI容器?益咬,替換之后有什么影響逮诲?.NET Core默認(rèn)的實(shí)現(xiàn)對于一些小型的項(xiàng)目完全夠用,甚至大型項(xiàng)目麻煩點(diǎn)也能用幽告,但是會有些麻煩梅鹦,原因在于只提供了最基本的AddXXXX方法來綁定實(shí)例關(guān)系,需要一個(gè)一個(gè)的添加冗锁。如果項(xiàng)目可能要添加好幾百行這樣的方法齐唆。
如果熟悉Autofac的同學(xué)可能會這下面這樣的代碼有映象。
builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
這會給我們的初始化帶來一些便利性冻河,我們來看看如何替換Autofac到ASP.NET Core箍邮。我們只需要把Startup類里面的 ConfigureService的 返回值從 void改為 IServiceProvider即可。而返回的則是一個(gè)AutoServiceProvider叨叙。
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 new AutofacServiceProvider(container);
}
4.1 有何變化
其中很大的一個(gè)變化在于锭弊,Autofac 原來的一個(gè)生命周期InstancePerRequest,將不再有效擂错。正如我們前面所說的味滞,整個(gè)request的生命周期被ASP.NET Core管理了,所以Autofac的這個(gè)將不再有效。我們可以使用 InstancePerLifetimeScope 剑鞍,同樣是有用的刹悴,對應(yīng)了我們ASP.NET Core DI 里面的Scoped。
原文地址:https://www.cnblogs.com/jesse2013/p/di-in-aspnetcore.html