D20Tek Mediator
A lightweight, minimal implementation of the Mediator and Command patterns for .NET. Perfect for building clean, decoupled architectures in your WebAPI, Blazor, or console applications without heavy dependencies.
What is the Mediator Pattern?
The Mediator pattern promotes loose coupling by preventing objects from referring to each other directly. Instead, they communicate through a mediator object that routes requests to the appropriate handlers.
D20Tek.Mediator provides a simple, focused implementation inspired by MediatR. With MediatR moving to a commercial license, this library offers a free, open-source alternative for common use cases.
Key Features
Core Concepts
Commands
Commands represent a request that should be handled by exactly one handler. They can optionally
return a result. Use the ICommand<TResult> interface to define your commands
and ICommandHandlerAsync<TCommand, TResult> to handle them.
Notifications
Notifications are broadcast messages that can be handled by multiple handlers. They're perfect
for cross-cutting concerns like logging, caching, or event-driven architectures. Implement
INotification and INotificationHandlerAsync<TNotification>.
The Mediator
The IMediator interface is your single point of contact. Inject it wherever you
need to send commands or publish notifications. The mediator handles routing to the correct
handler(s) automatically.
Installation
Install the NuGet package using the Package Manager Console or .NET CLI:
dotnet add package D20Tek.Mediator
Quick Start
1. Register Services
Add the mediator to your dependency injection container with automatic handler discovery:
// Program.cs builder.Services.AddMediator(typeof(Program).Assembly);
This registers the IMediator service and scans the specified assembly for all
command and notification handlers.
2. Define a Command and Handler
Create a command (the request) and its handler (the logic):
// Define the command with its return type
public sealed record GetWeatherCommand : ICommand<WeatherForecast[]>;
// Define the response
public sealed record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
// Implement the handler
public sealed class GetWeatherHandler : ICommandHandlerAsync<GetWeatherCommand, WeatherForecast[]>
{
public Task<WeatherForecast[]> HandleAsync(
GetWeatherCommand command,
CancellationToken cancellationToken)
{
var forecasts = Enumerable.Range(1, 5)
.Select(i => new WeatherForecast(
DateOnly.FromDateTime(DateTime.Now.AddDays(i)),
Random.Shared.Next(-20, 55),
"Sunny"))
.ToArray();
return Task.FromResult(forecasts);
}
}3. Send Commands via the Mediator
Inject IMediator and send your command:
// In a Minimal API endpoint
app.MapGet("api/weather", async (IMediator mediator, CancellationToken ct) =>
await mediator.SendAsync(new GetWeatherCommand(), ct));
// Or in a Controller
[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
private readonly IMediator _mediator;
public WeatherController(IMediator mediator) => _mediator = mediator;
[HttpGet]
public async Task<WeatherForecast[]> Get(CancellationToken ct) =>
await _mediator.SendAsync(new GetWeatherCommand(), ct);
}Working with Notifications
Broadcast events to multiple handlers:
// Define a notification
public sealed record UserCreatedNotification(string UserId, string Email) : INotification;
// Multiple handlers can respond to the same notification
public class SendWelcomeEmailHandler : INotificationHandlerAsync<UserCreatedNotification>
{
public Task HandleAsync(UserCreatedNotification notification, CancellationToken ct)
{
// Send welcome email logic
return Task.CompletedTask;
}
}
public class LogUserCreatedHandler : INotificationHandlerAsync<UserCreatedNotification>
{
public Task HandleAsync(UserCreatedNotification notification, CancellationToken ct)
{
// Log the event
return Task.CompletedTask;
}
}
// Publish the notification - all handlers will be invoked
await mediator.PublishAsync(new UserCreatedNotification("123", "user@example.com"), ct);Why Use the Mediator Pattern?
- Decoupled Code - Controllers/endpoints don't need to know about business logic implementations
- Single Responsibility - Each handler focuses on one specific operation
- Testability - Handlers can be unit tested in isolation
- CQRS Ready - Natural fit for Command Query Responsibility Segregation
- Clean Architecture - Supports layered architectures with clear boundaries
Sample Projects
Explore complete working examples in the GitHub repository:
- MemberService - Minimal WebAPI with full CRUD operations using async commands and handlers
- SampleApi - Simple WebAPI demonstrating basic mediator usage
- TipCalc.Cli - Console application showing mediator usage with Generic Host
Ready to simplify your application architecture?