Skip to main content
ClaudeWave
Skill68 repo starsupdated 14d ago

dotnet-pipeline-behaviors

This Claude Code skill generates MediatR Pipeline Behaviors that implement cross-cutting concerns such as logging, validation, exception handling, caching, and performance monitoring in .NET applications. Use this when building clean architecture applications that need to apply consistent behaviors across all command and query handlers without modifying individual handler code.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/ronnythedev/dotnet-clean-architecture-skills /tmp/dotnet-pipeline-behaviors && cp -r /tmp/dotnet-pipeline-behaviors/skills/10-dotnet-pipeline-behaviors ~/.claude/skills/dotnet-pipeline-behaviors
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# 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

```csharp
// 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

```csharp
// 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

```csharp
// src/{name}.application/Abstractions/Behaviors/ExceptionHandlingBehavior.cs
using MediatR;
dotnet-clean-architectureSkill

Scaffolds a complete .NET solution following Clean Architecture principles with proper layer separation (API, Application, Domain, Infrastructure). Creates project structure, dependency injection setup, and cross-cutting concerns configuration.

dotnet-cqrs-command-generatorSkill

Generates CQRS Commands with Handlers, Validators, and Request DTOs following Clean Architecture patterns. Commands represent actions that modify state and return Result types for proper error handling.

dotnet-cqrs-query-generatorSkill

Generates CQRS Queries with Handlers and Response DTOs for read operations. Uses Dapper for optimized read queries, bypassing the domain model for better performance.

dotnet-domain-entity-generatorSkill

Generates Domain Entities following DDD principles with factory methods, private setters, domain events, and proper encapsulation. Supports aggregate roots, child entities, and value objects.

dotnet-repository-patternSkill

Generates Repository interfaces and implementations following the Repository pattern. Provides data access abstraction for aggregate roots with EF Core implementations.

dotnet-ef-core-configurationSkill

Generates Entity Framework Core configurations using Fluent API. Maps domain entities to database tables with proper relationships, constraints, and conventions.

dotnet-legacy-api-controllersSkill

Generates RESTful API Controllers with proper routing, versioning, authorization, and MediatR integration. Follows REST conventions and Clean Architecture patterns.

dotnet-minimal-api-endpointsSkill

Generates Minimal API endpoints following Microsoft's recommended approach. Creates fast, testable HTTP APIs with minimal code using MapGet/MapPost/MapPut/MapDelete. Preferred over controller-based APIs for new projects.