An implementation of the mediator pattern in .NET using source-generation.
Install
| Product | Versions |
|---|---|
| .NET Standard |
README
Immediate.Handlers
Immediate.Handlers is an implementation of the mediator pattern in .NET using source-generation. All pipeline behaviors are determined and the call-tree built at compile-time; meaning that all dependencies are enforced via compile-time safety checks. Behaviors and dependencies are obtained via DI at runtime based on compile-time determined dependencies.
Examples
- Minimal Api: Normal
Installing Immediate.Handlers
You can install Immediate.Handlers with NuGet:
Install-Package Immediate.Handlers
Or via the .NET Core command line interface:
dotnet add package Immediate.Handlers
Either commands, from Package Manager Console or .NET Core CLI, will download and install Immediate.Handlers.
Using Immediate.Handlers
Creating Handlers
Create a Handler by adding the following code:
[Handler]
public static class GetUsersQuery
{
public record Query;
private static ValueTask<IEnumerable<User>> HandleAsync(
Query _,
UsersService usersService,
CancellationToken token)
{
return usersService.GetUsers();
}
}
This will automatically create a new class, GetUsersQuery.Handler, which encapsulates the following:
- attaching any behaviors defined for all queries in the assembly
- using a class to receive any DI services, such as
UsersService
Any consumer can now do the following:
public class Consumer(GetUsersQuery.Handler handler)
{
public async Task Consumer(CancellationToken token)
{
var response = await handler.HandleAsync(new(), token);
// do something with response
}
}
In case your project layout does not allow direct for references between consumer and handler, the handler will also be
registered as an IHandler<TRequest, Response>.
public class Consumer(IHandler<Query, IEnumerable<User>> handler)
{
public async Task Consumer(CancellationToken token)
{
var response = await handler.HandleAsync(new(), token);
// do something with response
}
}
Creating Behaviors
Create a behavior by implementing the Immediate.Handlers.Shared.Behaviors<,> class, as so:
public sealed class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
: Behavior<TRequest, TResponse>
{
public override async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken)
{
logger.LogInformation("LoggingBehavior.Enter");
var response = await Next(request, cancellationToken);
logger.LogInformation("LoggingBehavior.Exit");
return response;
}
}
This can be registered assembly-wide using:
[assembly: Behaviors(
typeof(LoggingBehavior<,>)
)]
or on an individual handler using:
[Handler]
[Behavior(
typeof(LoggingBehavior<,>)
)]
public static class GetUsersQuery
{
// ..
}
Once added to the pipeline, the behavior will be called as part of the pipeline to handle a request.
Note: adding a [Behavior] attribute to a handler will disregard all assembly-wide behaviors for that handler, so any
global behaviors necessary must be independently added to the handler override behaviors list.
Behavior Constraints
A constraint can be added to a behavior by using:
public sealed class LoggingBehavior<TRequest, TResponse>
: Behavior<TRequest, TResponse>
where TRequest : IRequestConstraint
where TResponse : IResponseConstraint
When a pipeline is generated, all potential behaviors are evaluated against the request and response types, and if either type does not match a given constraint, the behavior is not added to the generated pipeline.
Registering with IServiceCollection
Immediate.Handlers supports Microsoft.Extensions.DependencyInjection.Abstractions directly.
Registering Handlers
services.AddHandlers();
This registers all classes in the assembly marked with [Handler].
Registering Behaviors
services.AddBehaviors();
This registers all behaviors referenced in any [Behaviors] attribute.
Performance Comparisons
All performance benchmarks reported use the following environment:
// * Summary *
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22621.3007/22H2/2022Update/SunValley2)
12th Gen Intel Core i7-12700H, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.101
[Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2
Benchmarks.Simple
This benchmark tests the various mediator implementations with a single request/response handler.
| Method | Mean | Error | Ratio | Rank | Allocated |
|---|---|---|---|---|---|
| SendRequest_Baseline | 0.7701 ns | 0.0180 ns | 1.00 | 1 | - |
| SendRequest_IHandler | 15.6780 ns | 0.0476 ns | 20.36 | 2 | - |
| SendRequest_ImmediateHandler | 16.6023 ns | 0.0561 ns | 21.56 | 3 | - |
| SendRequest_Mediator | 27.2993 ns | 0.4269 ns | 35.49 | 4 | - |
| SendRequest_IMediator | 31.3420 ns | 0.1006 ns | 40.64 | 5 | - |
| SendRequest_MediatR | 68.3384 ns | 0.3453 ns | 88.73 | 6 | 240 B |
Benchmarks.Large
This benchmark tests the various mediator implementations in the face of 999 request/response handlers.
| Method | Mean | Error | Ratio | Rank | Allocated |
|---|---|---|---|---|---|
| SendRequest_Baseline | 0.5656 ns | 0.0252 ns | 1.00 | 1 | - |
| SendRequest_ImmediateHandler | 15.4346 ns | 0.0516 ns | 27.34 | 2 | - |
| SendRequest_IHandler | 16.0959 ns | 0.0552 ns | 28.50 | 3 | - |
| SendRequest_Mediator | 27.4104 ns | 0.0449 ns | 48.54 | 4 | - |
| SendRequest_MediatR | 80.0953 ns | 0.4749 ns | 141.83 | 5 | 240 B |
| SendRequest_IMediator | 435.3890 ns | 1.6399 ns | 771.01 | 6 | - |
Benchmarks.Behaviors
This benchmark tests a more realistic scenario of using 1 behavior and 1 service.
| Method | Mean | Error | Ratio | Rank | Allocated |
|---|---|---|---|---|---|
| SendRequest_Baseline | 56.71 ns | 0.174 ns | 1.00 | 1 | 40 B |
| SendRequest_IHandler | 78.90 ns | 0.304 ns | 1.39 | 2 | 40 B |
| SendRequest_ImmediateHandler | 80.02 ns | 0.288 ns | 1.41 | 3 | 40 B |
| SendRequest_Mediator | 101.23 ns | 0.263 ns | 1.78 | 4 | 40 B |
| SendRequest_IMediator | 104.92 ns | 0.297 ns | 1.85 | 5 | 40 B |
| SendRequest_MediatR | 201.27 ns | 1.023 ns | 3.55 | 6 | 560 B |
Changes from Previous Version
Adoption Assessment
Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.
Related Guides & Workflows
Learn how to evaluate, integrate, or audit this package in your .NET project.