Skip to content

核心概念 ​

🎓 深入理解 MediatR 的核心 API 和设计思想


📋 本章概览 ​

本章将详细讲解 MediatR 的核心概念,包括:

  • IRequest / IRequest<TResponse> - 请求契约
  • IRequestHandler<TRequest, TResponse> - 请求处理器
  • IMediator / ISender / IPublisher - 中介者接口
  • INotification / INotificationHandler<T> - 通知机制
  • 生命周期与依赖注入 - 服务注册和管理

1️⃣ IRequest / IRequest<TResponse> ​

什么是 IRequest? ​

IRequest 是 MediatR 中定义请求契约的标记接口。它表示一个需要被处理的请求对象,类似于命令模式中的 Command。

两种类型 ​

类型 1:无返回值请求 IRequest ​

csharp
// 用于不需要返回值的操作(如创建、更新、删除)
public class CreateOrderCommand : IRequest
{
    public string ProductName { get; set; }
    public int Quantity { get; set; }
}

public class UpdateUserCommand : IRequest
{
    public Guid UserId { get; set; }
    public string Email { get; set; }
}

public class DeleteProductCommand : IRequest
{
    public Guid ProductId { get; set; }
}

类型 2:带返回值请求 IRequest<TResponse> ​

csharp
// 用于需要返回数据的操作(如查询、创建后返回ID)
public class GetOrderQuery : IRequest<OrderDto>
{
    public Guid OrderId { get; set; }
}

public class CreateUserCommand : IRequest<UserResult>
{
    public string Username { get; set; }
    public string Email { get; set; }
}

public class CalculatePriceQuery : IRequest<decimal>
{
    public Guid ProductId { get; set; }
    public int Quantity { get; set; }
}

命名约定 ​

MediatR 社区推荐的命名规范:

类型后缀示例
命令(写操作)CommandCreateOrderCommand, UpdateUserCommand
查询(读操作)QueryGetOrderQuery, ListProductsQuery
请求(通用)RequestSendEmailRequest, ProcessPaymentRequest

CQRS 模式推荐:

  • ✅ 使用 Command 表示写操作(不返回值或只返回ID)
  • ✅ 使用 Query 表示读操作(返回数据)

最佳实践 ​

✅ 好的做法 ​

csharp
// 1. 保持请求类简洁,只包含数据
public class CreateOrderCommand : IRequest<OrderResult>
{
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
}

// 2. 使用记录类型(record)简化代码(C# 9+)
public record CreateOrderCommand(
    string ProductName,
    int Quantity,
    decimal Price
) : IRequest<OrderResult>;

// 3. 添加验证属性
public class CreateOrderCommand : IRequest<OrderResult>
{
    [Required]
    [StringLength(100)]
    public string ProductName { get; set; }

    [Range(1, 1000)]
    public int Quantity { get; set; }

    [Range(0.01, double.MaxValue)]
    public decimal Price { get; set; }
}

❌ 避免的做法 ​

csharp
// 1. 不要在请求类中包含业务逻辑
public class CreateOrderCommand : IRequest
{
    public string ProductName { get; set; }
    
    // ❌ 错误:不要在请求类中执行业务逻辑
    public void Validate()
    {
        if (string.IsNullOrEmpty(ProductName))
            throw new ArgumentException("产品名称不能为空");
    }
}

// 2. 不要继承复杂的基类
public class CreateOrderCommand : BaseCommand, IRequest // ❌ 过度设计
{
}

// 3. 不要包含服务对象
public class CreateOrderCommand : IRequest
{
    public string ProductName { get; set; }
    public IOrderService OrderService { get; set; } // ❌ 错误:请求应该是纯数据
}

2️⃣ IRequestHandler<TRequest, TResponse> ​

什么是 IRequestHandler? ​

IRequestHandler 是处理请求的处理器接口。每个请求都应该有一个对应的处理器,实现具体的业务逻辑。

接口定义 ​

csharp
// 无返回值处理器
public interface IRequestHandler<in TRequest> 
    where TRequest : IRequest
{
    Task Handle(TRequest request, CancellationToken cancellationToken);
}

// 带返回值处理器
public interface IRequestHandler<in TRequest, TResponse> 
    where TRequest : IRequest<TResponse>
{
    Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}

实现示例 ​

示例 1:无返回值处理器 ​

