A lightweight, fast, and simple implementation of the Mediator pattern for .NET 8, inspired by MediatR. This library provides in-process messaging with support for request/response, command handling, notification publishing, and pipeline behaviors.
- 🚀 Request/Response - Send requests and receive responses
- 📢 Notifications - Publish notifications to multiple handlers
- 🔧 Pipeline Behaviors - Add cross-cutting concerns like logging, validation, caching
- 📦 Dependency Injection - Built-in support for Microsoft.Extensions.DependencyInjection
- ⚡ Lightweight - Minimal dependencies and overhead
- 🎯 .NET 8 - Built for the latest .NET version
You can install the package via NuGet Package Manager or by adding it directly to your project file:
<PackageReference Include="MESK.MediatR" Version="1.0.8" />using MESK.MediatR;
var builder = WebApplication.CreateBuilder(args);
// Register MediatR services
builder.Services.AddMediatR(options =>
{
options.RegisterServicesFromAssembly(typeof(Program).Assembly);
// Register pipeline behaviors if needed
// options.RegisterPipelineBehavior(typeof(LoggingBehavior<,>));
});
var app = builder.Build();using MESK.MediatR;
// Define a request
public record GetUserQuery(int Id) : IRequest<User>;
// Define the response model
public record User(int Id, string Name, string Email);
// Implement the handler
public class GetUserHandler : IRequestHandler<GetUserQuery, User>
{
public async Task<User> Handle(GetUserQuery request, CancellationToken cancellationToken)
{
// Your business logic here
return new User(request.Id, "John Doe", "john@example.com");
}
}public class UserController : ControllerBase
{
private readonly ISender _sender;
public UserController(ISender sender)
{
_sender = sender;
}
[HttpGet("{id}")]
public async Task<User> GetUser(int id)
{
var query = new GetUserQuery(id);
return await _sender.Send(query);
}
}// Define a command
public record CreateUserCommand(string Name, string Email) : IRequest;
// Implement the handler
public class CreateUserHandler : IRequestHandler<CreateUserCommand>
{
public async Task Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
// Create user logic
Console.WriteLine($"Creating user: {request.Name}");
}
}
// Usage
await _sender.Send(new CreateUserCommand("Jane Doe", "jane@example.com"));// Define a notification
public record UserCreatedNotification(int UserId, string Name) : INotification;
// Multiple handlers can handle the same notification
public class EmailNotificationHandler : INotificationHandler<UserCreatedNotification>
{
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine($"Sending welcome email to user {notification.Name}");
}
}
public class LoggingNotificationHandler : INotificationHandler<UserCreatedNotification>
{
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine($"User created: {notification.UserId}");
}
}
// Usage - all handlers will be executed
await _sender.Publish(new UserCreatedNotification(1, "John Doe"));Pipeline behaviors allow you to add cross-cutting concerns like logging, validation, caching, etc.
// Create a logging behavior
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<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(TRequest).Name}");
return response;
}
}
// Register the behavior
builder.Services.AddMediatR(options =>
{
options.RegisterServicesFromAssembly(typeof(Program).Assembly);
options.RegisterPipelineBehavior(typeof(LoggingBehavior<,>));
});public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}Base interfaces for requests.
Interfaces for handling requests.
Base interface for notifications.
Interface for handling notifications.
Main interface for sending requests and publishing notifications.
Interfaces for implementing pipeline behaviors.
Configuration class with methods:
RegisterServicesFromAssembly(Assembly assembly)RegisterServicesFromAssemblies(params Assembly[] assemblies)RegisterPipelineBehavior(Type behaviorType)
- Use Records for Requests: Records provide immutability and value equality.
- Keep Handlers Focused: Each handler should have a single responsibility.
- Use Pipeline Behaviors for Cross-Cutting Concerns: Logging, validation, caching, etc.
- Async All the Way: Always use async/await for non-blocking operations.
- Use CancellationTokens: Always respect cancellation tokens for better performance.
- Handlers are resolved from DI container on each request
- Pipeline behaviors are executed in reverse order of registration
- Notifications are published to all handlers in parallel using
Task.WhenAll
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.