天天看點

淺談ASP.NET Core中的DI

淺談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

繼續閱讀