MediatR初始化失败主因是v12后移除自动扫描,须用泛型重载显式注册程序集,如AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));IRequest用于无返回值操作,IRequest用于需返回值场景,TResponse不可为void,应改用Unit;命令用Send,事件用Publish,Handler中依赖注入失败多因生命周期冲突或未await异步调用。

MediatR 初始化失败:为什么 AddMediatR 找不到服务注册方法
常见错误是直接调用 AddMediatR() 却提示“未找到该扩展方法”。这是因为 MediatR 从 v12 开始移除了对程序集自动扫描的支持,AddMediatR 不再接受空参数或仅传入 Assembly,必须显式指定处理程序所在的程序集。
正确做法是使用泛型重载,传入任意一个含 IRequestHandler 或 INotificationHandler 的类型(推荐用 Program 类本身,避免额外引用):
// .NET 6+ Program.cs builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
如果处理程序分散在多个程序集,需重复调用或合并为一个 Assembly.GetExecutingAssembly() 集合。漏掉某个程序集,对应命令/查询就会抛出 InvalidOperationException: Handler was not found for request。
写命令和查询时,IRequest<tresponse></tresponse> 和 IRequest 怎么选
区分关键不在“有没有返回值”,而在于你是否需要同步获取结果。CQRS 中的“Query”本质是只读操作,但技术上它仍是一个 IRequest<tresponse></tresponse>;“Command”通常无返回值,但也可返回状态(如 Guid 新建 ID 或 Result<bool></bool>)。
-
IRequest:适合不关心执行结果的场景,比如发通知、记录日志、触发后台任务——MediatR 会等待 handler 执行完才返回,但调用方拿不到任何值 -
IRequest<tresponse></tresponse>:必须返回值,且TResponse不能为void;若想返回空对象,用Unit(MediatR 自带的占位类型)
错误示例:public class CreateUserCommand : IRequest<void></void> 会编译失败;正确写法是 IRequest<unit></unit> 或直接 IRequest。
Send vs Publish:命令该用哪个,事件又该用哪个
Send 是点对点的,严格一对一:一个请求(IRequest)对应唯一一个 handler,执行后返回结果;Publish 是一对多广播,用于领域事件(INotification),所有注册的 INotificationHandler 都会被调用,且不等待全部完成(默认并行,可配置顺序)。
典型误用:
- 用
Publish调用命令(如Publish(new CreateUserCommand()))→ 语义错乱,违反 CQRS 原则,且无法捕获 handler 抛出的异常(除非显式await并启用NotificationPublisher配置) - 用
Send发送事件(如Send(new UserCreatedEvent()))→ 编译失败,因为UserCreatedEvent实现的是INotification,不是IRequest
记住口诀:命令走 Send,事件走 Publish;类型签名就是最硬的约束。
Handler 里依赖注入失败:为什么 IMediator 在构造函数里能用,但 DbContext 报 null
根本原因不是 MediatR,而是 ASP.NET Core 生命周期管理。MediatR 默认把所有 handler 注册为 Scoped,但如果 handler 构造函数中注入了 Transient 或 Singleton 服务,而该服务又依赖 Scoped(比如 DbContext),就会触发“scoped service cannot be consumed by singleton”类错误。
检查点:
- 确认
DbContext是通过AddDbContext注册的,默认生命周期为Scoped - 不要在 handler 中 new 出非托管资源或手动 resolve
IServiceScope——handler 本身就是 scoped 上下文的一部分 - 若 handler 需要异步 DB 操作,确保方法签名是
Task<tresponse> Handle(...)</tresponse>,且调用方用了await mediator.Send(...)
最容易被忽略的是 handler 内部嵌套调用另一个 Send 时,没加 await,导致上下文提前释放,后续 DB 操作报 ObjectDisposedException。


















