请选择 进入手机版 | 继续访问电脑版

[ASP.NET] 明白 ASP.NET Core 依靠 注入(Dependency Injection)

  [复制链接]
查看292 | 回复68 | 2021-9-15 08:18:11 | 显示全部楼层 |阅读模式
目次

依赖 注入

什么是依赖 注入

简单说,就是将对象的创建和烧毁 工作交给DI容器来举行 ,调用方只必要 吸取 注入的对象实例即可。

依赖 注入有什么好处

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

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

  1. public class FileLogger
  2. {
  3. public void LogInfo(string message)
  4. {
  5. }
  6. }
复制代码

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

  1. public class MyService
  2. {
  3. private readonly FileLogger _logger = new FileLogger();
  4. public void Get()
  5. {
  6. _logger.LogInfo("MyService.Get");
  7. }
  8. }
复制代码

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

  1. public class MyService
  2. {
  3. private readonly ElkLogger _logger = new ElkLogger();
  4. public void Get()
  5. {
  6. _logger.LogInfo("MyService.Get");
  7. }
  8. }
复制代码
  • 在一个大型项目中,如许 的代码分散在项目各处,涉及到的服务均必要 举行 修改,显然一个一个去修改不实际 ,且违反 了“开闭原则”。
  • 假如 Logger中还必要 其他一些依赖 项,那么用到Logger的服务也要为其提供依赖 ,假如 依赖 项修改了,其他服务也必须要举行 更改,更加增大了维护难度。
  • 很难举行 单元测试,由于 它无法举行 mock

正因云云 ,以是 依赖 注入办理 了这些棘手的标题 :

  • 通过接口或基类(包含抽象方法或虚方法等)将依赖 关系举行 抽象化
  • 将依赖 关系存放到服务容器中
  • 由框架负责创建和开释 依赖 关系的实例,并将实例注入到构造函数、属性或方法中

ASP.NET Core内置的依赖 注入

服务生存周期

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

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

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

服务开释

若服务实现了

  1. IDisposable
复制代码
接口,并且该服务是由DI容器创建的,那么你不应该去
  1. Dispose
复制代码
,DI容器会对服务主动 举行 开释 。

如,有Service1、Service2、Service3、Service4四个服务,并且都实现了

  1. IDisposable
复制代码
接口,如:

  1. public class Service1 : IDisposable
  2. {
  3. public void Dispose()
  4. {
  5. Console.WriteLine("Service1.Dispose");
  6. }
  7. }
  8. public class Service2 : IDisposable
  9. {
  10. public void Dispose()
  11. {
  12. Console.WriteLine("Service2.Dispose");
  13. }
  14. }
  15. public class Service3 : IDisposable
  16. {
  17. public void Dispose()
  18. {
  19. Console.WriteLine("Service3.Dispose");
  20. }
  21. }
  22. public class Service4 : IDisposable
  23. {
  24. public void Dispose()
  25. {
  26. Console.WriteLine("Service4.Dispose");
  27. }
  28. }
复制代码

并注册为:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // 每次使用完(请求结束时)即释放
  4. services.AddTransient<Service1>();
  5. // 超出范围(请求结束时)则释放
  6. services.AddScoped<Service2>();
  7. // 程序停止时释放
  8. services.AddSingleton<Service3>();
  9. // 程序停止时释放
  10. services.AddSingleton(sp => new Service4());
  11. }
复制代码

构造函数注入一下

  1. public ValuesController(
  2. Service1 service1,
  3. Service2 service2,
  4. Service3 service3,
  5. Service4 service4)
  6. { }
复制代码

哀求 一下,获取输出:

  1. Service2.Dispose
  2. Service1.Dispose
复制代码

这些服务实例都是由DI容器创建的,以是 DI容器也会负责服务实例的开释 和烧毁 。留意 ,单例此时还没到开释 的时间 。

但假如 注册为:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // 注意与上面的区别,这个是直接 new 的,而上面是通过 sp => new 的
  4. services.AddSingleton(new Service1());
  5. services.AddSingleton(new Service2());
  6. services.AddSingleton(new Service3());
  7. services.AddSingleton(new Service4());
  8. }
复制代码

此时,实例都是咱们本身 创建的,DI容器就不会负责去开释 和烧毁 了,这些工作都必要 我们开发 职员 本身 去做。

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

