WebAPI中AddSingleton与AddHostedService的DI注册疑问
我在WebAPI应用中开发了若干用于管理PC设备(主要通过串口)的服务,需通过API控制器访问。每个服务需以单例模式运行(仅一个实例),因每个服务对应一个串口。
当前采用<TInterface>:<TImplementationClass>模式,利用DI/IoC框架并支持模拟测试:
DeviceManagerService类实现IDeviceManagerService接口IDeviceManagerService继承自IHostedService,目的是在StartAsync中执行启动配置(若不注册为HostedService,仅首次访问时调用构造函数,不会自动执行启动方法)- API控制器
SerialPortController通过构造函数注入IDeviceManagerService调用服务方法
尝试多种服务注册方式均失败,仅以下方式可行:
builder.Services.AddSingleton<DeviceManagerService>(); builder.Services.AddSingleton<IDeviceManagerService>(sp => sp.GetRequiredService<DeviceManagerService>()); builder.Services.AddHostedService(sp => sp.GetRequiredService<DeviceManagerService>());
疑问解答
1. AddHostedService为何不注册接口?
AddHostedService的核心作用是向DI容器注册**IHostedService类型**的服务,.NET主机启动时会自动查找所有注册的IHostedService实例并调用其StartAsync方法。它并不关心你自定义的业务接口,只认IHostedService这个标准接口。
当你调用AddHostedService<T>时,容器只会记录IHostedService与T的映射关系,不会自动关联你的自定义接口(比如IDeviceManagerService)。如果直接注册接口作为HostedService,因为接口无法实例化,主机无法获取有效实例来启动。
2. 为何需分别为实现类和接口调用AddSingleton?
这么做是为了同时满足两个需求:
- 控制器注入需求:让控制器能通过
IDeviceManagerService接口获取实例,便于后续替换实现(比如测试时用模拟类) - HostedService实例一致性需求:保证HostedService启动的
DeviceManagerService实例,和控制器注入的是同一个单例对象
如果只注册AddSingleton<IDeviceManagerService, DeviceManagerService>(),容器中仅存在IDeviceManagerService到实现类的映射,没有直接注册DeviceManagerService类型。此时调用AddHostedService(sp => sp.GetRequiredService<DeviceManagerService>())会报错,因为容器找不到DeviceManagerService类型的注册项。
3. 仅用AddSingleton<IDeviceManagerService, DeviceManagerService>()注册后,能否结合AddHostedService委托创建实例?
可以。因为IDeviceManagerService继承自IHostedService,你可以通过委托从容器中获取接口实例并转换为实现类(或直接作为IHostedService),保证实例唯一:
builder.Services.AddSingleton<IDeviceManagerService, DeviceManagerService>(); // 方式1:直接转换为实现类 builder.Services.AddHostedService(sp => (DeviceManagerService)sp.GetRequiredService<IDeviceManagerService>()); // 方式2:利用接口继承关系,转换为IHostedService(更安全) builder.Services.AddHostedService(sp => sp.GetRequiredService<IDeviceManagerService>());
这种方式下,容器只会创建一个DeviceManagerService实例:既作为IDeviceManagerService供控制器注入,又作为IHostedService被主机启动执行StartAsync。
失败原因复盘
- 方式1失败:仅注册接口与实现类的映射,容器中没有
DeviceManagerService类型的注册,调用GetRequiredService<DeviceManagerService>()时无法找到服务 - 方式2失败:将接口作为HostedService注册,但接口无法实例化,主机无法获取有效
IHostedService实例;同时容器未注册IDeviceManagerService的映射,控制器注入时无法解析 - 方式3失败:
AddHostedService<DeviceManagerService>()会自动注册一个新的DeviceManagerService单例,与之前接口注册的实例不是同一个,导致出现两个独立实例
内容的提问来源于stack exchange,提问作者Simon

