淺談ASP.NET Core中的DI
什麼是依賴注入(DI: Dependency Injection)?
依賴注入(DI)是一種面向對象的軟體設計模式,主要是幫助開發人員開發出松耦合的應用程式。同時呢,讓應用更容易進行單元測試和維護。
DI其實就是用一個注入器類為一個對象提供其依賴的一個過程!如何更好的了解呢?下面就舉個列子解釋下!
比如 class Client,它要使用服務class Service的提供的功能,這個時候就說Service是Client的依賴,程式實作如下:
var s = new Service();
var c = new Client(s);
很明顯我們還要承擔建立Service的對象的職責,程式出現了強耦合問題,後面如果需求變化,我們要替換掉Service,那我們就要修改這邊的代碼,這樣的程式很面明,擴充性,靈活性比較差了!
引入DI之後呢,我們應該還有一個注入器類,假設是 class Injector 。為了更好的解釋DI的好處,上面的代碼我們重新設定為 class Client 依賴 接口IService , class Service 實作了IService ,這個時候我們的程式主流程不需要關注如何建立的Service,可以把這部分的職責委托給Injector,我們隻要告訴Injector,我需要IService,請提供給我,程式實作如下:
var s = Injector.Get(typeof(IService));
var c = new Client(s);
這樣的好處就很明顯了,我們隻關注自己的核心業務職責,對應依賴如何建立的,具體是什麼類實作的,都不用自己管了,權力交給注入器就可以了!
劃重點:其實上面這個過程大家應該發現了我們把本來自己的一部分控制權,轉交給了注入器去做,這個就是我們經常說的IOC(Inversion of Control,控制反轉)。DI其實就是IOC計原則的一種實作。還有我們平常說的觀察者預設,其實也是IOC的一種實作,核心就是把部分職責(非核心職責)轉交出去,進而去建構出一種松耦合的應用!
什麼是依賴注入容器(DI Container)?
DI Container ,也可以叫 IOC Container,其實是一個架構(Framework),它提供了一整套的DI解決方案,它負責建立依賴,然後自動把依賴注入到需要它們的其他對象裡面,同時還負責管理依賴的生命周期!一些強大的第三方容器還提供各種各樣的功能,使我們更加愉快的撸代碼!
常用的第三方DI Container:
Spring.NET
Autofac
Unity
Ninject
ASP .NET Core中的DI
在ASP.NET Core中,把依賴統一稱作服務(services),是以DI Container就也被稱為Service Container,Asp.NET Core提供了一個簡單的内置容器 IServiceProvider ,它預設支援構造器注入 constructor injection,滿足我們大多數的功能需求!
服務主要分為兩類:
Framework Services:架構服務,由ASP.NET Core架構提供,例如 IApplicationBuilder 、IHostingEnvironment ... ,詳情見
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#framework-provided-services。
Appliction Services:應用服務,由我們根據實際業務建立。
如果要通過DI容器自動實作注入我們的服務,我們必須要先在容器中登記服務(隻需要注冊應用服務,架構服務已經被ASP.NET Core架構注入了)。
注冊服務(Registering Services)
假設我們有一個ILog接口和它的一個實作類,我們要把它注入到DI容器裡面,然後在應用中使用它。
public interface ILog
{
void Info(string msg);
void Error(string err);
}
public class ConsoleLogger:ILog
public void Info(string msg)
{
Console.WriteLine(msg);
}
public void Error(string err)
{
Console.WriteLine(err);
}
然後在Startup類的ConfigureServices()方法中注冊上面的服務,ConfigureServices()有一個IServiceCollection參數,就是用它來注冊應用服務。
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.Add(new ServiceDescriptor(typeof(ILog), typeof(ConsoleLogger), ServiceLifetime.Singleton));
}
類ServiceDescriptor用來描述服務的類型、服務的具體實作已經服務的一個生命周期(Service Lifetime),上面我們指定了服務ILog的實作是ConsoleLogger,且是一個單例(Singleton)。
通常情況我們都是使用IServiceCollection擴充方法來注冊服務
services.AddSingleton();//注冊為單例
services.TryAddSingleton();
構造函數注入(Contractor Injection)
一旦我們注冊了函數,當應用類的構造函數包含了需要依賴的服務,DI容器就自動幫我們注入依賴。
public class HomeController : Controller
ILog _log;
public HomeController(ILog log)
{
_log = log;
}
public IActionResult Index()
{
_log.Info("Executing /home/index");
return View();
}
}
控制器需要ILog服務,隻需要在構造函數的參數中包含ILog類型即可,我們不需做其他任何事,DI容器自動給我們建立ILog的執行個體,并根據注入時指定的生命周期,在切當是時機銷毀(Dispose)這個執行個體。
Action方法注入(Method Injection)
有時候,我們隻需要在某一個方法中需要這個服務,這時,我們可以給方案的參數标記上[FromServices] 這個特性,容器就能自動為我們注入這個依賴服務的執行個體了
public IActionResult Index([FromServices] ILog log)
log.Info("Index method executing");
return View();
屬性注入(Property Injection)
ASP.NET Core自帶這個容器不支援,需要使用第三方容器,例如Autofac 。
服務的生命周期(Service Lifetime)
DI容器負責管理已注冊服務的生命周期,它根據指定的生命周期自動銷毀服務執行個體。ASP.NET Core 的服務可以配置以下三種生命周期形式:
Transient(瞬态)
每次從DI容器中解析(擷取)服務時,DI容器都是傳回一個新的服務執行個體。
//原始方法
services.Add(new ServiceDescriptor(typeof(ILog), typeof(ConsoleLogger), ServiceLifetime.Transient));
//擴充方法
services.AddTransient();
Scoped(作用域)
每一個作用域範圍内(例如每一個HTTP 請求)從DI容器中解析出來的執行個體都是同一個
services.Add(new ServiceDescriptor(typeof(ILog), typeof(ConsoleLogger), ServiceLifetime.Scoped));
services.AddScoped();
Singleton(單例)
隻在第一次請求是建立,之後所有請求都共享同一個服務執行個體,直到應用程式的生命周結束。是以在ASP.NET Core應用中,沒有必須手動去建立一個單例,通過注冊一個單例服務到容器中即可。
services.Add(new ServiceDescriptor(typeof(ILog), new ConsoleLogger()));
//擴充方法
services.AddSingleton();
DI使用經驗的一些總結
泛型如何注冊?
通過開放式泛型(Open Generics)注冊服務。假設上面的類型修改成 ILog 和
ConsoleLogger ,那麼我們按照下面的方式注冊即可
services.AddScoped(typeof(ILog<>), typeof(ConsoleLogger<>));
相同的接口類型注入兩個實作類型會怎樣?
如果我們在注冊服務時,相同類型注冊多次并不會報錯,但在解析時,傳回的是最後一次注冊的類型的執行個體。
public interface ILog
{
void Info(string msg);
}
public class Logger1 : ILog
{
public void Info(string msg)
{
}
}
public class Logger2 : ILog
{
public void Info(string msg)
{
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ILog, Logger1>();
services.AddTransient<ILog, Logger2>();
}
ILog _log;
public HomeController(ILog log)
_log = log;//_log is Logger2
}
為了避免我們多次注冊,導緻具體實作被覆寫的問題,是以我們一般都是使用 TryAddTransient,這個方法注冊時,檢測到相同類型已經被注冊過,就不會在進行注冊
services.TryAddTransient<ILog, Logger1>();
services.TryAddTransient<ILog, Logger2>();
_log = log;//_log is Logger1
自己想要手動從容器中擷取服務對象,怎麼做?
ASP.NET Core中的DI Container是IServiceProvider,隻要擷取這個對象,然後調用 GetService 這個方法即可。
如果在Controller中
HttpContext的屬性RequestServices就是IServiceProvider類型,是以我們可以按照下面的方法:
public IActionResult Index()
{
var services = this.HttpContext.RequestServices;
var log = (ILog)services.GetService(typeof(ILog));
log.Info("Index method executing");
return View();
}
如果在應用中
在應用中時,我們可以通過構造函數注入IServiceProvider
public class MyAppService
private IServiceProvider _services;
public MyAppService(IServiceProvider services)
{
_services=services;
}
public void Test()
{
//原始方法
var log = (ILog)_services.GetService(typeof(ILog));
//通過擴充方法,需要nuget添加Microsoft.Extensions.DependencyInjection.Abstractions 這個引用
var log = _services.GetService<ILog>();
}
結語
ASP.NET Core已經很強大了,提供了很多實用功能,希望.Net的戰友們能在基本知識儲備的前提下,多多發掘總結出ASP.NET Core開發的最佳實踐,為推動.NET Core生态建設貢獻一份自己的力量!💪
作者:小偉06
出處:
https://www.cnblogs.com/liuww/p/12540309.html