csharp
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand>
{
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<CreateOrderHandler> _logger;

    public CreateOrderHandler(
        IOrderRepository orderRepository,
        ILogger<CreateOrderHandler> logger)
    {
        _orderRepository = orderRepository;
        _logger = logger;
    }

    public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        _logger.LogInformation("开始创建订单: {ProductName}", request.ProductName);

        var order = new Order
        {
            ProductName = request.ProductName,
            Quantity = request.Quantity,
            CreatedAt = DateTime.UtcNow
        };

        await _orderRepository.AddAsync(order, cancellationToken);

        _logger.LogInformation("订单创建成功: {OrderId}", order.Id);
    }
}

示例 2:带返回值处理器 ​

csharp
public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDto>
{
    private readonly IOrderRepository _orderRepository;
    private readonly IMapper _mapper;

    public GetOrderHandler(IOrderRepository orderRepository, IMapper mapper)
    {
        _orderRepository = orderRepository;
        _mapper = mapper;
    }

    public async Task<OrderDto> Handle(GetOrderQuery request, CancellationToken cancellationToken)
    {
        var order = await _orderRepository.GetByIdAsync(request.OrderId, cancellationToken);

        if (order == null)
            throw new NotFoundException($"订单 {request.OrderId} 不存在");

        return _mapper.Map<OrderDto>(order);
    }
}

关键特性 ​

1. 单一职责原则 ​

每个 Handler 只处理一种请求,保持职责单一:

csharp
// ✅ 正确:每个 Handler 职责单一
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    // 只负责创建订单
}

public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDto>
{
    // 只负责查询订单
}

// ❌ 错误:一个 Handler 处理多种请求
public class OrderHandler : 
    IRequestHandler<CreateOrderCommand>,
    IRequestHandler<GetOrderQuery>
{
    // 违反单一职责原则
}

2. 依赖注入 ​

Handler 可以通过构造函数注入所需的服务:

csharp
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepo;
    private readonly IPaymentService _paymentService;
    private readonly IEmailService _emailService;
    private readonly ILogger<CreateOrderHandler> _logger;

    // 通过构造函数注入所有依赖
    public CreateOrderHandler(
        IOrderRepository orderRepo,
        IPaymentService paymentService,
        IEmailService emailService,
        ILogger<CreateOrderHandler> logger)
    {
        _orderRepo = orderRepo;
        _paymentService = paymentService;
        _emailService = emailService;
        _logger = logger;
    }

    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 使用注入的服务执行业务逻辑
        var order = await _orderRepo.CreateAsync(request);
        await _paymentService.ProcessAsync(order.Id);
        await _emailService.SendConfirmationAsync(order.CustomerEmail);
        
        return new OrderResult { OrderId = order.Id };
    }
}

3. 异常处理 ​

在 Handler 中抛出异常,由上层或管道行为处理:

csharp
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 参数验证
        if (request.Quantity <= 0)
            throw new ValidationException("数量必须大于0");

        // 业务逻辑
        var order = await _orderRepo.CreateAsync(request);

        if (order == null)
            throw new BusinessException("订单创建失败");

        return new OrderResult { OrderId = order.Id };
    }
}

3️⃣ IMediator / ISender / IPublisher ​

三大核心接口 ​

MediatR 提供了三个主要接口用于发送请求和发布通知:

接口用途主要方法
IMediator统一接口(包含 Sender 和 Publisher)Send(), Publish()
ISender仅发送请求/响应Send()
IPublisher仅发布通知Publish()

IMediator ​

最常用的接口,同时支持请求/响应和发布/订阅:

csharp
public interface IMediator : ISender, IPublisher
{
}

// 使用示例
public class OrdersController : ControllerBase
{
    private readonly IMediator _mediator;

    public OrdersController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> CreateOrder([FromBody] CreateOrderCommand command)
    {
        // 发送请求(请求/响应)
        var result = await _mediator.Send(command);
        return Ok(result);
    }

    [HttpPost("{id}/cancel")]
    public async Task<IActionResult> CancelOrder(Guid id)
    {
        // 发布通知(发布/订阅)
        await _mediator.Publish(new OrderCancelledNotification { OrderId = id });
        return NoContent();
    }
}

ISender ​

专注于请求/响应模式:

csharp
public interface ISender
{
    // 发送请求并等待响应
    Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
    
    // 发送无返回值请求
    Task Send<TRequest>(TRequest request, CancellationToken cancellationToken = default) 
        where TRequest : IRequest;
    
    // 流式请求(MediatR 11+)
    IAsyncEnumerable<TResponse> CreateStream<TResponse>(IStreamRequest<TResponse> request, CancellationToken cancellationToken = default);
}

// 使用示例
public class QueryService
{
    private readonly ISender _sender;

    public QueryService(ISender sender)
    {
        _sender = sender;
    }

    public async Task<OrderDto> GetOrder(Guid orderId)
    {
        return await _sender.Send(new GetOrderQuery { OrderId = orderId });
    }
}

IPublisher ​

专注于发布/订阅模式:

csharp
public interface IPublisher
{
    // 发布通知
    Task Publish(object notification, CancellationToken cancellationToken = default);
    
    // 泛型发布
    Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default) 
        where TNotification : INotification;
}

// 使用示例
public class DomainEventService
{
    private readonly IPublisher _publisher;

    public DomainEventService(IPublisher publisher)
    {
        _publisher = publisher;
    }

    public async Task RaiseDomainEvent(IDomainEvent domainEvent)
    {
        await _publisher.Publish(domainEvent);
    }
}

选择建议 ​

场景推荐接口
同时使用请求和通知IMediator
只使用请求/响应(CQRS 查询侧)ISender
只发布领域事件IPublisher
想明确区分读写查询用 ISender,命令用 IMediator

4️⃣ 请求 vs 通知 ​

核心区别 ​

特性请求(Request)通知(Notification)
接口IRequest<T>INotification
处理器IRequestHandler<T>INotificationHandler<T>
处理器数量一个请求只有一个处理器一个通知可以有多个处理器
返回值可以有返回值无返回值(void)
调用方式_mediator.Send()_mediator.Publish()
执行模式点对点(Point-to-Point)发布/订阅(Publish/Subscribe)
异常处理异常直接抛出默认一个处理器异常会中断后续处理器
典型场景CQRS 命令和查询领域事件、审计日志

对比示例 ​

请求(一对一) ​

csharp
// 定义请求
public class CreateOrderCommand : IRequest<OrderResult>
{
    public string ProductName { get; set; }
}

// 定义处理器(只能有一个)
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 创建订单逻辑
        return new OrderResult { OrderId = Guid.NewGuid() };
    }
}

// 发送请求
var result = await _mediator.Send(new CreateOrderCommand { ProductName = "iPhone" });
// 只有 CreateOrderHandler 会被调用

通知(一对多) ​

csharp
// 定义通知
public class OrderCreatedNotification : INotification
{
    public Guid OrderId { get; set; }
}

// 处理器 1:发送邮件
public class SendEmailHandler : INotificationHandler<OrderCreatedNotification>
{
    public Task Handle(OrderCreatedNotification notification, CancellationToken ct)
    {
        Console.WriteLine("发送邮件");
        return Task.CompletedTask;
    }
}

// 处理器 2:更新缓存
public class UpdateCacheHandler : INotificationHandler<OrderCreatedNotification>
{
    public Task Handle(OrderCreatedNotification notification, CancellationToken ct)
    {
        Console.WriteLine("更新缓存");
        return Task.CompletedTask;
    }
}

// 处理器 3:写审计日志
public class AuditLogHandler : INotificationHandler<OrderCreatedNotification>
{
    public Task Handle(OrderCreatedNotification notification, CancellationToken ct)
    {
        Console.WriteLine("写审计日志");
        return Task.CompletedTask;
    }
}

// 发布通知
await _mediator.Publish(new OrderCreatedNotification { OrderId = Guid.NewGuid() });
// 所有三个处理器都会被调用
// 输出:
// 发送邮件
// 更新缓存
// 写审计日志

5️⃣ 生命周期与依赖注入 ​

服务生命周期 ​

MediatR 中的服务可以配置不同的生命周期:

生命周期说明适用场景
Transient每次请求创建新实例默认,推荐大多数情况
Scoped每个请求范围创建一个实例Web 应用中每个 HTTP 请求
Singleton整个应用生命周期共享无状态服务、缓存

配置生命周期 ​

csharp
// 默认 Transient
builder.Services.AddMediatR(cfg => 
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));

// 自定义为 Scoped
builder.Services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.Lifetime = ServiceLifetime.Scoped;
});

Handler 的生命周期 ​

Handler 的生命周期由容器管理,默认是 Transient:

csharp
// 每次 Send() 都会创建新的 Handler 实例
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand>
{
    public CreateOrderHandler()
    {
        Console.WriteLine("CreateOrderHandler 实例创建");
    }

    public async Task Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 处理逻辑
    }
}

// 测试
await _mediator.Send(command1); // 创建实例 1
await _mediator.Send(command2); // 创建实例 2

管道行为的生命周期 ​

管道行为通常注册为 Transient:

csharp
// 每次请求都会创建新的 Behavior 实例链
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

