核心概念
🎓 深入理解 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 社区推荐的命名规范:
| 类型 | 后缀 | 示例 |
|---|---|---|
| 命令(写操作) | Command | CreateOrderCommand, UpdateUserCommand |
| 查询(读操作) | Query | GetOrderQuery, ListProductsQuery |
| 请求(通用) | Request | SendEmailRequest, 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关键要点
- IRequest 定义请求契约,分为有返回值和无返回值两种
- IRequestHandler 实现业务逻辑,遵循单一职责原则
- IMediator 是统一接口,ISender 专注请求,IPublisher 专注通知
- 请求是一对一,通知是一对多
- 管道行为通过装饰器模式实现横切关注点
- 依赖注入管理 Handler 和 Behavior 的生命周期
📝 练习题
练习 1:识别请求类型
以下场景应该使用 IRequest 还是 IRequest<T>?
- 创建用户,返回用户ID
- 更新用户邮箱
- 删除订单
- 查询订单列表
- 计算购物车总价
点击查看答案解析
- ✅
IRequest<UserResult>- 需要返回ID - ✅
IRequest- 无需返回值 - ✅
IRequest- 无需返回值 - ✅
IRequest<List<OrderDto>>- 需要返回数据 - ✅
IRequest<decimal>- 需要返回计算结果
练习 2:设计 Handler
为一个电商系统设计以下 Handler:
- 创建订单
- 查询订单详情
- 取消订单(发布通知)
查看参考实现
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);
}
}🚀 下一步
掌握了核心概念后,继续学习:
- 管道行为 - MediatR 最强大的特性
- 通知机制 - 实现发布/订阅模式
- ASP.NET Core 集成 - 在 Web 应用中应用
💡 提示:理解这些核心概念是掌握 MediatR 的基础。建议多次阅读并结合代码示例实践,直到能够熟练运用。