mediatr管道行为需显式注册到di容器才生效,推荐使用services.addtransient(typeof(ipipelinebehavior), typeof(yourpipeline))泛型注册;多个管道按注册顺序从外向内包裹请求,最先注册的最外层、最先执行。

MediatR管道中间件怎么注册才能生效
MediatR的管道(IPipelineBehavior)不是自动启用的,必须显式注册到DI容器,且注册顺序影响执行逻辑。如果你加了管道但没看到日志或校验触发,大概率是漏了这步。
关键点:
-
AddMediatR()默认不注册任何管道,需手动调用AddTransient<ipipelinebehavior tresponse>, YourPipeline<trequest tresponse>>()</trequest></ipipelinebehavior> - 推荐用泛型注册方式,避免为每个请求类型重复写注册项:
services.AddTransient(typeof(IPipelineBehavior), typeof(ValidationPipeline));
- 若同时注册多个管道(如日志 + 校验 + 缓存),它们按注册顺序**从外向内**包裹请求——最先注册的管道最外层,最后注册的最内层(即最先执行、最后退出)
如何在管道里做请求参数校验(非FluentValidation)
直接在 IPipelineBehavior<trequest tresponse>.Handle()</trequest> 里写校验逻辑可行,但容易和业务耦合。更稳妥的做法是提取校验规则到独立接口,并利用反射自动发现标记了校验特性的属性。
常见错误现象:ArgumentNullException 抛在 Handle() 外层,导致管道捕获不到;或校验失败后直接 return default,绕过了后续管道和实际处理器。
正确做法:
- 校验失败时抛出
ValidationException(或自定义异常),让上层统一处理,不要静默返回 - 用
typeof(TRequest).GetCustomAttributes<validateattribute>()</validateattribute>动态读取校验标记,避免硬编码字段名 - 注意值类型请求(如
int或struct)无法为null,判空前先用default(TRequest).Equals(request)检查是否为默认值
日志管道为什么没记录到请求体和响应结果
MediatR管道中 TRequest 和 TResponse 是泛型,直接 logger.LogInformation("{@Request}", request) 可能因序列化器限制(如 System.Text.Json 默认忽略私有字段或循环引用)而输出空对象或报错。
实操建议:
- 对请求/响应对象打
[JsonObject(MemberSerialization.OptIn)](Newtonsoft)或[JsonInclude](System.Text.Json),显式声明要序列化的字段 - 避免在日志管道里记录敏感字段(如密码、token),可用
JsonIgnore或临时脱敏:var safeRequest = new { request.Id, request.Name }; - 响应结果如果是
Task<t></t>类型,必须 await 后再记录,否则日志中拿到的是未完成的Task对象,不是实际值
为什么ValidationPipeline和LoggingPipeline的执行顺序和预期相反
管道执行是“洋葱模型”:注册顺序决定包裹层级。比如你先注册日志、后注册校验,实际执行顺序是:日志 → 校验 → Handler → 校验 → 日志。这意味着校验失败时,日志管道的“结束”部分不会执行,但“开始”部分已执行过。
容易被忽略的细节:
- 如果校验管道抛出异常,后续管道的
Handle()方法不会进入,但已进入的管道(如日志的前置部分)仍会继续执行其await next()后的代码——所以日志里可能看到“Started”但没有“Completed” - 想让校验优先于日志(即只对合法请求打完整日志),就把校验管道注册在日志管道之前
- 调试时可在每个管道的
Handle开头加Console.WriteLine($"[{nameof(MyPipeline)}] entered")快速验证顺序










