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

Commands Send requests to a single handler with optional return values
Notifications Broadcast messages to multiple handlers simultaneously
Async Support Both synchronous and asynchronous handlers supported
DI Integration Auto-registration of handlers via assembly scanning
Minimal Dependencies Lightweight with no unnecessary overhead
Any .NET Project Works with WebAPI, Blazor, Console apps, and more

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?

An unhandled error has occurred. Reload 🗙