依赖注入最佳实践 ​

✅ 推荐做法 ​

csharp
// 1. Handler 通过构造函数注入依赖
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepo;
    private readonly ILogger<CreateOrderHandler> _logger;

    public CreateOrderHandler(IOrderRepository orderRepo, ILogger<CreateOrderHandler> logger)
    {
        _orderRepo = orderRepo;
        _logger = logger;
    }
}

// 2. 使用接口而非具体实现
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepo; // ✅ 接口

    public CreateOrderHandler(IOrderRepository orderRepo)
    {
        _orderRepo = orderRepo;
    }
}

// 3. Repository 注册为 Scoped(Web 应用)
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

❌ 避免的做法 ​

csharp
// 1. 不要在 Handler 中使用服务定位器
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand>
{
    public async Task Handle(CreateOrderCommand request, CancellationToken ct)
    {
        var repo = ServiceLocator.Get<IOrderRepository>(); // ❌ 反模式
    }
}

// 2. 不要在 Singleton 中注入 Scoped 服务
public class CacheService // Singleton
{
    private readonly DbContext _context; // ❌ Scoped 服务

    public CacheService(DbContext context)
    {
        _context = context; // 会导致内存泄漏
    }
}

// 3. 不要循环依赖
public class OrderHandler : IRequestHandler<OrderCommand>
{
    private readonly PaymentHandler _paymentHandler; // ❌ 循环依赖
}

🎯 核心概念总结 ​

知识图谱 ​

mermaid
graph TB
    A[IRequest/IRequest-T-] --> B[IRequestHandler]
    B --> C[IMediator.Send]
    
    D[INotification] --> E[INotificationHandler]
    E --> F[IMediator.Publish]
    
    G[IPipelineBehavior] --> H[装饰器链]
    H --> B
    
    I[ISender] --> C
    J[IPublisher] --> F
    
    K[DI Container] --> B
    K --> E
    K --> G
    
    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#ffe1f5
    style D fill:#e1ffe1
    style E fill:#f5e1ff
    style F fill:#ffe1e1

关键要点 ​

  1. IRequest 定义请求契约,分为有返回值和无返回值两种
  2. IRequestHandler 实现业务逻辑,遵循单一职责原则
  3. IMediator 是统一接口,ISender 专注请求,IPublisher 专注通知
  4. 请求是一对一,通知是一对多
  5. 管道行为通过装饰器模式实现横切关注点
  6. 依赖注入管理 Handler 和 Behavior 的生命周期

📝 练习题 ​

练习 1:识别请求类型 ​

以下场景应该使用 IRequest 还是 IRequest<T>?

  1. 创建用户,返回用户ID
  2. 更新用户邮箱
  3. 删除订单
  4. 查询订单列表
  5. 计算购物车总价
点击查看答案解析
  1. ✅ IRequest<UserResult> - 需要返回ID
  2. ✅ IRequest - 无需返回值
  3. ✅ IRequest - 无需返回值
  4. ✅ IRequest<List<OrderDto>> - 需要返回数据
  5. ✅ IRequest<decimal> - 需要返回计算结果

练习 2:设计 Handler ​

为一个电商系统设计以下 Handler:

  1. 创建订单
  2. 查询订单详情
  3. 取消订单(发布通知)
查看参考实现
csharp
// 1. 创建订单
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
    public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 实现创建逻辑
        return new OrderResult { OrderId = Guid.NewGuid() };
    }
}

// 2. 查询订单
public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDto>
{
    public async Task<OrderDto> Handle(GetOrderQuery request, CancellationToken ct)
    {
        // 实现查询逻辑
        return new OrderDto();
    }
}

// 3. 取消订单(发布通知)
public class CancelOrderHandler : IRequestHandler<CancelOrderCommand>
{
    private readonly IMediator _mediator;

    public async Task Handle(CancelOrderCommand request, CancellationToken ct)
    {
        // 取消订单逻辑
        
        // 发布通知
        await _mediator.Publish(new OrderCancelledNotification 
        { 
            OrderId = request.OrderId 
        }, ct);
    }
}

🚀 下一步 ​

掌握了核心概念后,继续学习:

  1. 管道行为 - MediatR 最强大的特性
  2. 通知机制 - 实现发布/订阅模式
  3. ASP.NET Core 集成 - 在 Web 应用中应用

💡 提示:理解这些核心概念是掌握 MediatR 的基础。建议多次阅读并结合代码示例实践,直到能够熟练运用。

Released under the CC BY-SA 4.0 License.