Goodtocode.Mediator
1.1.20
dotnet add package Goodtocode.Mediator --version 1.1.20
NuGet\Install-Package Goodtocode.Mediator -Version 1.1.20
<PackageReference Include="Goodtocode.Mediator" Version="1.1.20" />
<PackageVersion Include="Goodtocode.Mediator" Version="1.1.20" />
<PackageReference Include="Goodtocode.Mediator" />
paket add Goodtocode.Mediator --version 1.1.20
#r "nuget: Goodtocode.Mediator, 1.1.20"
#:package Goodtocode.Mediator@1.1.20
#addin nuget:?package=Goodtocode.Mediator&version=1.1.20
#tool nuget:?package=Goodtocode.Mediator&version=1.1.20
Goodtocode.Mediator
Goodtocode.Mediator is a lightweight implementation of the mediator pattern for .NET, designed to enable CQRS (Command Query Responsibility Segregation) in clean architecture solutions. It provides a simple way to decouple request handling, validation, and business logic without external dependencies.
Core Abstractions
- IRequest<TResponse> / IRequest: Marker interfaces for queries and commands, with or without a return type.
- IRequestHandler<TRequest, TResponse> / IRequestHandler<TRequest>: Handlers that process requests and return a result (or not).
- ISender: The main interface for sending requests; exposes
Sendmethods for both command and query patterns. - IRequestDispatcher: Internal dispatcher that resolves handlers and pipeline behaviors from DI, and invokes them.
- IPipelineBehavior<TRequest, TResponse> / IPipelineBehavior<TRequest>: Optional middleware for cross-cutting concerns (validation, logging, etc.), chained before the handler.
Pipeline Behaviors
Pipeline behaviors allow you to add cross-cutting logic around request handling, such as validation, logging, or transaction management. Behaviors are resolved from DI and executed in order before the handler.
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(TRequest request, RequestDelegateInvoker<TResponse> next, CancellationToken cancellationToken)
{
Console.WriteLine($"Handling {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"Handled {typeof(TRequest).Name}");
return response;
}
}
How It Works
- Define a request (command or query) implementing
IRequest<TResponse>orIRequest. - Implement a handler for the request, containing the logic in
.Handle. - Optionally, implement pipeline behaviors for validation, logging, etc.
- Register handlers and behaviors in DI.
- Use
ISender.Send(request)to dispatch from controllers or services.
Example: Aggregator Controller Using Goodtocode.Mediator
[ApiController]
[Route("api/v{version:apiVersion}/enrollments")]
public class EnrollmentController : ApiControllerBase
{
// Query: Get details
[HttpGet("{enrollmentId}")]
public async Task<EnrollmentDto> GetEnrollment(Guid enrollmentId)
{
return await Mediator.Send(new GetMyEnrollmentQuery { Id = enrollmentId });
}
// Command: Create
[HttpPost]
public async Task<ActionResult> Post(EnrollCommand command)
{
var response = await Mediator.Send(command);
return CreatedAtAction(nameof(GetMyEnrollment), new { enrollmentId = response.Id }, response);
}
// Command: Patch
[HttpPatch("{enrollmentId}")]
public async Task<ActionResult> Patch(Guid enrollmentId, UnenrollCommand command)
{
command.Id = enrollmentId;
await Mediator.Send(command);
return NoContent();
}
// Command: Delete
[HttpDelete]
public async Task<IActionResult> Delete(Guid enrollmentId)
{
await Mediator.Send(new DeleteEnrollmentCommand { Id = enrollmentId });
return NoContent();
}
}
Benefits
- Decouples controllers from business logic and validation
- Supports CQRS by separating commands and queries
- Enables clean, testable, and maintainable code
Features
- Simple integration with Blazor, ASP.NET Core, and .NET DI
Installation
Install via NuGet:
dotnet add package Goodtocode.Mediator
Dependency Injection Setup
To quickly register Goodtocode.Mediator handlers and core services, use the provided DI extension method:
services.AddMediatorServices();
This will register all request handlers and core mediator abstractions.
Note: You must still register your application's pipeline behaviors separately, as these are specific to your app and not included in the library (per SRP). These pipeline behaiors can be copied from the following sample implementations below: Agent Framework Quick-start w/ Pipeline Behavior classes
services.AddTransient(typeof(IPipelineBehavior<>), typeof(CustomUnhandledExceptionBehavior<>));
services.AddTransient(typeof(IPipelineBehavior<>), typeof(CustomValidationBehavior<>));
services.AddTransient(typeof(IPipelineBehavior<>), typeof(CustomPerformanceBehavior<>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CustomUnhandledExceptionBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CustomValidationBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CustomPerformanceBehavior<,>));
Example Pipeline Behaviors
Below are example implementations of common pipeline behaviors you can use in your application. These are not included in the Goodtocode.Mediator library, but you can copy and adapt them as needed.
// Logging
public class CustomLoggingBehavior<TRequest>(ILogger<TRequest> logger) : IRequestPreProcessor<TRequest> where TRequest : notnull
{
public async Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
await Task.Run(() => logger.LogRequest(requestName), cancellationToken);
}
}
// Performance
public class CustomPerformanceBehavior<TRequest, TResponse>(ILogger<TRequest> logger) : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
private readonly Stopwatch _timer = new();
private readonly ILogger<TRequest> _logger = logger;
public async Task<TResponse> Handle(TRequest request, RequestDelegateInvoker<TResponse> nextInvoker, CancellationToken cancellationToken)
{
_timer.Start();
var response = await nextInvoker();
_timer.Stop();
var elapsedMilliseconds = _timer.ElapsedMilliseconds;
if (elapsedMilliseconds > 500)
{
var requestName = typeof(TRequest).Name;
await Task.Run(() => _logger.LogLongRunningRequest(requestName, elapsedMilliseconds), cancellationToken);
}
return response;
}
}
// Exception Handling
public class CustomUnhandledExceptionBehavior<TRequest, TResponse>(ILogger<TRequest> logger) : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
private readonly ILogger<TRequest> _logger = logger;
public async Task<TResponse> Handle(TRequest request, RequestDelegateInvoker<TResponse> nextInvoker, CancellationToken cancellationToken)
{
try
{
return await nextInvoker();
}
catch (Exception ex)
{
var requestName = typeof(TRequest).Name;
await Task.Run(() => _logger.LogUnhandledException(ex, requestName), cancellationToken);
throw;
}
}
}
// Validation
public class CustomValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators) : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
private readonly IEnumerable<IValidator<TRequest>> _validators = validators;
public async Task<TResponse> Handle(TRequest request, RequestDelegateInvoker<TResponse> nextInvoker, CancellationToken cancellationToken)
{
foreach (var validator in _validators)
{
validator.ValidateAndThrow(request);
}
return await nextInvoker();
}
}
See Agent Framework Quick Start for full, working examples of these behaviors.
License
MIT
Contact
Version History
| Version | Date | Changes | .NET Version |
|---|---|---|---|
| 1.0.0 | 2025-01-01 | Initial release | 9 |
| 1.1.0 | 2026-01-22 | Version bump | 10 |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
-
net10.0
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.