淺談ASP.NET Core中的DI


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,滿足我們大多數的功能需求!

服務主要分為兩類:

  1. Framework Services:框架服務,由ASP.NET Core框架提供,例如 IApplicationBuilderIHostingEnvironment ... ,詳情見https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#framework-provided-services

  2. 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<ILog, ConsoleLogger>();//注冊為單例

services.TryAddSingleton<ILog, ConsoleLogger>();

構造函數注入(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<ILog, ConsoleLogger>();
    
  • Scoped(作用域)

    每一個作用域范圍內(例如每一個HTTP 請求)從DI容器中解析出來的實例都是同一個

    //原始方法
    services.Add(new ServiceDescriptor(typeof(ILog), typeof(ConsoleLogger), ServiceLifetime.Scoped));
    //擴展方法
    services.AddScoped<ILog, ConsoleLogger>();
    
  • Singleton(單例)

    只在第一次請求是創建,之后所有請求都共享同一個服務實例,直到應用程序的生命周結束。所以在ASP.NET Core應用中,沒有必須手動去創建一個單例,通過注冊一個單例服務到容器中即可。

    //原始方法
    services.Add(new ServiceDescriptor(typeof(ILog), new ConsoleLogger()));
    //擴展方法 
    services.AddSingleton<ILog, ConsoleLogger>();
    

DI使用經驗的一些總結

泛型如何注冊?

通過開放式泛型(Open Generics)注冊服務。假設上面的類型修改成 ILog<T>

ConsoleLogger<T> ,那么我們按照下面的方式注冊即可

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 class Startup
{
     public void ConfigureServices(IServiceCollection services)
     {
         services.AddTransient<ILog, Logger1>();
         services.AddTransient<ILog, Logger2>();
     }
}
public class HomeController : Controller
{
     ILog _log;
     public HomeController(ILog log)
     {

         _log = log;//_log is Logger2

     }
}

為了避免我們多次注冊,導致具體實現被覆蓋的問題,所以我們一般都是使用    TryAddTransient,這個方法注冊時,檢測到相同類型已經被注冊過,就不會在進行注冊

public class Startup
{
     public void ConfigureServices(IServiceCollection services)
     {
         services.TryAddTransient<ILog, Logger1>();
         services.TryAddTransient<ILog, Logger2>();
     }
}
public class HomeController : Controller
{
     ILog _log;
     public HomeController(ILog log)
     {

         _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生態建設貢獻一份自己的力量!💪


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM