.Net 5 DependencyInjection 依賴注入


依賴注入(Dependency Injection)簡稱DI,DI實現了控制反轉(Inversion of Control,Ioc),遵循了依賴倒置原則,

DI實現解耦、不需要手動去獲取或創建依賴的對象

控制反轉:由容器幫我們控制對象的創建和依賴對象的注入

正轉:直接獲取依賴對象並手動創建對象

案例:

一些接口和類

public interface IStorage
{
}

public class FileStorage : IStorage
{
    public string Read(string path)
    {
        return File.ReadAllText(path);
    }
}

public interface IBookService
{
    string[] GetBooks();
}

public class BookService : IBookService
{
    public IStorage Storage { get; }

    public BookService(IStorage storage)
    {
        Storage = storage;
    }

    public string[] GetBooks()
    {
        // ...
        return new string[] { };
    }
}

不使用依賴注入:

class Program
{
    static void Main(string[] args)
    {
        // 需要創建或獲取依賴
        IStorage fileStorage = new FileStorage();
        // 需要手動new服務並傳入依賴
        IBookService bookService = new BookService(fileStorage);
        bookService.GetBooks();
    }
}

使用依賴注入:

class Program
{
    static void Main(string[] args)
    {
         // 創建依賴容器
         IServiceCollection serviceCollection = new ServiceCollection();
         // 注冊服務
         serviceCollection.AddSingleton<IStorage, FileStorage>();
         // 注冊服務
         serviceCollection.AddSingleton<IBookService, BookService>();
         // 構建服務提供者
         IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
         // 獲取服務,IBookService的實現BookService的依賴將自動注入
         IBookService bookService = serviceProvider.GetService<IBookService>();
         bookService.GetBooks();
    }
}

服務注冊

IServiceCollection是一個ServiceDescriptor服務描述器集合,ServiceDescriptor描述了一個服務

public interface IServiceCollection : 
    IList<ServiceDescriptor>,
    ICollection<ServiceDescriptor>,
    IEnumerable<ServiceDescriptor>,
    IEnumerable
  {
  }

注冊服務就是向ServiceCollection這個集合中添加ServiceDescriptor

IServiceCollection serviceCollection = new ServiceCollection();
var serviceDescriptor = new ServiceDescriptor(
    typeof(IStorage), // 服務類型
    typeof(FileStorage), // 實現類型
    ServiceLifetime.Transient // 生命周期
);
// 清除服務
serviceCollection.Clear();
// 是否包含服務
if (serviceCollection.Contains(serviceDescriptor))
{
serviceCollection.Remove(serviceDescriptor);
}

// 注冊服務
serviceCollection.Add(serviceDescriptor);
// 只有容器中不存在此服務時才注冊服務
serviceCollection.TryAdd(serviceDescriptor);

AddSingletonAddScopedAddTransient是構建ServiceDescriptor的簡便擴展方法

IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IStorage, FileStorage>();
serviceCollection.AddScoped<IStorage, FileStorage>();
serviceCollection.AddTransient<IStorage, FileStorage>();
serviceCollection.AddTransient<FileStorage>(); // 等同於 serviceCollection.AddTransient<FileStorage, FileStorage>()

在向容器注冊服務時,可以填寫 實現類型、工廠或者實例

IServiceCollection serviceCollection = new ServiceCollection();

serviceCollection.Add(new ServiceDescriptor(typeof(IStorage),typeof(FileStorage),ServiceLifetime.Transient));

FileStorage fs = new FileStorage();
serviceCollection.Add(new ServiceDescriptor(typeof(IStorage), fs));

serviceCollection.Add(new ServiceDescriptor(typeof(IStorage), serviceProvider => new FileStorage(), ServiceLifetime.Singleton));
方法 對象自動 dispose 多種實現 轉遞參數
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()
例子:
services.AddSingleton<IMyDep, MyDep>()
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
例子:
services.AddSingleton<IMyDep>(sp => new MyDep())
services.AddSingleton<IMyDep>(sp => new MyDep(99));
Add{LIFETIME}<{IMPLEMENTATION}>()
例子:
services.AddSingleton<MyDep>()
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})
例子:
services.AddSingleton<IMyDep>(new MyDep())
services.AddSingleton<IMyDep>(new MyDep(99))
AddSingleton(new {IMPLEMENTATION})
例子:
services.AddSingleton(new MyDep())
services.AddSingleton(new MyDep(99))

