MediatR 是 .NET 中实现中介者模式(Mediator Pattern)的库,通过进程内消息传递机制支持请求/响应、命令、查询、通知和事件的消息传递,采用泛型智能调度,有助于解耦组件,提高代码的可维护性和可扩展性,常用于 CQRS(命令查询职责分离)架构 。
主要应用场景及示例:
- 请求/响应模式:通过实现
IRequest<TResponse>接口的请求类及对应的IRequestHandler<TRequest, TResponse>处理程序,处理复杂业务逻辑(如创建订单、获取用户信息等) 。 - 通知模式:使用
INotification接口发布通知,多个处理程序可同时处理同一条通知(如用户注册后发送欢迎邮件、记录日志、通知分析服务等) 。 - 管道行为(Pipeline Behavior):通过实现
IPipelineBehavior<TRequest, TResponse>接口,在请求处理前后插入自定义逻辑(如日志记录、验证、异常处理等) 。
示例:
- 在 ASP.NET Core Web API 中,使用 MediatR 实现用户注册通知系统:当新用户注册时,发布
UserRegisteredNotification,多个处理程序分别处理发送邮件、记录日志、通知分析服务等操作,实现事件驱动的解耦架构 。
安装 MediatR 需通过 NuGet 包(MediatR 和 MediatR.Extensions.Microsoft.DependencyInjection),在 Program.cs 中通过 AddMediatR 方法注册服务,并配置处理程序。
下面把三种典型用法各举一个可运行的 最小完整示例。
三个示例都基于 ASP.NET Core 6/7 项目,NuGet 已安装:
MediatR
MediatR.Extensions.Microsoft.DependencyInjection
并在 Program.cs 里注册:
builder.Services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
1. 请求/响应(Query / Command 带返回值)
场景:根据商品 Id 查询商品详情。
// 请求对象
public record GetProductByIdQuery(int Id) : IRequest<ProductDto>;
// 响应对象
public record ProductDto(int Id, string Name, decimal Price);
// 处理器
public class GetProductByIdHandler : IRequestHandler<GetProductByIdQuery, ProductDto>
{
public Task<ProductDto> Handle(GetProductByIdQuery request, CancellationToken ct)
{
// 模拟仓储查询
var product = new ProductDto(request.Id, "键盘", 299m);
// 这个值是浏览器返回给用户的
return Task.FromResult(product);
}
}
// API Controller
app.MapGet("/products/{id}", async (int id, ISender mediatr) =>
await mediatr.Send(new GetProductByIdQuery(id)));
浏览器访问 /products/1 → {"id":1,"name":"键盘","price":299}
2. 通知(事件广播,一个消息多个订阅者)
场景:用户注册成功后,需要同时发欢迎邮件、写日志、送优惠券。
// 通知对象
public record UserRegisteredNotification(int UserId, string Email) : INotification;
// 邮件处理器
public class SendWelcomeEmailHandler : INotificationHandler<UserRegisteredNotification>
{
public Task Handle(UserRegisteredNotification notification, CancellationToken ct)
{
Console.WriteLine($"[Email] 欢迎信已发送至 {notification.Email}");
return Task.CompletedTask;
}
}
// 日志处理器
public class LogNewUserHandler : INotificationHandler<UserRegisteredNotification>
{
public Task Handle(UserRegisteredNotification notification, CancellationToken ct)
{
Console.WriteLine($"[Log] 用户 {notification.UserId} 已注册");
return Task.CompletedTask;
}
}
// API Controller:注册并发布通知
app.MapPost("/register", async (UserDto dto, IPublisher mediatr) =>
{
// 1. 保存用户到数据库(略)
int newUserId = 42;
// 2. 发布事件
await mediatr.Publish(new UserRegisteredNotification(newUserId, dto.Email));
// 3. 客户端的返回值,估计也能返回其他的吧
return Results.Ok();
});
public record UserDto(string Email);
调用 /register 后控制台输出:
[Email] 欢迎信已发送至 user@mail.com
[Log] 用户 42 已注册
3. 管道行为(Pipeline Behavior,AOP 式横切逻辑)
场景:对所有 IRequest 做参数验证,失败直接返回 400,不进入 Handler。
// 管道行为
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
// 1. 简单演示:如果请求是 CreateOrderCommand 且数量为 0,则抛异常
if (request is CreateOrderCommand cmd && cmd.Quantity <= 0)
throw new ValidationException("Quantity 必须大于 0");
// 2. 通过校验,继续管道
return await next();
}
}
// 注册管道
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
// 业务命令
public record CreateOrderCommand(string Sku, int Quantity) : IRequest<Guid>;
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Guid>
{
public Task<Guid> Handle(CreateOrderCommand request, CancellationToken ct)
{
Console.WriteLine($"创建订单:{request.Sku} × {request.Quantity}");
return Task.FromResult(Guid.NewGuid());
}
}
// API
app.MapPost("/orders", async (CreateOrderCommand cmd, ISender mediatr) =>
{
var id = await mediatr.Send(cmd);
// 返回给客户
return Results.Ok(id);
});
- POST
/orders且 body{"sku":"KB01","quantity":0}→ 返回 400 + “Quantity 必须大于 0”。 - POST body 正确 → 正常创建订单并返回新 Guid。
✅ 目标
- 把 ValidationBehavior 从“抛异常”改成 返回业务友好的错误结果(不抛异常)。
- 再加两个管道行为,看看它们 排队顺序 是怎么决定的。
1. 用“结果对象”代替抛异常
定义一个通用结果包装:
public record Result<T>(T? Data, bool Success, string? Error = null)
{
public static Result<T> Ok(T data) => new(data, true);
public static Result<T> Fail(string error) => new(default, false, error);
}
把 CreateOrderCommand 的返回类型改成 Result<Guid>:
public record CreateOrderCommand(string Sku, int Quantity) : IRequest<Result<Guid>>;
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Result<Guid>>
{
public Task<Result<Guid>> Handle(CreateOrderCommand request, CancellationToken ct)
{
Console.WriteLine($"创建订单:{request.Sku} × {request.Quantity}");
return Task.FromResult(Result<Guid>.Ok(Guid.NewGuid()));
}
}
2. ValidationBehavior(不抛异常版)
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : Result<Guid> // 限定为 Result<Guid> 方便演示
{
public async Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
if (request is CreateOrderCommand cmd && cmd.Quantity <= 0)
{
// 不抛异常,直接返回失败结果
return (TResponse)Result<Guid>.Fail("Quantity 必须大于 0");
}
// 验证通过,继续管道
return await next();
}
}
3. 再添加两个管道行为,看排队
a) LoggingBehavior(最早执行)
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
Console.WriteLine($"[Log] Start {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"[Log] End {typeof(TRequest).Name}");
return response;
}
}
b) CachingBehavior(最晚执行,示例用)
public class CachingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
Console.WriteLine($"[Cache] 检查缓存 {typeof(TRequest).Name}");
// 这里可以查缓存,简化演示直接跳过
return await next();
}
}
4. 注册顺序 = 执行顺序
// 先注册的先执行(洋葱模型:外 → 内 → Handler)
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CachingBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
执行顺序
Logging(入) → Caching(入) → Validation(入) → Handler → Validation(出) → Caching(出) → Logging(出)
5. API 返回结果
app.MapPost("/orders", async (CreateOrderCommand cmd, ISender mediatr) =>
{
var res = await mediatr.Send(cmd);
return res.Success
? Results.Ok(res.Data)
: Results.BadRequest(res.Error);
});
6. 运行观察
-
合法请求
POST /orders { "sku": "KB01", "quantity": 2 }控制台:
[Log] Start CreateOrderCommand [Cache] 检查缓存 CreateOrderCommand 创建订单:KB01 × 2 [Log] End CreateOrderCommand返回 200 + GUID
-
非法请求
POST /orders { "sku": "KB01", "quantity": 0 }控制台:
[Log] Start CreateOrderCommand [Cache] 检查缓存 CreateOrderCommand [Log] End CreateOrderCommandHandler 没被执行,返回 400 +
"Quantity 必须大于 0"
小结
- 返回状态值:把结果包装成
Result<T>,在 Behavior 中短路返回即可。 - 排队顺序:注册顺序 = 洋葱包裹顺序,先注册的最外层,后注册的越靠近 Handler。
总结
| 类型 | 接口 | 典型用途 | 示例 |
|---|---|---|---|
| 请求/响应 | IRequest<T> / IRequestHandler<,> | 查询/命令(有返回值) | 获取商品详情 |
| 通知/事件 | INotification / INotificationHandler<> | 事件广播(无返回值,多订阅) | 用户注册后多处理器 |
| 管道行为 | IPipelineBehavior<,> | 横切关注点(日志、验证、事务) | 统一验证参数 |
三个示例可直接复制到 Program.cs 运行验证。
一句话区别
- 中间件(Middleware)运行在 ASP.NET Core 的 HTTP 管道里,针对 整个 HTTP 请求/响应 做横切。
- 管道行为(Pipeline Behavior)运行在 MediatR 的消息派发管道里,只针对 某一个 IRequest / INotification 做横切。
1. 运行位置不同
| 层级 | 中间件 | Pipeline Behavior |
|---|---|---|
| 所在管道 | ASP.NET Core HTTP 管线 | MediatR 消息派发管线 |
| 触发时机 | 请求进入/离开服务器时 | IMediator.Send / Publish 内部调用时 |
| 是否关心业务类型 | 不关心,所有请求都路过 | 只关心 TRequest(可泛型过滤) |
2. 作用范围不同
- 中间件:全局横切,如身份验证、CORS、异常处理、压缩、静态文件等。
- Pipeline Behavior:业务层横切,如验证、缓存、事务、日志、审计、重试等。
3. 代码形态对比
中间件(HTTP 级)
public class TimingMiddleware
{
private readonly RequestDelegate _next;
public async Task InvokeAsync(HttpContext ctx)
{
var sw = Stopwatch.StartNew();
await _next(ctx); // 继续 HTTP 管线
sw.Stop();
Console.WriteLine($"HTTP {ctx.Request.Path} 耗时 {sw.ElapsedMilliseconds} ms");
}
}
Pipeline Behavior(业务级)
public class LoggingBehavior<TReq, TRes> : IPipelineBehavior<TReq, TRes>
{
public async Task<TRes> Handle(TReq req,
RequestHandlerDelegate<TRes> next,
CancellationToken ct)
{
Console.WriteLine($"开始处理 {typeof(TReq).Name}");
var res = await next(); // 继续 MediatR 管线
Console.WriteLine($"完成处理 {typeof(TReq).Name}");
return res;
}
}
4. 一张图秒懂
浏览器 ──> Routing ──> Authentication ──> CORS ──> Endpoint ──> 返回
(中间件) (中间件) (中间件)
Controller 内部:
IMediator.Send(new GetProductByIdQuery(1))
│
├─▶ LoggingBehavior ──> ValidationBehavior ──> GetProductByIdHandler
(Pipeline) (Pipeline) (业务逻辑)
什么时候用哪个?
- 需要 HTTP 级别 的功能 → 中间件(如鉴权、压缩、跨域)。
- 需要 业务级别 的功能 → Pipeline Behavior(如验证、缓存、事务)。