亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

理解ASP.NET Core 依賴注入(Dependency Injection)

 更新時(shí)間:2021年09月07日 16:19:01   作者:xiaoxiaotank  
把有依賴關(guān)系的類放到容器中,解析出這些類的實(shí)例,就是依賴注入。目的是實(shí)現(xiàn)類的解耦。本文主要介紹了ASP.NET Core 依賴注入(Dependency Injection),需要了解具體內(nèi)容的可以仔細(xì)閱讀這篇文章,希望對(duì)你有所幫助

依賴注入

什么是依賴注入

簡(jiǎn)單說(shuō),就是將對(duì)象的創(chuàng)建和銷毀工作交給DI容器來(lái)進(jìn)行,調(diào)用方只需要接收注入的對(duì)象實(shí)例即可。

依賴注入有什么好處

依賴注入在.NET中,可謂是“一等公民”,處處都離不開它,那么它有什么好處呢?

假設(shè)有一個(gè)日志類 FileLogger,用于將日志記錄到本地文件。

public class FileLogger
{
    public void LogInfo(string message)
    {

    }
}

日志很常用,幾乎所有服務(wù)都需要記錄日志。如果不使用依賴注入,那么我們就必須在每個(gè)服務(wù)中手動(dòng) new FileLogger 來(lái)創(chuàng)建一個(gè) FileLogger 實(shí)例。

public class MyService
{
    private readonly FileLogger _logger = new FileLogger();

    public void Get()
    {
        _logger.LogInfo("MyService.Get");
    }
}

如果某一天,想要替換掉 FileLogger,而是使用 ElkLogger,通過(guò)ELK來(lái)處理日志,那么我們就需要將所有服務(wù)中的代碼都要改成 new ElkLogger。

public class MyService
{
    private readonly ElkLogger _logger = new ElkLogger();

    public void Get()
    {
        _logger.LogInfo("MyService.Get");
    }
}
  • 在一個(gè)大型項(xiàng)目中,這樣的代碼分散在項(xiàng)目各處,涉及到的服務(wù)均需要進(jìn)行修改,顯然一個(gè)一個(gè)去修改不現(xiàn)實(shí),且違反了“開閉原則”。
  • 如果Logger中還需要其他一些依賴項(xiàng),那么用到Logger的服務(wù)也要為其提供依賴,如果依賴項(xiàng)修改了,其他服務(wù)也必須要進(jìn)行更改,更加增大了維護(hù)難度。
  • 很難進(jìn)行單元測(cè)試,因?yàn)樗鼰o(wú)法進(jìn)行 mock

正因如此,所以依賴注入解決了這些棘手的問(wèn)題:

  • 通過(guò)接口或基類(包含抽象方法或虛方法等)將依賴關(guān)系進(jìn)行抽象化
  • 將依賴關(guān)系存放到服務(wù)容器中
  • 由框架負(fù)責(zé)創(chuàng)建和釋放依賴關(guān)系的實(shí)例,并將實(shí)例注入到構(gòu)造函數(shù)、屬性或方法中

ASP.NET Core內(nèi)置的依賴注入

服務(wù)生存周期

Transient
瞬時(shí),即每次獲取,都是一個(gè)全新的服務(wù)實(shí)例

Scoped
范圍(或稱為作用域),即在某個(gè)范圍(或作用域內(nèi))內(nèi),獲取的始終是同一個(gè)服務(wù)實(shí)例,而不同范圍(或作用域)間獲取的是不同的服務(wù)實(shí)例。對(duì)于Web應(yīng)用,每個(gè)請(qǐng)求為一個(gè)范圍(或作用域)。

Singleton
單例,即在單個(gè)應(yīng)用中,獲取的始終是同一個(gè)服務(wù)實(shí)例。另外,為了保證程序正常運(yùn)行,要求單例服務(wù)必須是線程安全的。

服務(wù)釋放

