ASP.NET中MediatR注册顺序致HostedService依赖解析失败如何解决
问题背景
我们正在开发从AWS SQS采集数据并批量发送至客户端的服务,采用MediatR发布通知,程序架构如下:
问题描述
故障出在MediatR的首个NotificationHandler中,相关实现代码如下:
private readonly EventCollectorHostedService _collector; public CollectIncomingEventNotificationHandler(EventCollectorHostedService collector) { _collector = collector; }
EventCollectorHostedService自身会调用MediatR发布“批次已就绪可发送”的通知。运行时抛出错误:
无法构造CollectIncomingEventNotificationHandler实例,原因 -> 无法解析类型为'Api.Services.HostedServices.EventCollectorHostedService'的服务。
当前服务注册代码如下:
services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly); services.AddHostedService<EventCollectorHostedService>();
目前已知的两种方案存在明显缺陷,不符合整洁编码要求:
- 将
EventCollectorHostedService的部分功能声明为静态 - 不直接注入
EventCollectorHostedService、改为注入IServiceProvider实现服务定位
注意:这个问题的核心不是单纯的注册顺序:AddHostedService<EventCollectorHostedService>()默认只会将服务注册为IHostedService类型的单例,不会将具体实现类EventCollectorHostedService本身注册到容器中,就算调换两个注册方法的顺序,直接注入具体类也会抛出解析失败的错误。同时直接在Scoped生命周期的NotificationHandler中注入单例的托管服务具体实现,也违背了依赖倒置原则,后续维护和测试成本很高。
规范解决方案
通过抽象接口解耦Handler和托管服务的依赖,完全遵循依赖倒置原则,不需要使用静态类或服务定位器反模式:
- 抽离
EventCollectorHostedService中需要被外部Handler调用的公共能力,定义独立的抽象接口,例如IEventBuffer:public interface IEventBuffer { void AddIncomingEvent(IncomingEvent evt); // 其他需要被外部调用的公共方法定义 } - 让
EventCollectorHostedService继承BackgroundService的同时实现该接口,把内部缓冲相关的公共逻辑放到接口实现中:public class EventCollectorHostedService : BackgroundService, IEventBuffer { // 内部用通道做批量缓冲 private readonly Channel<IncomingEvent> _buffer = Channel.CreateUnbounded<IncomingEvent>(); // 实现接口方法,供外部Handler写入待处理事件 public void AddIncomingEvent(IncomingEvent evt) { if (!_buffer.Writer.TryWrite(evt)) { // 可自行处理写入失败逻辑 } } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 保留原有批量读取缓冲、达到批次阈值后调用MediatR发送通知的逻辑 } } - 调整服务注册逻辑,先注册抽象接口和对应实现的单例,再注册托管服务复用同一个实例,最后注册MediatR:
// 先注册单例的缓冲服务抽象与实现 services.AddSingleton<IEventBuffer, EventCollectorHostedService>(); // 注册托管服务时,直接从容器获取已注册的实例,避免重复创建 services.AddHostedService(sp => (EventCollectorHostedService)sp.GetRequiredService<IEventBuffer>()); // 最后注册MediatR组件 services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly); - 修改
CollectIncomingEventNotificationHandler的依赖注入逻辑,不再依赖具体的托管服务类,改为依赖抽象的IEventBuffer接口:private readonly IEventBuffer _collector; public CollectIncomingEventNotificationHandler(IEventBuffer collector) { _collector = collector; }
这种方案的优势:
- 完全符合SOLID原则,上层Handler不依赖具体服务实现,只依赖抽象,后续替换缓冲实现、做单元测试都非常方便
- 从根源解决服务解析失败的问题,不存在生命周期不匹配的隐患
- 依赖方向清晰,不会产生循环依赖问题:托管服务依赖MediatR发送批次就绪通知,Handler依赖缓冲接口写入新事件,两者没有直接的强耦合
- 不需要使用静态类、服务定位器这类破坏代码可维护性的临时方案
内容的提问来源于stack exchange,提问作者Masteroxify
相关产品推荐
相关产品推荐

