Lightweight library for implementing CQRS and event dispatching in .NET.
Install the package from NuGet.
dotnet add package DSPTCHCreate a request, query or command by implementing the IRequest, IQuery or ICommand interface.
public record GetProductByIdQuery(Guid Id)
: IQuery<Product>;
public record GetProductByNameQuery(string Name)
: IRequest<List<Product>>;
public record CreateProductCommand(string Name, decimal Price)
: ICommand<Guid>;IQuery and ICommand inherit from IRequest.
Create a handler for each request, query or command by implementing the IRequestHandler interface.
public class GetProductByIdQueryHandler(IProductRepository productRepository)
: IRequestHandler<GetProductByIdQuery, Product?>
{
public Task<Product?> Handle(GetProductByIdQuery query, CancellationToken cancellationToken)
{
return productsRepository.GetById(query.Id);
}
}Dispatch the request, query or command using the IDispather interface.
app.MapGet("/products/{id}", async (IDispatcher dispatcher, Guid id) =>
{
Product? product = await dispatcher.Dispatch<GetProductByIdQuery, Product?>(new GetProductByIdQuery(id));
return product is not null ? Results.Ok(product) : Results.NotFound();
});Use dispatch decorators to add cross-cutting concerns to the dispatching process.
// This decorator will be called before and after every request is dispatched
public class LoggingDecorator<TRequest, TResult>(ILogger<TRequest> logger)
: IDispatcherDecorator<TRequest, TResult>
where TRequest : IRequest<TResult>
{
public async Task<TResult> Dispatch(
TRequest command,
RequestHandlerDelegate<TResult> handler,
CancellationToken cancellationToken = default)
{
var requestName = typeof(TRequest).Name;
logger.LogInformation("Dispatching request: {req}", requestName);
try
{
var result = await handler();
logger.LogInformation("Request dispatched: {req}", requestName);
return result;
}
catch (Exception ex)
{
logger.LogError(ex, "Error dispatching request: {req}", requestName);
throw;
}
}
}
// This decorator will be called before and after every query is dispatched
// It won't be called for IRequestHandler or ICommandHandler implementations
public class CachingDecorator<TQuery, TResult>(
IMemoryCache cache,
ILogger<CachingDecorator<TQuery, TResult>> logger)
: IDispatcherDecorator<TQuery, TResult>
where TQuery : IQuery<TResult>
{
}
// This decorator will only be called before and after every IAuthorizableRequest is dispatched
public class AuthorizationDecorator<TQuery, TResult>
: IDispatcherDecorator<TQuery, TResult>
where TQuery : IAuthorizableRequest<TResult>
{
}Use Microsoft DI to register the dispatcher and handlers.
builder.Services.AddDsptch(opts =>
{
opts.RegisterServicesFromAssemblyContaining<Program>();
// Register dispatchers using DsptchConfiguration
opts.RegisterDispatcherDecorator(typeof(LoggingDecorator<,>));
opts.RegisterDispatcherDecorator(typeof(CachingDecorator<,>));
});
// Or register dispatchers manually
builder.Services.TryAddTransient(typeof(IDispatcherDecorator<,>), typeof(LoggingDecorator<,>));
builder.Services.TryAddTransient(typeof(IDispatcherDecorator<,>), typeof(CachingDecorator<,>));- The
AddDsptchmethod will register the dispatcher and handlers in the specified assemblies.- They will be registered as transient services by default, but this can be changed via
opts.Lifetime.
- They will be registered as transient services by default, but this can be changed via
- The decorators can be registered using the
RegisterDispatcherDecoratormethod.
This project is licensed under the MIT License - see the LICENSE file for details.