若服務(wù)實(shí)現(xiàn)了IDisposable接口,并且該服務(wù)是由DI容器創(chuàng)建的,那么你不應(yīng)該去Dispose,DI容器會(huì)對(duì)服務(wù)自動(dòng)進(jìn)行釋放。

如,有Service1、Service2、Service3、Service4四個(gè)服務(wù),并且都實(shí)現(xiàn)了IDisposable接口,如:

public class Service1 : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Service1.Dispose");
    }
}

public class Service2 : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Service2.Dispose");
    }
}

public class Service3 : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Service3.Dispose");
    }
}

public class Service4 : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Service4.Dispose");
    }
}

并注冊(cè)為:

public void ConfigureServices(IServiceCollection services)
{
    // 每次使用完(請(qǐng)求結(jié)束時(shí))即釋放
    services.AddTransient<Service1>();
    // 超出范圍(請(qǐng)求結(jié)束時(shí))則釋放
    services.AddScoped<Service2>();
    // 程序停止時(shí)釋放
    services.AddSingleton<Service3>();
    // 程序停止時(shí)釋放
    services.AddSingleton(sp => new Service4());
}

構(gòu)造函數(shù)注入一下

public ValuesController(
    Service1 service1, 
    Service2 service2, 
    Service3 service3, 
    Service4 service4)
{ }

請(qǐng)求一下,獲取輸出:

Service2.Dispose
Service1.Dispose

這些服務(wù)實(shí)例都是由DI容器創(chuàng)建的,所以DI容器也會(huì)負(fù)責(zé)服務(wù)實(shí)例的釋放和銷毀。注意,單例此時(shí)還沒(méi)到釋放的時(shí)候。

但如果注冊(cè)為:

public void ConfigureServices(IServiceCollection services)
{
    // 注意與上面的區(qū)別,這個(gè)是直接 new 的,而上面是通過(guò) sp => new 的
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());
    services.AddSingleton(new Service3());
    services.AddSingleton(new Service4());
}

此時(shí),實(shí)例都是咱們自己創(chuàng)建的,DI容器就不會(huì)負(fù)責(zé)去釋放和銷毀了,這些工作都需要我們開發(fā)人員自己去做。

更多注冊(cè)方式,請(qǐng)參考官方文檔-Service registration methods

TryAdd{Lifetime}擴(kuò)展方法

當(dāng)你將同樣的服務(wù)注冊(cè)了多次時(shí),如:

services.AddSingleton<IMyService, MyService>();
services.AddSingleton<IMyService, MyService>();

那么當(dāng)使用IEnumerable<{Service}>(下面會(huì)講到)解析服務(wù)時(shí),就會(huì)產(chǎn)生多個(gè)MyService實(shí)例的副本。

為此,框架提供了TryAdd{Lifetime}擴(kuò)展方法,位于命名空間Microsoft.Extensions.DependencyInjection.Extensions下。當(dāng)DI容器中已存在指定類型的服務(wù)時(shí),則不進(jìn)行任何操作;反之,則將該服務(wù)注入到DI容器中。

services.AddTransient<IMyService, MyService1>();
// 由于上面已經(jīng)注冊(cè)了服務(wù)類型 IMyService,所以下面的代碼不不會(huì)執(zhí)行任何操作(與生命周期無(wú)關(guān))
services.TryAddTransient<IMyService, MyService1>();
services.TryAddTransient<IMyService, MyService2>();
  • TryAdd:通過(guò)參數(shù)ServiceDescriptor將服務(wù)類型、實(shí)現(xiàn)類型、生命周期等信息傳入進(jìn)去
  • TryAddTransient:對(duì)應(yīng)AddTransient
  • TryAddScoped:對(duì)應(yīng)AddScoped
  • TryAddSingleton:對(duì)應(yīng)AddSingleton
  • TryAddEnumerable:這個(gè)和TryAdd的區(qū)別是,TryAdd僅根據(jù)服務(wù)類型來(lái)判斷是否要進(jìn)行注冊(cè),而TryAddEnumerable則是根據(jù)服務(wù)類型和實(shí)現(xiàn)類型一同進(jìn)行判斷是否要進(jìn)行注冊(cè),常常用于注冊(cè)同一服務(wù)類型的多個(gè)不同實(shí)現(xiàn)。舉個(gè)例子吧:
