DEV Community

Spyros Ponaris
Spyros Ponaris

Posted on

Enhancing Request Pipelines with MediatR Behaviors

Introduction

MediatR is a widely used library in .NET applications that follows the mediator pattern, helping to decouple requests from handlers. While it simplifies CQRS (Command Query Responsibility Segregation), it also offers a robust feature called Pipeline Behaviors.

These behaviors enable developers to intercept requests and implement cross-cutting concerns such as logging, validation , performance tracking , transaction management , and error handling in a structured and reusable manner.

Source Code

You can find the complete source code for this tutorial at:
👉 GitHub Repository

Understanding MediatR Behaviors and the Decorator Pattern

MediatR Behaviors function as middleware, allowing developers to execute logic before and after request handlers.

The Decorator Pattern in MediatR Behaviors

MediatR Behaviors follow the Decorator Pattern, a structural design pattern that allows additional responsibilities to be dynamically added to an object without modifying its code.
Instead of modifying request handlers directly, behaviors act as layers around the handler execution, applying cross-cutting concerns transparently.

How MediatR Implements the Decorator Pattern

Each MediatR behavior wraps the handler, decorating it with additional functionality. This follows the Open-Closed Principle (OCP), as we can extend behavior without modifying the existing handler.

Example: Applying the Decorator Pattern in MediatR Behaviors

When MediatR processes a request, it applies behaviors in order, wrapping the original handler:

1️⃣ ValidationBehavior runs first and validates the request.
2️⃣ LoggingBehavior logs the request before and after execution.
3️⃣ Request Handler executes after all decorators have run.

Each behavior decorates the request handler, adding functionality without modifying it.

Why Use Behaviors?

Separation of concerns: Keep handlers focused on business logic.

Code reusability: Apply behaviors to multiple handlers.

Maintainability: Centralized logic makes updates easier.

Consistent processing: Ensure common concerns (e.g., logging, validation) are always executed.

Implementing a MediatR Behavior
To create a MediatR behavior, you implement IPipelineBehavior.

Example: Logging Behavior

using MediatR;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Handling {RequestName}", typeof(TRequest).Name);
        var response = await next(); // Call the next behavior or handler
        _logger.LogInformation("Handled {RequestName}", typeof(TRequest).Name);
        return response;
    }
}
Enter fullscreen mode Exit fullscreen mode

Registering the Behavior in DI

To make behaviors work, register them in the DI container:

    builder.Services.AddMediatR(options =>
    {
        options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());

        options.AddOpenBehavior(typeof(ValidationBehaviour<,>));
        options.AddOpenBehavior(typeof(PerformanceBehaviour<,>));
        options.AddOpenBehavior(typeof(UnhandledExceptionBehaviour<,>)); 
        options.AddOpenBehavior(typeof(LoggingBehavior<,>));
    });
Enter fullscreen mode Exit fullscreen mode

More Use Cases for MediatR Behaviors :

Validation

Integrate FluentValidation to validate requests before handling.

using FluentValidation;
using MediatR;

public class ValidationBehaviour<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators) : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators = validators;

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        if (_validators.Any())
        {
            var context = new ValidationContext<TRequest>(request);

            var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
            var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();

            if (failures.Any())
            {
                throw new ValidationException(failures);
            }
        }

        return await next();
    }
}
Enter fullscreen mode Exit fullscreen mode

Performance Monitoring

Measure execution time using Stopwatch.

using MediatR;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

public class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<PerformanceBehavior<TRequest, TResponse>> _logger;

    public PerformanceBehavior(ILogger<PerformanceBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        var stopwatch = Stopwatch.StartNew();
        var response = await next();
        stopwatch.Stop();

        _logger.LogInformation("Request {Request} executed in {ElapsedMilliseconds}ms", typeof(TRequest).Name, stopwatch.ElapsedMilliseconds);

        return response;
    }
}

Enter fullscreen mode Exit fullscreen mode

Transaction Handling

Wrap requests in a database transaction scope.

using MediatR;
using Microsoft.EntityFrameworkCore;
using System.Threading;
using System.Threading.Tasks;

public class TransactionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly DbContext _dbContext;

    public TransactionBehavior(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        await using var transaction = await _dbContext.Database.BeginTransactionAsync(cancellationToken);

        try
        {
            var response = await next();
            await _dbContext.SaveChangesAsync(cancellationToken);
            await transaction.CommitAsync(cancellationToken);
            return response;
        }
        catch
        {
            await transaction.RollbackAsync(cancellationToken);
            throw;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Exception Handling

Catch and log exceptions globally.

using MediatR;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

public class ExceptionHandlingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<ExceptionHandlingBehavior<TRequest, TResponse>> _logger;

    public ExceptionHandlingBehavior(ILogger<ExceptionHandlingBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        try
        {
            return await next();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Request {Request} failed with exception {Message}", typeof(TRequest).Name, ex.Message);
            throw;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

**
Final Thoughts : **

MediatR Behaviors, leveraging the Decorator Pattern, provide an elegant solution for handling cross-cutting concerns in .NET applications. By combining multiple behaviors, developers can ensure cleaner, more maintainable, and efficient request processing

References

MediatR GitHub Repository

FluentValidation Documentation

Microsoft Dependency Injection

Decorator pattern

Decorator pattern

Top comments (0)