TryAdd{Lifetime}扩展方法

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

  1. services.AddSingleton<IMyService, MyService>();
  2. services.AddSingleton<IMyService, MyService>();
复制代码

那么当使用

  1. IEnumerable<{Service}>
复制代码
(下面会讲到)分析 服务时,就会产生多个
  1. MyService
复制代码
实例的副本。

为此,框架提供了

  1. TryAdd{Lifetime}
复制代码
扩展方法,位于定名 空间
  1. Microsoft.Extensions.DependencyInjection.Extensions
复制代码
下。当DI容器中已存在指定范例 的服务时,则不举行 任何操作;反之,则将该服务注入到DI容器中。

  1. services.AddTransient<IMyService, MyService1>();
  2. // 由于上面已经注册了服务类型 IMyService,所以下面的代码不不会执行任何操作(与生命周期无关)
  3. services.TryAddTransient<IMyService, MyService1>();
  4. services.TryAddTransient<IMyService, MyService2>();
复制代码
  • TryAdd:通过参数
    1. ServiceDescriptor
    复制代码
    将服务范例 、实现范例 、生命周期等信息传入进去
  • TryAddTransient:对应AddTransient
  • TryAddScoped:对应AddScoped
  • TryAddSingleton:对应AddSingleton
  • TryAddEnumerable:这个和
    1. TryAdd
    复制代码
    的区别是,
    1. TryAdd
    复制代码
    仅根据服务范例 来判断 是否要举行 注册,而
    1. TryAddEnumerable
    复制代码
    则是根据服务范例 和实现范例 一同举行 判断 是否要举行 注册,常常用于注册同一服务范例 的多个不同实现。举个例子吧:
  1. // 注册了 IMyService - MyService1
  2. services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());
  3. // 注册了 IMyService - MyService2
  4. services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService2>());
  5. // 未进行任何操作,因为 IMyService - MyService1 在上面已经注册了
  6. services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());
复制代码

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

默认环境 下,假如 注入了同一个服务的多个不同实现,那么当举行 服务分析 时,会以末了 一个注入的为准。

假如 想要分析 出同一服务范例 的全部 服务实例,那么可以通过

  1. IEnumerable<{Service}>
复制代码
来分析 (次序 同注册次序 划一 ):

  1. public interface IAnimalService { }
  2. public class DogService : IAnimalService { }
  3. public class PigService : IAnimalService { }
  4. public class CatService : IAnimalService { }
  5. public void ConfigureServices(IServiceCollection services)
  6. {
  7. // 生命周期没有限制
  8. services.AddTransient<IAnimalService, DogService>();
  9. services.AddScoped<IAnimalService, PigService>();
  10. services.AddSingleton<IAnimalService, CatService>();
  11. }
  12. public ValuesController(
  13. // CatService
  14. IAnimalService animalService,
  15. // DogService、PigService、CatService
  16. IEnumerable<IAnimalService> animalServices)
  17. {
  18. }
复制代码

Replace && Remove 扩展方法

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

  1. Replace
复制代码
  1. Remove
复制代码

  1. // 将 IMyService 的实现替换为 MyService1
  2. services.Replace(ServiceDescriptor.Singleton<IMyService, MyService>());
  3. // 移除 IMyService 注册的实现 MyService
  4. services.Remove(ServiceDescriptor.Singleton<IMyService, MyService>());
  5. // 移除 IMyService 的所有注册
  6. services.RemoveAll<IMyService>();
  7. // 清除所有服务注册
  8. services.Clear();
复制代码

Autofac

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

1.安装nuget包:

  1. Install-Package Autofac
  2. Install-Package Autofac.Extensions.DependencyInjection
复制代码

2.更换 服务提供器工厂

  1. public static IHostBuilder CreateHostBuilder(string[] args) =>
  2. Host.CreateDefaultBuilder(args)
  3. .ConfigureWebHostDefaults(webBuilder =>
  4. {
  5. webBuilder.UseStartup<Startup>();
  6. })
  7. // 通过此处将默认服务提供器工厂替换为 autofac
  8. .UseServiceProviderFactory(new AutofacServiceProviderFactory());
复制代码