// 注冊(cè)了 IMyService - MyService1
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());
// 注冊(cè)了 IMyService - MyService2
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService2>());
// 未進(jìn)行任何操作,因?yàn)?IMyService - MyService1 在上面已經(jīng)注冊(cè)了
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());

解析同一服務(wù)的多個(gè)不同實(shí)現(xiàn)

默認(rèn)情況下,如果注入了同一個(gè)服務(wù)的多個(gè)不同實(shí)現(xiàn),那么當(dāng)進(jìn)行服務(wù)解析時(shí),會(huì)以最后一個(gè)注入的為準(zhǔn)。

如果想要解析出同一服務(wù)類型的所有服務(wù)實(shí)例,那么可以通過(guò)IEnumerable<{Service}>來(lái)解析(順序同注冊(cè)順序一致):

public interface IAnimalService { }
public class DogService : IAnimalService { }
public class PigService : IAnimalService { }
public class CatService : IAnimalService { }

public void ConfigureServices(IServiceCollection services)
{
    // 生命周期沒(méi)有限制
    services.AddTransient<IAnimalService, DogService>();
    services.AddScoped<IAnimalService, PigService>();
    services.AddSingleton<IAnimalService, CatService>();
}

public ValuesController(
    // CatService
    IAnimalService animalService,   
    // DogService、PigService、CatService
    IEnumerable<IAnimalService> animalServices)
{
}

Replace && Remove 擴(kuò)展方法

上面我們所提到的,都是注冊(cè)新的服務(wù)到DI容器中,但是有時(shí)我們想要替換或是移除某些服務(wù),這時(shí)就需要使用ReplaceRemove

// 將 IMyService 的實(shí)現(xiàn)替換為 MyService1
services.Replace(ServiceDescriptor.Singleton<IMyService, MyService>());
// 移除 IMyService 注冊(cè)的實(shí)現(xiàn) MyService
services.Remove(ServiceDescriptor.Singleton<IMyService, MyService>());
// 移除 IMyService 的所有注冊(cè)
services.RemoveAll<IMyService>();
// 清除所有服務(wù)注冊(cè)
services.Clear();

Autofac

Autofac 是一個(gè)老牌DI組件了,接下來(lái)我們使用Autofac替換ASP.NET Core自帶的DI容器。

1.安裝nuget包:

Install-Package Autofac
Install-Package Autofac.Extensions.DependencyInjection

2.替換服務(wù)提供器工廠

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        })
        // 通過(guò)此處將默認(rèn)服務(wù)提供器工廠替換為 autofac
        .UseServiceProviderFactory(new AutofacServiceProviderFactory());

3.在 Startup 類中添加 ConfigureContainer 方法

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public ILifetimeScope AutofacContainer { get; private set; }

    public void ConfigureServices(IServiceCollection services)
    {
        // 1. 不要 build 或返回任何 IServiceProvider,否則會(huì)導(dǎo)致 ConfigureContainer 方法不被調(diào)用。
        // 2. 不要?jiǎng)?chuàng)建 ContainerBuilder,也不要調(diào)用 builder.Populate(),AutofacServiceProviderFactory 已經(jīng)做了這些工作了
        // 3. 你仍然可以在此處通過(guò)微軟默認(rèn)的方式進(jìn)行服務(wù)注冊(cè)
        
        services.AddOptions();
        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication.Ex", Version = "v1" });
        });
    }

    // 1. ConfigureContainer 用于使用 Autofac 進(jìn)行服務(wù)注冊(cè)
    // 2. 該方法在 ConfigureServices 之后運(yùn)行,所以這里的注冊(cè)會(huì)覆蓋之前的注冊(cè)
    // 3. 不要 build 容器,不要調(diào)用 builder.Populate(),AutofacServiceProviderFactory 已經(jīng)做了這些工作了
    public void ConfigureContainer(ContainerBuilder builder)
    {
        // 將服務(wù)注冊(cè)劃分為模塊,進(jìn)行注冊(cè)
        builder.RegisterModule(new AutofacModule());
    }
    
    public class AutofacModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            // 在此處進(jìn)行服務(wù)注冊(cè)
            builder.RegisterType<UserService>().As<IUserService>();
        }
    }

    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
    {
        // 通過(guò)此方法獲取 autofac 的 DI容器
        AutofacContainer = app.ApplicationServices.GetAutofacRoot();
    }
}

