Introduction
The MediatR library in .NET Core is a powerful tool for implementing the mediator pattern, which helps in decoupling the sending and handling of requests. While the basic usage of MediatR is straightforward, its real power comes into play in more advanced scenarios. This article explores advanced techniques for using MediatR in .NET Core applications, focusing on command/query separation, pipeline behaviors, and notifications.
Why Use MediatR?
MediatR helps to maintain a clean architecture by,
- Decoupling request handling from the core application logic.
- Promoting the single responsibility principle.
- Making it easier to extend and maintain the application.
Setting Up MediatR in .NET Core
Let's start by setting up a .NET Core application with MediatR.
Step 1. Create a .NET Core Project
Create a new .NET Core web API project.
dotnet new webapi -n AdvancedMediatRDemo
cd AdvancedMediatRDemo
Step 2. Install MediatR and MediatR.Extensions.Microsoft.DependencyInjection
dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
Step 3. Configure MediatR in `Startup.cs`
Add MediatR services in the `ConfigureServices` method.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMediatR(typeof(Startup));
}
Advanced MediatR Usage
1. Command/Query Separation
Implementing the Command Query Responsibility Segregation (CQRS) pattern helps in separating read and write operations.
Commands
Create a command to handle write operations.
public class CreateOrderCommand : IRequest<Order>
{
public string ProductName { get; set; }
public int Quantity { get; set; }
}
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Order>
{
public Task<Order> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
var order = new Order { ProductName = request.ProductName, Quantity = request.Quantity };
// Save order to the database
return Task.FromResult(order);
}
}
Queries
Create a query to handle read operations.
public class GetOrderByIdQuery : IRequest<Order>
{
public int Id { get; set; }
}
public class GetOrderByIdHandler : IRequestHandler<GetOrderByIdQuery, Order>
{
public Task<Order> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken)
{
var order = // Fetch order from the database by request.Id
return Task.FromResult(order);
}
}
2. Pipeline Behaviors
Pipeline behaviors allow you to add cross-cutting concerns like logging, validation, and caching.
Create a logging behavior
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
_logger.LogInformation($"Handling {typeof(TRequest).Name}");
var response = await next();
_logger.LogInformation($"Handled {typeof(TResponse).Name}");
return response;
}
}
Register the behavior in `Startup.cs`
services.AddTransient(
typeof(IPipelineBehavior<,>),
typeof(LoggingBehavior<,>)
);
3. Notifications
Notifications in MediatR allow multiple handlers to respond to the same event.
Create a notification
public class OrderCreatedNotification : INotification
{
public Order Order { get; set; }
}
public class SendEmailHandler : INotificationHandler<OrderCreatedNotification>
{
public Task Handle(OrderCreatedNotification notification, CancellationToken cancellationToken)
{
// Send email
return Task.CompletedTask;
}
}
public class UpdateInventoryHandler : INotificationHandler<OrderCreatedNotification>
{
public Task Handle(OrderCreatedNotification notification, CancellationToken cancellationToken)
{
// Update inventory
return Task.CompletedTask;
}
}
Publish the notification in the `CreateOrderHandler`
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Order>
{
private readonly IMediator _mediator;
public CreateOrderHandler(IMediator mediator)
{
_mediator = mediator;
}
public async Task<Order> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
var order = new Order { ProductName = request.ProductName, Quantity = request.Quantity };
// Save order to the database
await _mediator.Publish(new OrderCreatedNotification { Order = order });
return order;
}
}
Conclusion
The MediatR library in .NET Core offers a powerful and flexible way to implement the mediator pattern, especially when dealing with complex scenarios. By separating commands and queries, using pipeline behaviors for cross-cutting concerns, and utilizing notifications for event-driven architectures, you can build robust and maintainable applications.