04 Behaviors and Pipeline - vagnerjsmello/NexMediator GitHub Wiki
NexMediator supports ordered, pluggable pipeline behaviors for cross-cutting concerns.
Each behavior runs before and/or after the request handler. Behaviors are executed in the order you define when registering AddNexMediator(...)
.
services.AddNexMediator(options =>
{
options.AddBehavior(typeof(LoggingBehavior<,>), 1);
options.AddBehavior(typeof(FluentValidationBehavior<,>), 2);
options.AddBehavior(typeof(CachingBehavior<,>), 3);
options.AddBehavior(typeof(TransactionBehavior<,>), 4);
});
Adds logging context for each request.
Optionally, it can also include X-Correlation-ID
tracking if enabled.
To enable correlation tracking, pass useCorrelationId: true
to the behavior’s constructor:
options.AddBehavior(typeof(LoggingBehavior<,>), 1);
If correlation is enabled, you also need:
services.AddScoped<ICorrelationContext, CorrelationContext>();
app.UseMiddleware<CorrelationMiddleware>();
This will read/write the X-Correlation-ID
HTTP header and push it into your logs.
NexMediator is agnostic about the logging provider. In the AuctionBoardGame sample, logging is done via Serilog, but you could also use:
- Microsoft.Extensions.Logging
- Azure Application Insights
- Elastic (ELK), Seq, or others
Runs all FluentValidation validators before executing the handler.
If validation fails, the pipeline is short-circuited and the error is returned.
- Reference to
FluentValidation
- Validators implementing
IValidator<T>
public class CreateAuctionValidator : AbstractValidator<CreateAuctionCommand>
{
public CreateAuctionValidator()
{
RuleFor(x => x.Title).NotEmpty();
RuleFor(x => x.EndDate).GreaterThan(DateTime.UtcNow);
}
}
Caches the response of any query implementing ICacheableRequest<T>
.
NexMediator checks the CacheKey
and Expiration
properties of the request.
services.AddMemoryCache();
services.AddScoped<ICache, MemoryRequestCache>();
The interface ICache
is defined in NexMediator.Abstractions.
In the AuctionBoardGame example, it is implemented as:
public class MemoryRequestCache : ICache
{
private readonly IMemoryCache _cache;
public Task<T?> GetAsync<T>(string key, CancellationToken ct) { ... }
public Task SetAsync<T>(string key, T value, int? ttl, CancellationToken ct) { ... }
public Task RemoveAsync(string key, CancellationToken ct) { ... }
public Task RemoveByPrefixAsync(string prefix, CancellationToken ct) { ... }
}
This could be swapped for:
- Redis cache
- SQL-based cache
- Distributed memory
- File-based cache
Just implement ICache
.
Wraps request execution in a transaction scope when request implements ITransactionalRequest<T>
.
services.AddScoped<ITransactionManager, EfCoreTransactionManager>();
The EfCoreTransactionManager
used in the example looks like this:
public class EfCoreTransactionManager : ITransactionManager
{
public async Task BeginTransactionAsync(CancellationToken ct) => ...;
public async Task CommitTransactionAsync(CancellationToken ct) => ...;
public async Task RollbackTransactionAsync(CancellationToken ct) => ...;
}
You could replace it with:
- Dapper-based transaction
- MongoDB transaction
- Cosmos DB or any other transactional service
As long as it implements ITransactionManager
.
You can add your own behavior by implementing:
public class CustomBehavior<TRequest, TResponse> : INexPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
// Before handler
var response = await next();
// After handler
return response;
}
}
Then register:
options.AddBehavior(typeof(CustomBehavior<,>), 99);
Pipeline behaviors help enforce cross-cutting concerns with minimal boilerplate.
Combined with NexMediator’s ordering system and DI-first design, they give you full control over the request lifecycle.
Next → Notifications