You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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和托管服务的依赖,完全遵循依赖倒置原则,不需要使用静态类或服务定位器反模式:

  1. 抽离EventCollectorHostedService中需要被外部Handler调用的公共能力,定义独立的抽象接口,例如IEventBuffer:
    public interface IEventBuffer
    {
        void AddIncomingEvent(IncomingEvent evt);
        // 其他需要被外部调用的公共方法定义
    }
    
  2. 让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发送通知的逻辑
        }
    }
    
  3. 调整服务注册逻辑,先注册抽象接口和对应实现的单例,再注册托管服务复用同一个实例,最后注册MediatR:
    // 先注册单例的缓冲服务抽象与实现
    services.AddSingleton<IEventBuffer, EventCollectorHostedService>();
    // 注册托管服务时,直接从容器获取已注册的实例,避免重复创建
    services.AddHostedService(sp => (EventCollectorHostedService)sp.GetRequiredService<IEventBuffer>());
    // 最后注册MediatR组件
    services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);
    
  4. 修改CollectIncomingEventNotificationHandler的依赖注入逻辑,不再依赖具体的托管服务类,改为依赖抽象的IEventBuffer接口:
    private readonly IEventBuffer _collector;
    
    public CollectIncomingEventNotificationHandler(IEventBuffer collector)
    {
        _collector = collector;
    }
    

这种方案的优势:

  • 完全符合SOLID原则,上层Handler不依赖具体服务实现,只依赖抽象,后续替换缓冲实现、做单元测试都非常方便
  • 从根源解决服务解析失败的问题,不存在生命周期不匹配的隐患
  • 依赖方向清晰,不会产生循环依赖问题:托管服务依赖MediatR发送批次就绪通知,Handler依赖缓冲接口写入新事件,两者没有直接的强耦合
  • 不需要使用静态类、服务定位器这类破坏代码可维护性的临时方案

内容的提问来源于stack exchange,提问作者Masteroxify

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 18:21:52