关于ASP.NET Core依赖注入的详细介绍

发布时间:2021-09-08 07:42:24 作者:chen
来源:亿速云 阅读:163

这篇文章主要讲解了“关于ASP.NET Core依赖注入的详细介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“关于ASP.NET Core依赖注入的详细介绍”吧!

目录

依赖注入

什么是依赖注入

简单说,就是将对象的创建和销毁工作交给DI容器来进行,调用方只需要接收注入的对象实例即可。

依赖注入有什么好处

依赖注入在.NET中,可谓是“一等公民”,处处都离不开它,那么它有什么好处呢?

假设有一个日志类 FileLogger,用于将日志记录到本地文件。

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

    }
}

日志很常用,几乎所有服务都需要记录日志。如果不使用依赖注入,那么我们就必须在每个服务中手动 new FileLogger 来创建一个 FileLogger 实例。

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

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

如果某一天,想要替换掉 FileLogger,而是使用 ElkLogger,通过ELK来处理日志,那么我们就需要将所有服务中的代码都要改成 new ElkLogger。

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

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

正因如此,所以依赖注入解决了这些棘手的问题:

ASP.NET Core内置的依赖注入

服务生存周期

Transient
瞬时,即每次获取,都是一个全新的服务实例

Scoped
范围(或称为作用域),即在某个范围(或作用域内)内,获取的始终是同一个服务实例,而不同范围(或作用域)间获取的是不同的服务实例。对于Web应用,每个请求为一个范围(或作用域)。

Singleton
单例,即在单个应用中,获取的始终是同一个服务实例。另外,为了保证程序正常运行,要求单例服务必须是线程安全的。

服务释放

若服务实现了IDisposable接口,并且该服务是由DI容器创建的,那么你不应该去Dispose,DI容器会对服务自动进行释放。

如,有Service1、Service2、Service3、Service4四个服务,并且都实现了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");
    }
}

并注册为:

public void ConfigureServices(IServiceCollection services)
{
    // 每次使用完(请求结束时)即释放
    services.AddTransient<Service1>();
    // 超出范围(请求结束时)则释放
    services.AddScoped<Service2>();
    // 程序停止时释放
    services.AddSingleton<Service3>();
    // 程序停止时释放
    services.AddSingleton(sp => new Service4());
}

构造函数注入一下

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

请求一下,获取输出:

Service2.Dispose
Service1.Dispose

这些服务实例都是由DI容器创建的,所以DI容器也会负责服务实例的释放和销毁。注意,单例此时还没到释放的时候。

但如果注册为:

public void ConfigureServices(IServiceCollection services)
{
    // 注意与上面的区别,这个是直接 new 的,而上面是通过 sp => new 的
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());
    services.AddSingleton(new Service3());
    services.AddSingleton(new Service4());
}

此时,实例都是咱们自己创建的,DI容器就不会负责去释放和销毁了,这些工作都需要我们开发人员自己去做。

更多注册方式,请参考官方文档-Service registration methods

TryAdd{Lifetime}扩展方法

当你将同样的服务注册了多次时,如:

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

那么当使用IEnumerable<{Service}>(下面会讲到)解析服务时,就会产生多个MyService实例的副本。

为此,框架提供了TryAdd{Lifetime}扩展方法,位于命名空间Microsoft.Extensions.DependencyInjection.Extensions下。当DI容器中已存在指定类型的服务时,则不进行任何操作;反之,则将该服务注入到DI容器中。

services.AddTransient<IMyService, MyService1>();
// 由于上面已经注册了服务类型 IMyService,所以下面的代码不不会执行任何操作(与生命周期无关)
services.TryAddTransient<IMyService, MyService1>();
services.TryAddTransient<IMyService, MyService2>();
// 注册了 IMyService - MyService1
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());
// 注册了 IMyService - MyService2
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService2>());
// 未进行任何操作,因为 IMyService - MyService1 在上面已经注册了
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());

解析同一服务的多个不同实现

默认情况下,如果注入了同一个服务的多个不同实现,那么当进行服务解析时,会以最后一个注入的为准。

如果想要解析出同一服务类型的所有服务实例,那么可以通过IEnumerable<{Service}>来解析(顺序同注册顺序一致):

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