不由服務容器創建的服務

考慮下列代碼:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());
}

在上述代碼中:

服務實例不是由服務容器創建的,框架不會自動釋放服務,開發人員負責釋放服務。

服務獲取

GetRequiredServiceGetService區別

如果容器中不存在要獲取的服務,GetRequiredService將拋出異常,GetService將返回null

使用IServiceProvider延遲獲取服務

案例:

class MyService6
{
}
class MyService5
{
    public IServiceProvider ServiceProvider { get; }

    public MyService5(IServiceProvider serviceProvider)
    {
        ServiceProvider = serviceProvider;
    }

    public void GetService6()
    {
        ServiceProvider.GetService<MyService6>();
    }
}

獲取IEnumerable<>服務數組

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<Animal, Dog>();
serviceCollection.AddSingleton<Animal, Cat>();
serviceCollection.AddSingleton<Animal, Pig>();
var serviceProvider = serviceCollection.BuildServiceProvider(true);
var animals = serviceProvider.GetService<IEnumerable<Animal>>();
Console.WriteLine(animals.Count()); // 3

生命周期

有如下3種聲明周期

  • Transient:臨時,每次都將創建一個實例
  • Scoped:范圍,作用域,對於 Web 應用程序,每次Http請求創建一個實例,也可以通過CreateScope創建一個作用域,在此作用域內只會創建一個實例
  • Singleton:單例,只會創建一個實例

有作用域的服務由創建它們的容器釋放

Transient聲明周期案例

class MyService : IDisposable
{
    public MyService()
    {
        Console.WriteLine("MyService Construct"); // 創建一個新的實例將輸出`MyService Construct`
    }

    public void Hello()
    {
        Console.WriteLine("MyService Hello");
    }

    public void Dispose()
    {
        Console.WriteLine("MyService Dispose");
    }
}

C#2

var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<MyService>();
var serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetService<MyService>(); // 輸出:MyService Construct
serviceProvider.GetService<MyService>(); // 輸出:MyService Construct
serviceProvider.GetService<MyService>(); // 輸出:MyService Construct

Scoped聲明周期案例

var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<MyService>();
var serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetService<MyService>(); // 輸出:MyService Construct
serviceProvider.GetService<MyService>(); // 無輸出
serviceProvider.GetService<MyService>(); // 無輸出
using (var serviceScope = serviceProvider.CreateScope())
{
    serviceScope.ServiceProvider.GetService<MyService>(); // 輸出:MyService Construct
    serviceScope.ServiceProvider.GetService<MyService>(); // 無輸出
    serviceScope.ServiceProvider.GetService<MyService>(); // 無輸出
}
// 上面作用域結束后,將自動釋放服務,輸出 MyService Dispose

Single聲明周期案例

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<MyService>();
var serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetService<MyService>(); // 輸出:MyService Construct
serviceProvider.GetService<MyService>(); // 無輸出
serviceProvider.GetService<MyService>(); // 無輸出
using (var serviceScope = serviceProvider.CreateScope())
{
    serviceScope.ServiceProvider.GetService<MyService>(); // 無輸出
    serviceScope.ServiceProvider.GetService<MyService>(); // 無輸出
    serviceScope.ServiceProvider.GetService<MyService>(); // 無輸出
}

作用域驗證

在調用BuildServiceProvider時可以傳入參數來配置是否啟用作用域驗證

對於Web應用程序,如果應用環境為“Development”(開發環境),默認作用域驗證將啟用(CreateDefaultBuilder 會將 ServiceProviderOptions.ValidateScopes 設為 true),若要始終驗證作用域(包括在生存環境中驗證),請使用HostBuilder上的 UseDefaultServiceProvider 配置 ServiceProviderOptions

