Receber por email
← Voltar para o catálogo

dotnet-pipeline-behaviors

Generates MediatR Pipeline Behaviors for cross-cutting concerns like logging, validation, exception handling, caching, and performance monitoring. Implements the decorator pattern around handlers.

66estrelas
Atualizado há 5 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add ronnythedev/dotnet-clean-architecture-skills

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/dotnet-pipeline-behaviors/svg)](https://www.skillteca.com.br/skills/dotnet-pipeline-behaviors)

Skills relacionadas

Ver todas de Desenvolvimento →

Alerta por categoria

Receba novas skills de Desenvolvimento toda segunda

1 email curto, só com as skills novas de Desenvolvimento. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

MediatR Pipeline Behaviors

Overview

Pipeline Behaviors implement cross-cutting concerns that execute before/after every command or query handler:

  • Validation - Validate requests before handler executes
  • Logging - Log request/response details
  • Exception Handling - Convert exceptions to Results
  • Transaction - Wrap handlers in database transactions
  • Caching - Cache query results
  • Performance - Monitor slow operations

Quick Reference

| Behavior | Purpose | Order | |----------|---------|-------| | LoggingBehavior | Log requests | First (outer) | | ValidationBehavior | Validate input | Second | | ExceptionHandlingBehavior | Convert exceptions | Third | | TransactionBehavior | Database transaction | Fourth | | CachingBehavior | Cache responses | Fifth (inner) |

Behavior Structure

/Application/Abstractions/Behaviors/
├── LoggingBehavior.cs
├── ValidationBehavior.cs
├── ExceptionHandlingBehavior.cs
├── TransactionBehavior.cs
├── QueryCachingBehavior.cs
└── PerformanceBehavior.cs

Template: Logging Behavior

// src/{name}.application/Abstractions/Behaviors/LoggingBehavior.cs
using MediatR;
using Microsoft.Extensions.Logging;
using Serilog.Context;

namespace {name}.application.abstractions.behaviors;

/// <summary>
/// Logs all requests and responses with timing information
/// </summary>
public sealed class LoggingBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<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)
    {
        var requestName = typeof(TRequest).Name;
        var requestId = Guid.NewGuid();

        using (LogContext.PushProperty("RequestId", requestId))
        using (LogContext.PushProperty("RequestName", requestName))
        {
            _logger.LogInformation(
                "Handling {RequestName} ({RequestId})",
                requestName,
                requestId);

            var stopwatch = System.Diagnostics.Stopwatch.StartNew();

            try
            {
                var response = await next();

                stopwatch.Stop();

                _logger.LogInformation(
                    "Handled {RequestName} ({RequestId}) in {ElapsedMs}ms",
                    requestName,
                    requestId,
                    stopwatch.ElapsedMilliseconds);

                return response;
            }
            catch (Exception ex)
            {
                stopwatch.Stop();

                _logger.LogError(
                    ex,
                    "Error handling {RequestName} ({RequestId}) after {ElapsedMs}ms",
                    requestName,
                    requestId,
                    stopwatch.ElapsedMilliseconds);

                throw;
            }
        }
    }
}

Template: Validation Behavior

// src/{name}.application/Abstractions/Behaviors/ValidationBehavior.cs
using FluentValidation;
using MediatR;
using {name}.domain.abstractions;

namespace {name}.application.abstractions.behaviors;

/// <summary>
/// Validates requests using FluentValidation validators
/// Returns ValidationResult with errors instead of throwing
/// </summary>
public sealed class ValidationBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        if (!_validators.Any())
        {
            return await next();
        }

        var context = new ValidationContext<TRequest>(request);

        var validationResults = await Task.WhenAll(
            _validators.Select(v => v.ValidateAsync(context, cancellationToken)));

        var errors = validationResults
            .SelectMany(result => result.Errors)
            .Where(failure => failure is not null)
            .Select(failure => new Error(
                failure.PropertyName,
                failure.ErrorMessage))
            .Distinct()
            .ToArray();

        if (errors.Length != 0)
        {
            return CreateValidationResult<TResponse>(errors);
        }

        return await next();
    }

    private static TResponse CreateValidationResult<TResponse>(Error[] errors)
    {
        // Handle Result type
        if (typeof(TResponse) == typeof(Result))
        {
            return (TResponse)(object)ValidationResult.WithErrors(errors);
        }

        // Handle Result<T> type
        var resultType = typeof(TResponse);

        if (resultType.IsGenericType &&
            resultType.GetGenericTypeDefinition() == typeof(Result<>))
        {
            var valueType = resultType.GetGenericArguments()[0];

            var validationResultType = typeof(ValidationResult<>).MakeGenericType(valueType);

            var validationResult = Activator.CreateInstance(
                validationResultType,
                BindingFlags.Instance | BindingFlags.NonPublic,
                null,
                new object[] { errors },
                null);

            return (TResponse)validationResult!;
        }

        throw new InvalidOperationException(
            $"Cannot create validation result for type {typeof(TResponse).Name}");
    }
}

Template: Exception Handling Behavior

// src/{name}.application/Abstractions/Behaviors/ExceptionHandlingBehavior.cs
using MediatR;
using Microsoft.Extensions.Logging;
using {name}.domain.abstractions;

namespace {name}.application.abstractions.behaviors;

/// <summary>
/// Catches unhandled exceptions and converts them to Result.Failure
/// </summary>
public sealed class ExceptionHandlingBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
    where TResponse : Result
{
    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)
        {
            var requestName = typeof(TRequest).Name;

            _logger.LogError(
                ex,
                "Unhandled exception for request {RequestName}",
                requestName);

            return CreateExceptionResult<TResponse>(ex);
        }
    }

    private static TResponse CreateExceptionResult<TResponse>(Exception exception)
    {
        var error = new Error(
            "Error.Unhandled",
            exception.Message);

        if (typeof(TResponse) == typeof(Result))
        {
            return (TResponse)(object)Result.Failure(error);
        }

        var resultType = typeof(TResponse);

        if (resultType.IsGenericType &&
            resultType.GetGenericTypeDefinition() == typeof(Result<>))
        {
            var valueType = resultType.GetGenericArguments()[0];

            var failureMethod = typeof(Result)
                .GetMethod(nameof(Result.Failure), new[] { typeof(Error) })!
                .MakeGenericMethod(valueType);

            return (TResponse)failureMethod.Invoke(null, new object[] { error })!;
        }

Compartilhar X LinkedIn