mediatr 的 ipipelinebehavior 是请求处理链的控制点,可拦截、修改、记录或短路流程;注册顺序决定嵌套结构,必须显式 await next() 并 return,异步返回值需按契约判断是否提交事务,streamrequest 行为须避免阻塞枚举。

MediatR 的 IPipelineBehavior<trequest tresponse></trequest> 不是用来“加功能”的装饰器,而是请求处理链的控制点——它能拦截、修改、记录、甚至短路整个请求流程。用错顺序、漏掉 await next()、或在异步路径里丢掉返回值,都会让 Handler 静默失效,且无报错。
注册顺序决定执行嵌套结构
多个 IPipelineBehavior 按 AddTransient 注册顺序“套娃”:先注册的在外层,后注册的在内层。比如你希望日志总在最外层看到完整耗时,验证在中间拦截非法输入,事务在最里层提交,那注册顺序就得是:
services.AddTransient(typeof(IPipelineBehavior), typeof(LoggingBehavior))services.AddTransient(typeof(IPipelineBehavior), typeof(ValidationBehavior))services.AddTransient(typeof(IPipelineBehavior), typeof(UnitOfWorkBehavior))
反过来注册,验证就可能在事务已开始后才执行;而如果把限流行为放最里层,它就只作用于 Handler 本身,无法保护整个管道开销(比如日志序列化也该被限)。
Handle 方法里必须显式 await next() 并 return
这是最常踩的坑:写完前置逻辑后忘了调用 next(),或者 await next() 后没 return,导致后续 Handler 完全不执行,且不会抛异常——请求就卡在管道里,最终超时。
正确写法必须包含三要素:
- 前置逻辑(如记录开始时间、校验请求头)
var response = await next(cancellationToken)-
return response(不能省略,哪怕 response 是Task<unit></unit>)
错误示例:
public async Task<tresponse> Handle(TRequest request, RequestHandlerDelegate<tresponse> next, CancellationToken ct) {<br> LogStart(request);<br> await next(ct); // ❌ 缺少 return,response 被丢弃<br>}</tresponse></tresponse>
异步返回值类型影响 SaveChanges 的判断逻辑
在事务行为中,await _context.SaveChangesAsync() 不能无脑放在 await next() 后面。因为有些 Handler 只读不写(比如 IRequest<ienumerable>></ienumerable>),这时调用 SaveChangesAsync 是冗余甚至危险的(可能提前提交其他未完成变更)。
更安全的做法是检查 response 类型和状态:
- 若
response是ValueTask或已完成的Task,说明 Handler 已同步执行完毕,可判断是否需要提交 - 若 Handler 显式返回
default(TResponse)或空集合,不能作为“无变更”依据——得靠业务标记(如实现ITransactionCommand接口) - EF Core 的
ChangeTracker.HasChanges()可辅助判断,但要注意并发下可能误判
所以推荐按契约驱动:让写操作命令显式实现 ITransactionCommand,事务行为只对这类请求提交。
StreamRequest 管道行为要避免阻塞枚举
IStreamRequest<t></t> 返回 IAsyncEnumerable<t></t>,它的管道行为不能像普通请求那样直接 await next()——因为 next() 返回的是一个可枚举对象,不是立即完成的任务。
如果你写了:
public async Task<tresponse> Handle(...)<br>{<br> await LogBefore();<br> var stream = await next(); // ✅ 这步没问题<br> await LogAfter(); // ❌ 错!LogAfter 会在 stream 枚举完成前就执行<br> return stream;<br>}</tresponse>
会导致日志时间戳失真,且无法捕获流过程中的异常。正确做法是包装枚举器:
- 用
yield return手动遍历stream,在每次MoveNextAsync前后插日志或异常处理 - 或改用
AsyncEnumerable.ToAsyncEnumerable()+ 自定义 enumerator wrapper - 不要在管道里做任何需等待整个流结束的操作
这点容易被忽略:Stream 管道不是“请求前后”,而是“每个元素进出时”。











