Medi8.Net is a simple yet fast mediator implementation in .NET 7. It makes it easy to design your application according to CQRS principles and slice your solution into vertical and feature-based parts.
Medi8.Net is Apache 2.0 licensed.
Using .NET CLI
dotnet add package Medi8.Net --version 1.0.0
Using the package manager console
NuGet\Install-Package Medi8.Net -Version 1.0.0
serviceCollection.AddMediator(cfg =>
{
cfg.AddHandler<MyQuery, MyQueryHandler>();
cfg.AddHandler<MyCommand, MyCommandHandler>();
});
After injecting IMediator
into your business logic, you are ready to go:
var result = await this.mediator.HandleQueryAsync<MyQuery, MyResult?>(new MyQuery(...), CancellationToken.None);
The result object will contain the result of the query/command and also information about the operation itself. Any exceptions during the handling of the query will be directly propagated back to the caller.
public record MyQuery(string Name) : IQuery<MyResult?>
{
public class MyQueryHandler : IQueryHandler<MyQuery, MyResult?>
{
public Task<MyResult?> HandleAsync(IProcessingContext<MyQuery> context)
{
// perform the actual handling of the query and return the result
}
}
}
public record MyCommandWithResult : ICommand<MyResult?>
{
public class MyCommandWithResultHandler : ICommandHandler<MyCommand, MyResult?>
{
public Task<MyResult?> HandleAsync(IProcessingContext<MyCommand> context)
{
// perform the actual handling of the query and return the result
}
}
}
public record MyCommand : ICommand
{
public class MyCommandHandler : ICommandHandler<MyCommand>
{
public Task HandleAsync(IProcessingContext<MyCommand> context)
{
// perform the actual handling of the query and return the result
}
}
}
Validation in Medi8.Net is possible by implementing an interface IValidateRequest<TRequest>
in a class.
This interface will automatically be picked up by the middleware pipeline:
builder.Services.AddMediator(cfg =>
{
cfg.AddHandler<FindProductByIdQuery, FindProductByIdQuery.FindProductByIdQueryHandler>();
cfg.AddHandler<AddProductCommand, AddProductCommand.AddProductCommandHandler>();
cfg.AddValidator<AddProductCommand, AddProductCommand.AddProductValidator>();
cfg.AddValidator<FindProductByIdQuery, FindProductByIdQuery.FindProductByIdQueryValidator>();
});
and an implementation
public class FindProductByIdQueryValidator : IValidateRequest<FindProductByIdQuery>
{
public Task<Errors> ValidateAsync(IProcessingContext<FindProductByIdQuery> context)
{
// do implementation here
// ...
return Task.FromResult(Errors.Empty);
}
}
Medi8.Net supports full pipeline processing for pre- and post-processing of a request.
You can implement custom filters using IPreProcessor
and IPostProcessor
and add them to your
code as follows:
serviceCollection.AddMediator(
cfg =>
{
cfg.AddHandler<MyQuery, MyQueryHandler>();
cfg.AddPreExecutionMiddleware<MyPreFilter>();
cfg.AddPostExecutionMiddleware<MyPreFilter>();
});
The filters will be executed in order of their registration.
If filters rely on results from previous filters or if you need to transport custom payloads through your pipeline,
you can access respective methods on the ProcessingContext
:
public class PreFilter : IPreProcessor
{
public async Task InvokeAsync<TRequest>(ProcessingContext<TRequest> context, Next<TRequest> next)
{
context.TryAddPayload("MyKey", 42);
await next(context);
}
}
Intercepting the pipeline through filters is recommended to be done using status codes and adding errors to the context:
public class PreFilter : IPreProcessor
{
public async Task InvokeAsync<TRequest>(ProcessingContext<TRequest> context, Next<TRequest> next)
{
if (somethingWentWrong)
{
context.WriteTo(new Errors("MyFilter", "Failed because of reason..."));
context.WriteTo(StatusCode.PipelineFailed);
}
else
{
await next(context);
}
}
}
If your filters need dependencies, this will not be possible through ctor injection. Instead, you can resolve your dependencies from the scope provided by the context:
public async Task InvokeAsync<TRequest>(ProcessingContext<TRequest> context, Next<TRequest> next)
{
var statusCode = context.GetRequiredService<StatusCodeProvider>().GetAndIncrement();
context.WriteTo(statusCode);
await next(context);
}
Please refer to the samples section for a simple but full example of a small web application.
Feel free to raise issues and discussions in this repository for bugs and feature requests.