服務(wù)解析和注入

上面我們主要講了服務(wù)的注入方式,接下來(lái)看看服務(wù)的解析方式。解析方式有兩種:

1.IServiceProvider

2.ActivatorUtilities

  • 用于創(chuàng)建未在DI容器中注冊(cè)的服務(wù)實(shí)例
  • 用于某些框架級(jí)別的功能

構(gòu)造函數(shù)注入

上面我們舉得很多例子都是使用了構(gòu)造函數(shù)注入——通過(guò)構(gòu)造函數(shù)接收參數(shù)。構(gòu)造函數(shù)注入是非常常見(jiàn)的服務(wù)注入方式,也是首選方式,這要求:

  • 構(gòu)造函數(shù)可以接收非依賴注入的參數(shù),但必須提供默認(rèn)值
  • 當(dāng)服務(wù)通過(guò)IServiceProvider解析時(shí),要求構(gòu)造函數(shù)必須是public
  • 當(dāng)服務(wù)通過(guò)ActivatorUtilities解析時(shí),要求構(gòu)造函數(shù)必須是public,雖然支持構(gòu)造函數(shù)重載,但必須只能有一個(gè)是有效的,即參數(shù)能夠全部通過(guò)依賴注入得到值

方法注入

顧名思義,方法注入就是通過(guò)方法參數(shù)來(lái)接收服務(wù)實(shí)例。

[HttpGet]
public string Get([FromServices]IMyService myService)
{
    return "Ok";
}

屬性注入

ASP.NET Core內(nèi)置的依賴注入是不支持屬性注入的。但是Autofac支持,用法如下:

老規(guī)矩,先定義服務(wù)和實(shí)現(xiàn)

public interface IUserService 
{
    string Get();
}

public class UserService : IUserService
{
    public string Get()
    {
        return "User";
    }
}

然后注冊(cè)服務(wù)

  • 默認(rèn)情況下,控制器的構(gòu)造函數(shù)參數(shù)由DI容器來(lái)管理嗎,而控制器實(shí)例本身卻是由ASP.NET Core框架來(lái)管理,所以這樣“屬性注入”是無(wú)法生效的
  • 通過(guò)AddControllersAsServices方法,將控制器交給 autofac 容器來(lái)處理,這樣就可以使“屬性注入”生效了
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers().AddControllersAsServices();
}

public void ConfigureContainer(ContainerBuilder builder)
{
    builder.RegisterModule<AutofacModule>();
}

public class AutofacModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<UserService>().As<IUserService>();

        var controllerTypes = Assembly.GetExecutingAssembly().GetExportedTypes()
            .Where(type => typeof(ControllerBase).IsAssignableFrom(type))
            .ToArray();

        // 配置所有控制器均支持屬性注入
        builder.RegisterTypes(controllerTypes).PropertiesAutowired();
    }
}

最后,我們?cè)诳刂破髦型ㄟ^(guò)屬性來(lái)接收服務(wù)實(shí)例

public class ValuesController : ControllerBase
{
    public IUserService UserService { get; set; }

