Asp.Net Core依赖注入中继承服务时如何传递构造函数参数
问题
我正在为Asp.Net Core 6应用创建一个继承自BackgroundService的服务基类:
public class MyBaseService : BackgroundService { private readonly IMyCommand _command; private readonly ILogger<MyBaseService> _logger; private readonly IOtherDep _dep; public MyBaseService( IMyCommand command, ILogger<MyBaseService> logger, IOtherDep dep ) { _command = command; _logger = logger; _dep = dep; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { <...> await _command.ExecuteAsync(); <...> } }
现在需要创建多个派生类并将它们注册到依赖注入容器中。请问当前的实现方式是否正确?如何在不传递IServiceProvider(这被认为是反模式)的情况下,在依赖注入中使用构造函数参数?
派生类示例:
public class MyChildService1 : MyBaseService { public MyChildService1( IMyCommand cmd, <?>) : base(cmd, <?>) { } }
DI注册代码:
services.AddHostedService<MyChildService1>(sp => { var param = <...> return new MyChildService1( new MyCommand1(param), <?> ); }); services.AddHostedService<MyChildService2>(...);
解决方案
当前实现的核心问题
- 直接在DI注册工厂中
new MyCommand1(param)会绕过依赖注入容器,导致MyCommand1内部的依赖无法被容器自动管理,也不利于后续测试和维护。 - 派生类构造函数未正确传递基类所需的
ILogger<MyBaseService>和IOtherDep依赖,需要手动从容器获取,容易出错且不符合DI设计原则。
正确实现方式
1. 优化派生类构造函数
派生类需在构造函数中接收基类所需的所有依赖,通过base关键字传递给基类。若派生类需要绑定特定命令实现,可直接注入具体命令类型(而非通用接口):
// 绑定MyCommand1的派生服务 public class MyChildService1 : MyBaseService { public MyChildService1( MyCommand1 cmd, ILogger<MyBaseService> logger, IOtherDep dep ) : base(cmd, logger, dep) { // 派生类自定义逻辑可在此添加 } } // 无需绑定特定命令的派生服务 public class MyChildService2 : MyBaseService { public MyChildService2( IMyCommand cmd, ILogger<MyBaseService> logger, IOtherDep dep ) : base(cmd, logger, dep) { } }
2. 规范DI注册流程
方式一:注册特定命令实现并自动注入
先将命令实现注册到容器,再直接注册托管服务,容器会自动解析所有构造函数依赖:
// 注册MyCommand1,通过容器获取配置参数 services.AddSingleton<MyCommand1>(sp => { var config = sp.GetRequiredService<IConfiguration>(); var param = config.GetValue<string>("Command1:Param"); return new MyCommand1(param); }); // 直接注册托管服务,容器自动注入所有依赖 services.AddHostedService<MyChildService1>(); // 同理注册MyChildService2对应的命令和服务 services.AddSingleton<MyCommand2>(sp => { var param = sp.GetRequiredService<IOtherParamSource>().GetParam(); return new MyCommand2(param); }); services.AddHostedService<MyChildService2>();
方式二:泛型基类简化多服务注册
若派生服务逻辑高度一致,仅命令实现不同,可将基类改为泛型:
// 泛型基类 public class BaseHostedService<TCommand> : BackgroundService where TCommand : IMyCommand { private readonly TCommand _command; private readonly ILogger<BaseHostedService<TCommand>> _logger; private readonly IOtherDep _dep; public BaseHostedService( TCommand command, ILogger<BaseHostedService<TCommand>> logger, IOtherDep dep ) { _command = command; _logger = logger; _dep = dep; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { <...> await _command.ExecuteAsync(); <...> } } // 派生类无需额外逻辑 public class MyChildService1 : BaseHostedService<MyCommand1> { public MyChildService1( MyCommand1 command, ILogger<BaseHostedService<MyCommand1>> logger, IOtherDep dep ) : base(command, logger, dep) { } }
注册时同样只需注册命令和托管服务:
services.AddSingleton<MyCommand1>(sp => { var param = sp.GetRequiredService<IConfiguration>().GetValue<string>("Command1:Param"); return new MyCommand1(param); }); services.AddHostedService<MyChildService1>();
3. 避免IServiceProvider反模式的核心原则
- 所有依赖通过构造函数注入,不在构造函数或业务逻辑中手动使用
IServiceProvider。 - 容器负责管理所有对象生命周期,避免手动
new需要依赖的类型。 - 传递配置参数优先使用Options模式(如
IOptions<Command1Settings>),而非硬编码或手动传递。
内容的提问来源于stack exchange,提问作者egeo
相关产品推荐
相关产品推荐