3.在 Startup 类中添加 ConfigureContainer 方法

  1. public class Startup
  2. {
  3. public Startup(IConfiguration configuration)
  4. {
  5. Configuration = configuration;
  6. }
  7. public IConfiguration Configuration { get; }
  8. public ILifetimeScope AutofacContainer { get; private set; }
  9. public void ConfigureServices(IServiceCollection services)
  10. {
  11. // 1. 不要 build 或返回任何 IServiceProvider,否则会导致 ConfigureContainer 方法不被调用。
  12. // 2. 不要创建 ContainerBuilder,也不要调用 builder.Populate(),AutofacServiceProviderFactory 已经做了这些工作了
  13. // 3. 你仍然可以在此处通过微软默认的方式进行服务注册
  14. services.AddOptions();
  15. services.AddControllers();
  16. services.AddSwaggerGen(c =>
  17. {
  18. c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication.Ex", Version = "v1" });
  19. });
  20. }
  21. // 1. ConfigureContainer 用于使用 Autofac 进行服务注册
  22. // 2. 该方法在 ConfigureServices 之后运行,所以这里的注册会覆盖之前的注册
  23. // 3. 不要 build 容器,不要调用 builder.Populate(),AutofacServiceProviderFactory 已经做了这些工作了
  24. public void ConfigureContainer(ContainerBuilder builder)
  25. {
  26. // 将服务注册划分为模块,进行注册
  27. builder.RegisterModule(new AutofacModule());
  28. }
  29. public class AutofacModule : Autofac.Module
  30. {
  31. protected override void Load(ContainerBuilder builder)
  32. {
  33. // 在此处进行服务注册
  34. builder.RegisterType<UserService>().As<IUserService>();
  35. }
  36. }
  37. public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
  38. {
  39. // 通过此方法获取 autofac 的 DI容器
  40. AutofacContainer = app.ApplicationServices.GetAutofacRoot();
  41. }
  42. }
复制代码

服务分析 和注入

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

1.IServiceProvider

2.ActivatorUtilities

  • 用于创建未在DI容器中注册的服务实例
  • 用于某些框架级别的功能

构造函数注入

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

  • 构造函数可以吸取 非依赖 注入的参数,但必须提供默认值
  • 当服务通过
    1. IServiceProvider
    复制代码
    分析 时,要求构造函数必须是public
  • 当服务通过
    1. ActivatorUtilities
    复制代码
    分析 时,要求构造函数必须是public,固然 支持构造函数重载,但必须只能有一个是有用 的,即参数可以或许 全部通过依赖 注入得到值

方法注入

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

  1. [HttpGet]
  2. public string Get([FromServices]IMyService myService)
  3. {
  4. return "Ok";
  5. }
复制代码

属性注入

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

老例子 ,先定义服务和实现

  1. public interface IUserService
  2. {
  3. string Get();
  4. }
  5. public class UserService : IUserService
  6. {
  7. public string Get()
  8. {
  9. return "User";
  10. }
  11. }
复制代码

然后注册服务

  • 默认环境 下,控制器的构造函数参数由DI容器来管理吗,而控制器实例本身却是由ASP.NET Core框架来管理,以是 如许 “属性注入”是无法见效 的
  • 通过
    1. AddControllersAsServices
    复制代码
    方法,将控制器交给 autofac 容器来处理,如许 就可以使“属性注入”见效 了
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllers().AddControllersAsServices();
  4. }
  5. public void ConfigureContainer(ContainerBuilder builder)
  6. {
  7. builder.RegisterModule<AutofacModule>();
  8. }
  9. public class AutofacModule : Autofac.Module
  10. {
  11. protected override void Load(ContainerBuilder builder)
  12. {
  13. builder.RegisterType<UserService>().As<IUserService>();
  14. var controllerTypes = Assembly.GetExecutingAssembly().GetExportedTypes()
  15. .Where(type => typeof(ControllerBase).IsAssignableFrom(type))
  16. .ToArray();
  17. // 配置所有控制器均支持属性注入
  18. builder.RegisterTypes(controllerTypes).PropertiesAutowired();
  19. }
  20. }
复制代码

末了 ,我们在控制器中通过属性来吸取 服务实例

  1. public class ValuesController : ControllerBase
  2. {
  3. public IUserService UserService { get; set; }
  4. [HttpGet]
  5. public string Get()
  6. {
  7. return UserService.Get();
  8. }
  9. }
复制代码

通过调用

  1. Get
复制代码
接口,我们就可以得到
  1. IUserService
复制代码
的实例,从而得到相应

  1. User