    [HttpGet]
    public string Get()
    {
        return UserService.Get();
    }
}

通過(guò)調(diào)用Get接口,我們就可以得到IUserService的實(shí)例,從而得到響應(yīng)

User

一些注意事項(xiàng)

  • 避免使用服務(wù)定位模式。盡量避免使用GetService來(lái)獲取服務(wù)實(shí)例,而應(yīng)該使用DI。
using Microsoft.Extensions.DependencyInjection;

public class ValuesController : ControllerBase
{
    private readonly IServiceProvider _serviceProvider;

    // 應(yīng)通過(guò)依賴注入的方式獲取服務(wù)實(shí)例
    public ValuesController(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    [HttpGet]
    public string Get()
    {
        // 盡量避免通過(guò) GetService 方法獲取服務(wù)實(shí)例
        var myService = _serviceProvider.GetService<IMyService>();

        return "Ok";
    }
}
  • 避免在ConfigureServices中調(diào)用BuildServiceProvider。因?yàn)檫@會(huì)導(dǎo)致創(chuàng)建第二個(gè)DI容器的副本,從而導(dǎo)致注冊(cè)的單例服務(wù)出現(xiàn)多個(gè)副本。
public void ConfigureServices(IServiceCollection services)
{
    // 不要在該方法中調(diào)用該方法
    var serviceProvider = services.BuildServiceProvider();
}
  • 一定要注意服務(wù)解析范圍,不要在 Singleton 中解析 Transient 或 Scoped 服務(wù),這可能導(dǎo)致服務(wù)狀態(tài)錯(cuò)誤(如導(dǎo)致服務(wù)實(shí)例生命周期提升為單例)。允許的方式有:

1.在 Scoped 或 Transient 服務(wù)中解析 Singleton 服務(wù)

2.在 Scoped 或 Transient 服務(wù)中解析 Scoped 服務(wù)(不能和前面的Scoped服務(wù)相同)

  • 當(dāng)在Development環(huán)境中運(yùn)行、并通過(guò) CreateDefaultBuilder生成主機(jī)時(shí),默認(rèn)的服務(wù)提供程序會(huì)進(jìn)行如下檢查:

1.不能在根服務(wù)提供程序解析 Scoped 服務(wù),這會(huì)導(dǎo)致 Scoped 服務(wù)的生命周期提升為 Singleton,因?yàn)楦萜髟趹?yīng)用關(guān)閉時(shí)才會(huì)釋放。

2.不能將 Scoped 服務(wù)注入到 Singleton 服務(wù)中

  • 隨著業(yè)務(wù)增長(zhǎng),需要依賴注入的服務(wù)也越來(lái)越多,建議使用擴(kuò)展方法,封裝服務(wù)注入,命名為Add{Group_Name},如將所有 AppService 的服務(wù)注冊(cè)封裝起來(lái)
namespace Microsoft.Extensions.DependencyInjection
{
    public static class ApplicationServiceCollectionExtensions
    {
        public static IServiceCollection AddApplicationService(this IServiceCollection services)
        {
            services.AddTransient<Service1>();
            services.AddScoped<Service2>();
            services.AddSingleton<Service3>();
            services.AddSingleton(sp => new Service4());

            return services;
        }
    }
}

然后在ConfigureServices中調(diào)用即可

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationService();
}

框架默認(rèn)提供的服務(wù)

以下列出一些常用的框架已經(jīng)默認(rèn)注冊(cè)的服務(wù):

服務(wù)類型 生命周期
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transient
IHostApplicationLifetime Singleton
IHostLifetime Singleton
IWebHostEnvironment Singleton
IHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transient
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transient
Microsoft.Extensions.Logging.ILogger Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions Transient
Microsoft.Extensions.Options.IOptions Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

到此這篇關(guān)于理解ASP.NET Core 依賴注入(Dependency Injection)的文章就介紹到這了,更多相關(guān)ASP.NET Core 依賴注入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論