public void ConfigureServices(IServiceCollection services)
{
    // 生命周期没有限制
    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 扩展方法

上面我们所提到的,都是注册新的服务到DI容器中,但是有时我们想要替换或是移除某些服务,这时就需要使用ReplaceRemove

// 将 IMyService 的实现替换为 MyService1
services.Replace(ServiceDescriptor.Singleton<IMyService, MyService>());
// 移除 IMyService 注册的实现 MyService
services.Remove(ServiceDescriptor.Singleton<IMyService, MyService>());
// 移除 IMyService 的所有注册
services.RemoveAll<IMyService>();
// 清除所有服务注册
services.Clear();

Autofac

Autofac 是一个老牌DI组件了,接下来我们使用Autofac替换ASP.NET Core自带的DI容器。

1.安装nuget包:

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

2.替换服务提供器工厂

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        })
        // 通过此处将默认服务提供器工厂替换为 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,否则会导致 ConfigureContainer 方法不被调用。
        // 2. 不要创建 ContainerBuilder,也不要调用 builder.Populate(),AutofacServiceProviderFactory 已经做了这些工作了
        // 3. 你仍然可以在此处通过微软默认的方式进行服务注册
        
        services.AddOptions();
        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication.Ex", Version = "v1" });
        });
    }

    // 1. ConfigureContainer 用于使用 Autofac 进行服务注册
    // 2. 该方法在 ConfigureServices 之后运行,所以这里的注册会覆盖之前的注册
    // 3. 不要 build 容器,不要调用 builder.Populate(),AutofacServiceProviderFactory 已经做了这些工作了
    public void ConfigureContainer(ContainerBuilder builder)
    {
        // 将服务注册划分为模块,进行注册
        builder.RegisterModule(new AutofacModule());
    }
    
    public class AutofacModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            // 在此处进行服务注册
            builder.RegisterType<UserService>().As<IUserService>();
        }
    }

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

服务解析和注入

上面我们主要讲了服务的注入方式,接下来看看服务的解析方式。解析方式有两种:

1.IServiceProvider

2.ActivatorUtilities

构造函数注入

上面我们举得很多例子都是使用了构造函数注入——通过构造函数接收参数。构造函数注入是非常常见的服务注入方式,也是首选方式,这要求:

方法注入

顾名思义,方法注入就是通过方法参数来接收服务实例。

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

属性注入

ASP.NET Core内置的依赖注入是不支持属性注入的。但是Autofac支持,用法如下:

老规矩,先定义服务和实现

public interface IUserService 
{
    string Get();
}

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

然后注册服务

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();
    }
}

最后,我们在控制器中通过属性来接收服务实例

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

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

通过调用Get接口,我们就可以得到IUserService的实例,从而得到响应

User

一些注意事项

using Microsoft.Extensions.DependencyInjection;

public class ValuesController : ControllerBase
{
    private readonly IServiceProvider _serviceProvider;

    // 应通过依赖注入的方式获取服务实例
    public ValuesController(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    [HttpGet]
    public string Get()
    {
        // 尽量避免通过 GetService 方法获取服务实例
        var myService = _serviceProvider.GetService<IMyService>();

        return "Ok";
    }
}
public void ConfigureServices(IServiceCollection services)
{
    // 不要在该方法中调用该方法
    var serviceProvider = services.BuildServiceProvider();
}

1.在 Scoped 或 Transient 服务中解析 Singleton 服务

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

1.不能在根服务提供程序解析 Scoped 服务,这会导致 Scoped 服务的生命周期提升为 Singleton,因为根容器在应用关闭时才会释放。

2.不能将 Scoped 服务注入到 Singleton 服务中

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中调用即可

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

框架默认提供的服务

以下列出一些常用的框架已经默认注册的服务:

服务类型生命周期
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactoryTransient
IHostApplicationLifetimeSingleton
IHostLifetimeSingleton
IWebHostEnvironmentSingleton
IHostEnvironmentSingleton
Microsoft.AspNetCore.Hosting.IStartupSingleton
Microsoft.AspNetCore.Hosting.IStartupFilterTransient
Microsoft.AspNetCore.Hosting.Server.IServerSingleton
Microsoft.AspNetCore.Http.IHttpContextFactoryTransient
Microsoft.Extensions.Logging.ILoggerSingleton
Microsoft.Extensions.Logging.ILoggerFactorySingleton
Microsoft.Extensions.ObjectPool.ObjectPoolProviderSingleton
Microsoft.Extensions.Options.IConfigureOptionsTransient
Microsoft.Extensions.Options.IOptionsSingleton
System.Diagnostics.DiagnosticSourceSingleton
System.Diagnostics.DiagnosticListenerSingleton

感谢各位的阅读,以上就是“关于ASP.NET Core依赖注入的详细介绍”的内容了,经过本文的学习后,相信大家对关于ASP.NET Core依赖注入的详细介绍这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. 介绍asp.net core 依赖注入
  2. ASP.NET Core过滤器中如何使用依赖注入

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

asp.net

上一篇:MyBatis-Plus的简介以及基础操作介绍

下一篇:css中怎么实现背景定位

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》