复制代码

一些留意 事项

  • 避免使用 服务定位模式。只管 避免使用
    1. GetService
    复制代码
    来获取服务实例,而应该使用 DI。
  1. using Microsoft.Extensions.DependencyInjection;
  2. public class ValuesController : ControllerBase
  3. {
  4. private readonly IServiceProvider _serviceProvider;
  5. // 应通过依赖注入的方式获取服务实例
  6. public ValuesController(IServiceProvider serviceProvider)
  7. {
  8. _serviceProvider = serviceProvider;
  9. }
  10. [HttpGet]
  11. public string Get()
  12. {
  13. // 尽量避免通过 GetService 方法获取服务实例
  14. var myService = _serviceProvider.GetService<IMyService>();
  15. return "Ok";
  16. }
  17. }
复制代码
  • 避免在
    1. ConfigureServices
    复制代码
    中调用
    1. BuildServiceProvider
    复制代码
    。由于 这会导致创建第二个DI容器的副本,从而导致注册的单例服务出现多个副本。
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // 不要在该方法中调用该方法
  4. var serviceProvider = services.BuildServiceProvider();
  5. }
复制代码
  • 肯定 要留意 服务分析 范围,不要在 Singleton 中分析 Transient 或 Scoped 服务,这大概 导致服务状态错误(如导致服务实例生命周期提升 为单例)。答应 的方式有:

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

2.在 Scoped 或 Transient 服务中分析 Scoped 服务(不能和前面的Scoped服务类似 )

  • 当在
    1. Development
    复制代码
    环境中运行、并通过
    1. CreateDefaultBuilder
    复制代码
    天生 主机时,默认的服务提供程序会举行 如下检查:

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

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

  • 随着业务增长,必要 依赖 注入的服务也越来越多,建议使用 扩展方法,封装服务注入,定名 为
    1. Add{Group_Name}
    复制代码
    ,如将全部 AppService 的服务注册封装起来
  1. namespace Microsoft.Extensions.DependencyInjection
  2. {
  3. public static class ApplicationServiceCollectionExtensions
  4. {
  5. public static IServiceCollection AddApplicationService(this IServiceCollection services)
  6. {
  7. services.AddTransient<Service1>();
  8. services.AddScoped<Service2>();
  9. services.AddSingleton<Service3>();
  10. services.AddSingleton(sp => new Service4());
  11. return services;
  12. }
  13. }
  14. }
复制代码

然后在

  1. ConfigureServices
复制代码
中调用即可

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddApplicationService();
  4. }
复制代码

框架默认提供的服务

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

服务范例 生命周期
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

到此这篇关于明确 ASP.NET Core 依赖 注入(Dependency Injection)的文章就先容 到这了,更多相干 ASP.NET Core 依赖 注入内容请搜索 脚本之家从前 的文章或继续欣赏 下面的相干 文章渴望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar 浓茶人生 | 2021-9-15 23:38:20 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的网站,运动刷步数还是免费刷的,QQ和微信都可以刷,特别好用。访问地址:http://yd.mxswl.com 猫先森网络
回复

使用道具 举报

avatar Jean893 | 2021-9-20 09:29:34 | 显示全部楼层
缺乏激情了!
回复

使用道具 举报

avatar 123457748 | 2021-9-25 13:09:56 | 显示全部楼层
admin楼主是一个典型的文艺青年啊!
回复

使用道具 举报

avatar Harry192 | 2021-9-28 14:26:19 | 显示全部楼层
很多天不上线,一上线就看到这么给力的帖子!
回复

使用道具 举报

avatar 狂人阿飙湛 | 2021-9-28 15:07:20 | 显示全部楼层
不是惊喜,是惊吓!
回复

使用道具 举报

avatar ggp27 | 2021-9-29 10:11:06 | 显示全部楼层
好多兽医在广场上义诊,admin楼主去看看吧!
回复

使用道具 举报

avatar wb47 | 2021-9-29 10:13:52 | 显示全部楼层
admin楼主看起来很有学问!
回复

使用道具 举报

avatar 程狄矢 | 2021-9-29 10:16:06 | 显示全部楼层
小弟默默的路过贵宝地~~~
回复

使用道具 举报

avatar 聪明牛得 | 2021-9-29 10:19:43 | 显示全部楼层
知识就是力量啊!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则