Overview
The Intent.Application.MediatR.Behaviours module generates MediatR pipeline behaviours that add cross-cutting concerns to your request handling. These behaviours wrap your command and query handlers with logging, authorization, validation, transactions, and more.
Behaviours are executed in a pipeline before and after your handlers run, making them ideal for cross-cutting concerns that apply to multiple operations.
Generated Behaviours
This module generates the following pipeline behaviours:
UnhandledExceptionBehaviour
Catches and logs any unhandled exceptions from your handlers.
UnhandledExceptionBehaviour.cs
public class UnhandledExceptionBehaviour < TRequest , TResponse >
: IPipelineBehavior < TRequest , TResponse >
where TRequest : notnull
{
private readonly ILogger < UnhandledExceptionBehaviour < TRequest , TResponse >> _logger ;
private readonly bool _logRequestPayload ;
public UnhandledExceptionBehaviour (
ILogger < UnhandledExceptionBehaviour < TRequest , TResponse >> logger ,
IConfiguration configuration )
{
_logger = logger ;
_logRequestPayload = configuration . GetValue < bool ?>( "CqrsSettings:LogRequestPayload" ) ?? false ;
}
public async Task < TResponse > Handle (
TRequest request ,
RequestHandlerDelegate < TResponse > next ,
CancellationToken cancellationToken )
{
try
{
return await next ();
}
catch ( Exception ex )
{
var requestName = typeof ( TRequest ). Name ;
if ( _logRequestPayload )
{
_logger . LogError ( ex , "MyApp Request: Unhandled Exception for Request {Name} {@Request}" ,
requestName , request );
}
else
{
_logger . LogError ( ex , "MyApp Request: Unhandled Exception for Request {Name}" , requestName );
}
throw ;
}
}
}
Features:
Logs exception details with request name
Optionally includes full request payload in logs
Configured via CqrsSettings:LogRequestPayload setting
LoggingBehaviour
Logs request information before handler execution.
public class LoggingBehaviour < TRequest > : IRequestPreProcessor < TRequest >
where TRequest : notnull
{
private readonly ILogger < LoggingBehaviour < TRequest >> _logger ;
private readonly ICurrentUserService _currentUserService ;
private readonly bool _logRequestPayload ;
public async Task Process ( TRequest request , CancellationToken cancellationToken )
{
var requestName = typeof ( TRequest ). Name ;
var user = await _currentUserService . GetAsync ();
if ( _logRequestPayload )
{
_logger . LogInformation (
"MyApp Request: {Name} {@UserId} {@UserName} {@Request}" ,
requestName , user ? . Id , user ? . Name , request );
}
else
{
_logger . LogInformation (
"MyApp Request: {Name} {@UserId} {@UserName}" ,
requestName , user ? . Id , user ? . Name );
}
}
}
Features:
Logs request name, user ID, and username
Optionally includes full request payload
Runs before handler execution
AuthorizationBehaviour
Enforces authorization rules based on attributes.
public class AuthorizationBehaviour < TRequest , TResponse > : IPipelineBehavior < TRequest , TResponse >
where TRequest : notnull
{
private readonly ICurrentUserService _currentUserService ;
public async Task < TResponse > Handle (
TRequest request ,
RequestHandlerDelegate < TResponse > next ,
CancellationToken cancellationToken )
{
var authorizeAttributes = request . GetType ()
. GetCustomAttributes < AuthorizeAttribute >();
if ( authorizeAttributes . Any ())
{
var user = await _currentUserService . GetAsync ();
if ( user == null )
{
throw new UnauthorizedAccessException ();
}
// Check roles and policies...
}
return await next ();
}
}
Features:
Enforces [Authorize] attribute semantics
Checks roles and policies
Integrates with ICurrentUserService
UnitOfWorkBehaviour
Ensures database changes are committed after successful handler execution.
/// < summary >
/// Ensures that all operations processed as part of handling a < see cref = "ICommand" /> either
/// pass or fail as one unit. This behaviour makes it unnecessary for developers to call
/// SaveChangesAsync() inside their business logic (e.g. command handlers), and doing so should
/// be avoided unless absolutely necessary.
/// </ summary >
public class UnitOfWorkBehaviour < TRequest , TResponse > : IPipelineBehavior < TRequest , TResponse >
where TRequest : notnull , ICommand
{
private readonly IUnitOfWork _unitOfWork ;
public UnitOfWorkBehaviour ( IUnitOfWork unitOfWork )
{
_unitOfWork = unitOfWork ;
}
public async Task < TResponse > Handle (
TRequest request ,
RequestHandlerDelegate < TResponse > next ,
CancellationToken cancellationToken )
{
var response = await next ();
await _unitOfWork . SaveChangesAsync ( cancellationToken );
return response ;
}
}
Features:
Only applies to commands (via ICommand constraint)
Automatically commits changes after handler succeeds
Developers don’t need to call SaveChangesAsync() manually
Measures and logs request execution time.
public class PerformanceBehaviour < TRequest , TResponse > : IPipelineBehavior < TRequest , TResponse >
where TRequest : notnull
{
private readonly Stopwatch _timer ;
private readonly ILogger < PerformanceBehaviour < TRequest , TResponse >> _logger ;
public async Task < TResponse > Handle (
TRequest request ,
RequestHandlerDelegate < TResponse > next ,
CancellationToken cancellationToken )
{
_timer . Start ();
var response = await next ();
_timer . Stop ();
var elapsedMilliseconds = _timer . ElapsedMilliseconds ;
if ( elapsedMilliseconds > 500 ) // Configurable threshold
{
var requestName = typeof ( TRequest ). Name ;
_logger . LogWarning (
"Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@Request}" ,
requestName , elapsedMilliseconds , request );
}
return response ;
}
}
Features:
Tracks request execution time
Logs warnings for slow requests
Configurable performance threshold
MessageBusPublishBehaviour
Publishes integration events after successful command execution.
public class MessageBusPublishBehaviour < TRequest , TResponse > : IPipelineBehavior < TRequest , TResponse >
where TRequest : notnull
{
private readonly IEventBus _eventBus ;
public async Task < TResponse > Handle (
TRequest request ,
RequestHandlerDelegate < TResponse > next ,
CancellationToken cancellationToken )
{
var response = await next ();
await _eventBus . FlushAllAsync ( cancellationToken );
return response ;
}
}
Features:
Publishes queued integration events
Only publishes after handler succeeds
Requires Intent.Eventing.Contracts module
Behaviour Execution Order
MediatR executes behaviours in registration order (outer → inner). Typical ordering:
The order matters! For example, UnhandledExceptionBehaviour should wrap the handler closely to catch all exceptions, while UnitOfWorkBehaviour needs to commit changes before MessageBusPublishBehaviour publishes events.
Configuration
Log Request Payload
CqrsSettings.LogRequestPayload
Controls whether the full request object is logged by LoggingBehaviour and UnhandledExceptionBehaviour. Security Warning : Enabling this may log sensitive data. Use with caution in production.
Configuration in appsettings.json:
{
"CqrsSettings" : {
"LogRequestPayload" : false
}
}
Dependencies
This module requires:
Intent.Application.MediatR - Base MediatR implementation
Intent.Application.Identity - Provides ICurrentUserService for authorization
Intent.Application.DependencyInjection.MediatR - DI registration
Intent.Common.UnitOfWork - Unit of work abstraction for transactions
Optional dependencies:
Intent.Eventing.Contracts - For MessageBusPublishBehaviour
Intent.CosmosDB - Cosmos DB transaction support
Intent.MongoDb - MongoDB transaction support
When to Use
Use This Module When
You need consistent cross-cutting concerns
Medium to large applications
Multiple commands/queries benefit from same behaviors
Want to avoid scattering logging/auth/validation logic
Skip This Module When
Extremely small or script-like projects
Pipeline overhead is unnecessary
Custom behavior ordering is required
Using a different middleware pattern
Customization
Behaviours are generated with merge mode, allowing customization:
[ IntentManaged ( Mode . Merge , Signature = Mode . Fully )]
public partial class LoggingBehaviour < TRequest > : IRequestPreProcessor < TRequest >
where TRequest : notnull
{
// Add custom fields or methods
private readonly IMetricsService _metrics ;
partial void OnLogging ( TRequest request )
{
// Custom logging logic
_metrics . IncrementCounter ( "requests" , typeof ( TRequest ). Name );
}
}
Common customizations:
Add correlation IDs to logging
Integrate OpenTelemetry tracing
Custom authorization policies
Filter or batch event publication
Adjust performance thresholds
MediatR Core CQRS implementation with MediatR
MediatR FluentValidation Validation behaviour using FluentValidation
Application Identity Provides ICurrentUserService for authorization
Additional Resources