啟用作用域驗證后,將驗證以下內容:

  • 確保沒有從根服務提供程序直接或間接解析到有作用域的服務
  • 未將有作用域的服務直接或間接注入到單一實例。

案例

class MyService2
{
}

class MyService3
{
    public MyService3(MyService2 myService2)
    {
    }
}

C#2

var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<MyService2>();
var serviceProvider = serviceCollection.BuildServiceProvider(true); // 傳入true,開啟作用域驗證

using (var serviceScope = serviceProvider.CreateScope())
{
    serviceScope.ServiceProvider.GetService<MyService2>(); // 正確用法
}

serviceProvider.GetService<MyService2>(); // 將拋出異常,因為不能從根服務提供程序直接或間接解析到有作用域的服務

C#3

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<MyService3>();
serviceCollection.AddScoped<MyService2>();
var serviceProvider = serviceCollection.BuildServiceProvider(true);
serviceProvider.GetService<MyService3>(); // 將拋出異常,不能將有作用域的服務直接或間接注入到單一實例

調用 BuildServiceProvider 時,會創建根服務提供程序。 在啟動提供程序和應用時,根服務提供程序的生存期對應於應用/服務的生存期,並在關閉應用時釋放。

有作用域的服務由創建它們的容器釋放

如果作用域創建於根容器,則該服務的生存會有效地提升至單一實例,因為根容器只會在應用/服務關閉時將其釋放

構造函數注入行為

服務能被獲取通過:

  • IServiceProvider
  • ActivatorUtilities:創建沒有在容器中注入的服務

構造函數可以使用沒有在容器中注入的服務,但是參數必須分配默認值。

通過IServiceProviderActivatorUtilities解析服務時,構造函數注入需要公共構造函數

通過ActivatorUtilities解析服務時,構造函數注入要求僅存在一個適用的構造函數。 ActivatorUtilities支持構造函數重載,其所有參數都可以通過依賴項注入來實現。

案例

class MyService4
{
    public MyService4()
    {
        Console.WriteLine("0 Parameter Constructor");
    }

    public MyService4(string a)
    {
        Console.WriteLine("1 Parameter Constructor");
    }

    public MyService4(string a, string b)
    {
        Console.WriteLine("2 Parameter Constructor");
    }
}

C#2

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<MyService4>();
var serviceProvider = serviceCollection.BuildServiceProvider(true);
ActivatorUtilities.CreateInstance<MyService4>(serviceProvider); // 0 Parameter Constructor
ActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1"); // 1 Parameter Constructor
ActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1", "Param 2"); // 2 Parameter Constructor
ActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1", 12);// 拋出異常,沒有找到合適的構造函數
ActivatorUtilities.CreateInstance<MyService4>(serviceProvider, "Param 1", "Param 2", "Param 3");// 拋出異常,沒有找到合適的構造函數

Asp.Net Core,注入 Startup 的服務

服務可以注入 Startup 構造函數和 Startup.Configure 方法

使用泛型主機 (IHostBuilder) 時,只能將以下服務注入 Startup 構造函數:

  • IWebHostEnvironment
  • IHostEnvironment
  • IConfiguration

任何向 DI 容器注冊的服務都可以注入 Startup.Configure 方法:

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
}

使用擴展方法注冊服務組

ASP.NET Core 框架使用一種約定來注冊一組相關服務。 約定使用單個 Add{GROUP_NAME} 擴展方法來注冊該框架功能所需的所有服務。 例如,AddControllers 擴展方法會注冊 MVC 控制器所需的服務

從 main 調用服務

使用 IServiceScopeFactory.CreateScope 創建 IServiceScope 以解析應用范圍內的作用域服務。 此方法可以用於在啟動時訪問有作用域的服務以便運行初始化任務。

以下示例演示如何訪問范圍內 IMyDependency 服務並在 Program.Main 中調用其 WriteMessage 方法:

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myDependency = services.GetRequiredService<IMyDependency>();
                myDependency.WriteMessage("Call services from main");
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}


免責聲明!

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



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