以MediatR为代表的管道 和 中间件的区别与联系

180 阅读4分钟

MediatR 是 .NET 中实现中介者模式(Mediator Pattern)的库,通过进程内消息传递机制支持请求/响应、命令、查询、通知和事件的消息传递,采用泛型智能调度,有助于解耦组件,提高代码的可维护性和可扩展性,常用于 CQRS(命令查询职责分离)架构 。

主要应用场景及示例:

  1. 请求/响应模式:通过实现 IRequest<TResponse> 接口的请求类及对应的 IRequestHandler<TRequest, TResponse> 处理程序,处理复杂业务逻辑(如创建订单、获取用户信息等) 。
  2. 通知模式:使用 INotification 接口发布通知,多个处理程序可同时处理同一条通知(如用户注册后发送欢迎邮件、记录日志、通知分析服务等) 。
  3. 管道行为(Pipeline Behavior):通过实现 IPipelineBehavior<TRequest, TResponse> 接口,在请求处理前后插入自定义逻辑(如日志记录、验证、异常处理等) 。

示例:

  • 在 ASP.NET Core Web API 中,使用 MediatR 实现用户注册通知系统:当新用户注册时,发布 UserRegisteredNotification,多个处理程序分别处理发送邮件、记录日志、通知分析服务等操作,实现事件驱动的解耦架构 。

安装 MediatR 需通过 NuGet 包(MediatRMediatR.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。

✅ 目标

  1. ValidationBehavior 从“抛异常”改成 返回业务友好的错误结果(不抛异常)。
  2. 再加两个管道行为,看看它们 排队顺序 是怎么决定的。

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 CreateOrderCommand
    

    Handler 没被执行,返回 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(如验证、缓存、事务)。