Token导航 LogoToken导航TokenDH.com
待分类只读github未标认证来源可访问许可证需确认审计通过

pipeline-behaviors管道行为

Agent Skill

pipeline-behaviors 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

190

周安装

8

GitHub Stars

50

下载量

67
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:pipeline-behaviors(管道行为)
来源仓库:https://github.com/ronnythedev/dotnet-clean-architecture-skills
仓库路径:skills/pipeline-behaviors
安装命令:
npx skills add https://github.com/ronnythedev/dotnet-clean-architecture-skills --skill pipeline-behaviors
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/ronnythedev/dotnet-clean-architecture-skills --skill pipeline-behaviors

简介

pipeline-behaviors 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息。

  • 适合在 Codex、Claude、Cursor、Gemini CLI 中围绕仓库状态、代码变更或协作事项进行整理。
  • 通过 npx skills add 命令从指定 GitHub 仓库安装并使用。
  • 安装前需确认权限范围、维护状态,注意是否涉及联网、命令执行或文件读写操作。
  • 建议结合原始 README 和仓库内容进一步核验具体功能和使用边界。

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

BehaviorPurposeOrder
LoggingBehaviorLog requestsFirst (outer)
ValidationBehaviorValidate inputSecond
ExceptionHandlingBehaviorConvert exceptionsThird
TransactionBehaviorDatabase transactionFourth
CachingBehaviorCache responsesFifth (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 })!;
        }

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

Template: Transaction Behavior

// src/{name}.application/Abstractions/Behaviors/TransactionBehavior.cs
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using {name}.application.abstractions.messaging;
using {name}.infrastructure;

namespace {name}.application.abstractions.behaviors;

/// <summary>
/// Wraps command handlers in database transactions
/// Only applies to commands (write operations)
/// </summary>
public sealed class TransactionBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : ICommand<TResponse>  // Only commands
{
    private readonly ApplicationDbContext _dbContext;
    private readonly ILogger<TransactionBehavior<TRequest, TResponse>> _logger;

    public TransactionBehavior(
        ApplicationDbContext dbContext,
        ILogger<TransactionBehavior<TRequest, TResponse>> logger)
    {
        _dbContext = dbContext;
        _logger = logger;
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        var requestName = typeof(TRequest).Name;

        // Check if already in a transaction
        if (_dbContext.Database.CurrentTransaction is not null)
        {
            return await next();
        }

        await using var transaction = await _dbContext.Database
            .BeginTransactionAsync(cancellationToken);

        _logger.LogInformation(
            "Beginning transaction for {RequestName}",
            requestName);

        try
        {
            var response = await next();

            await transaction.CommitAsync(cancellationToken);

            _logger.LogInformation(
                "Committed transaction for {RequestName}",
                requestName);

            return response;
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(cancellationToken);

            _logger.LogError(
                ex,
                "Rolled back transaction for {RequestName}",
                requestName);

            throw;
        }
    }
}

Template: Query Caching Behavior

// src/{name}.application/Abstractions/Caching/ICachedQuery.cs
namespace {name}.application.abstractions.caching;

/// <summary>
/// Marker interface for queries that should be cached
/// </summary>
public interface ICachedQuery
{
    string CacheKey { get; }
    TimeSpan? CacheDuration { get; }
}

/// <summary>
/// Strongly-typed cached query
/// </summary>
public interface ICachedQuery<TResponse> : IQuery<TResponse>, ICachedQuery
{
}
// src/{name}.application/Abstractions/Behaviors/QueryCachingBehavior.cs
using MediatR;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using {name}.application.abstractions.caching;
using {name}.domain.abstractions;

namespace {name}.application.abstractions.behaviors;

/// <summary>
/// Caches query results using distributed cache
/// Only applies to queries implementing ICachedQuery
/// </summary>
public sealed class QueryCachingBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : ICachedQuery<TResponse>
    where TResponse : class
{
    private readonly IDistributedCache _cache;
    private readonly ILogger<QueryCachingBehavior<TRequest, TResponse>> _logger;

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };

    public QueryCachingBehavior(
        IDistributedCache cache,
        ILogger<QueryCachingBehavior<TRequest, TResponse>> logger)
    {
        _cache = cache;
        _logger = logger;
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        var cacheKey = request.CacheKey;

        // Try to get from cache
        var cachedValue = await _cache.GetStringAsync(cacheKey, cancellationToken);

        if (!string.IsNullOrEmpty(cachedValue))
        {
            _logger.LogInformation(
                "Cache hit for {CacheKey}",
                cacheKey);

            return JsonSerializer.Deserialize<TResponse>(cachedValue, JsonOptions)!;
        }

        _logger.LogInformation(
            "Cache miss for {CacheKey}",
            cacheKey);

        // Execute query
        var response = await next();

        // Cache the result if successful
        if (response is Result { IsSuccess: true })
        {
            var cacheOptions = new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = request.CacheDuration ?? TimeSpan.FromMinutes(5)
            };

            var serialized = JsonSerializer.Serialize(response, JsonOptions);

            await _cache.SetStringAsync(
                cacheKey,
                serialized,
                cacheOptions,
                cancellationToken);

            _logger.LogInformation(
                "Cached response for {CacheKey}",
                cacheKey);
        }

        return response;
    }
}

Using Cached Query

// src/{name}.application/{Feature}/Get{Entity}ById/Get{Entity}ByIdQuery.cs
public sealed record Get{Entity}ByIdQuery(Guid Id)
    : ICachedQuery<{Entity}Response>
{
    public string CacheKey => $"{Entity}:{Id}";
    public TimeSpan? CacheDuration => TimeSpan.FromMinutes(10);
}

Template: Performance Behavior

// src/{name}.application/Abstractions/Behaviors/PerformanceBehavior.cs
using System.Diagnostics;
using MediatR;
using Microsoft.Extensions.Logging;

namespace {name}.application.abstractions.behaviors;

/// <summary>
/// Logs a warning for slow requests (>500ms by default)
/// </summary>
public sealed class PerformanceBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger<PerformanceBehavior<TRequest, TResponse>> _logger;
    private readonly Stopwatch _timer;
    private const int SlowRequestThresholdMs = 500;

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

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        _timer.Start();

        var response = await next();

        _timer.Stop();

        var elapsedMs = _timer.ElapsedMilliseconds;

        if (elapsedMs > SlowRequestThresholdMs)
        {
            var requestName = typeof(TRequest).Name;

            _logger.LogWarning(
                "Long running request: {RequestName} ({ElapsedMs}ms) - {@Request}",
                requestName,
                elapsedMs,
                request);
        }

        return response;
    }
}

Template: Idempotency Behavior

// src/{name}.application/Abstractions/Idempotency/IIdempotentCommand.cs
namespace {name}.application.abstractions.idempotency;

/// <summary>
/// Marker interface for commands that support idempotency
/// </summary>
public interface IIdempotentCommand
{
    Guid IdempotencyKey { get; }
}
// src/{name}.application/Abstractions/Behaviors/IdempotencyBehavior.cs
using MediatR;
using Microsoft.Extensions.Logging;
using {name}.application.abstractions.idempotency;
using {name}.domain.abstractions;

namespace {name}.application.abstractions.behaviors;

/// <summary>
/// Prevents duplicate command execution using idempotency keys
/// </summary>
public sealed class IdempotencyBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IIdempotentCommand, IRequest<TResponse>
    where TResponse : Result
{
    private readonly IIdempotencyService _idempotencyService;
    private readonly ILogger<IdempotencyBehavior<TRequest, TResponse>> _logger;

    public IdempotencyBehavior(
        IIdempotencyService idempotencyService,
        ILogger<IdempotencyBehavior<TRequest, TResponse>> logger)
    {
        _idempotencyService = idempotencyService;
        _logger = logger;
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        // Check if already processed
        if (await _idempotencyService.ExistsAsync(
            request.IdempotencyKey,
            cancellationToken))
        {
            _logger.LogInformation(
                "Duplicate request detected with key {IdempotencyKey}",
                request.IdempotencyKey);

            // Return cached response or success
            return await _idempotencyService
                .GetResponseAsync<TResponse>(request.IdempotencyKey, cancellationToken)
                ?? CreateSuccessResult<TResponse>();
        }

        var response = await next();

        // Store the response
        await _idempotencyService.SaveAsync(
            request.IdempotencyKey,
            response,
            cancellationToken);

        return response;
    }

    private static TResponse CreateSuccessResult<TResponse>()
    {
        if (typeof(TResponse) == typeof(Result))
        {
            return (TResponse)(object)Result.Success();
        }

        var resultType = typeof(TResponse);

        if (resultType.IsGenericType &&
            resultType.GetGenericTypeDefinition() == typeof(Result<>))
        {
            // Return default success - caller should use cached response instead
            throw new InvalidOperationException(
                "Cannot create default success for generic Result. " +
                "Cached response should be used.");
        }

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

Registering Behaviors

// src/{name}.application/DependencyInjection.cs
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using {name}.application.abstractions.behaviors;

namespace {name}.application;

public static class DependencyInjection
{
    public static IServiceCollection AddApplication(this IServiceCollection services)
    {
        services.AddMediatR(configuration =>
        {
            configuration.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly);

            // Register behaviors in order (outer to inner)
            // Logging is outermost - sees everything
            configuration.AddOpenBehavior(typeof(LoggingBehavior<,>));

            // Performance monitoring
            configuration.AddOpenBehavior(typeof(PerformanceBehavior<,>));

            // Validation - reject invalid requests early
            configuration.AddOpenBehavior(typeof(ValidationBehavior<,>));

            // Exception handling - convert exceptions to Results
            configuration.AddOpenBehavior(typeof(ExceptionHandlingBehavior<,>));

            // Transaction - wrap commands in transactions
            // Note: Only add if using EF Core directly in Application layer
            // configuration.AddOpenBehavior(typeof(TransactionBehavior<,>));
        });

        services.AddValidatorsFromAssembly(typeof(DependencyInjection).Assembly);

        return services;
    }
}

Behavior Execution Order

Request
    │
    ▼
┌─────────────────────┐
│  LoggingBehavior    │  ← Outermost: logs request start
│  ┌─────────────────┐│
│  │ PerformanceBeh. ││  ← Starts timer
│  │ ┌─────────────┐ ││
│  │ │ Validation  │ ││  ← Validates request
│  │ │ ┌─────────┐ │ ││
│  │ │ │Exception│ │ ││  ← Catches exceptions
│  │ │ │ ┌─────┐ │ │ ││
│  │ │ │ │Trans.│ │ │ ││  ← Begins transaction
│  │ │ │ │ ┌─┐ │ │ │ ││
│  │ │ │ │ │H│ │ │ │ ││  ← Handler executes
│  │ │ │ │ └─┘ │ │ │ ││
│  │ │ │ └─────┘ │ │ ││  ← Commits/Rolls back
│  │ │ └─────────┘ │ ││  ← Catches, converts to Result
│  │ └─────────────┘ ││  ← Stops timer, logs slow
│  └─────────────────┘│
└─────────────────────┘  ← Logs request end
    │
    ▼
Response

Critical Rules

  1. Register order matters - First registered is outermost
  2. Generic constraints - Use where TRequest: ICommand for command-only behaviors
  3. Don't swallow exceptions - Log and rethrow or convert to Result
  4. Keep behaviors focused - One responsibility per behavior
  5. Use open generics - typeof(Behavior<,>) not typeof(Behavior<Cmd, Resp>)
  6. Async all the way - Never block with .Result or .Wait()
  7. Don't modify request - Behaviors are observers, not transformers
  8. Transaction behavior last - Before handler, after validation
  9. Cache reads, not writes - Only cache query results
  10. Log at appropriate level - Info for normal, Warning for slow, Error for failures

Anti-Patterns to Avoid

// ❌ WRONG: Behavior that modifies request
public async Task<TResponse> Handle(...)
{
    request.ModifiedAt = DateTime.UtcNow;  // Don't modify!
    return await next();
}

// ✅ CORRECT: Behaviors observe, don't modify
public async Task<TResponse> Handle(...)
{
    _logger.LogInformation("Processing at {Time}", DateTime.UtcNow);
    return await next();
}

// ❌ WRONG: Swallowing exceptions silently
try { return await next(); }
catch { return default!; }  // Silent failure!

// ✅ CORRECT: Log and convert or rethrow
try { return await next(); }
catch (Exception ex)
{
    _logger.LogError(ex, "Error in handler");
    return CreateFailureResult(ex);
}

// ❌ WRONG: Blocking async code
var result = next().Result;  // Deadlock risk!

// ✅ CORRECT: Await properly
var result = await next();

// ❌ WRONG: Caching commands
public sealed class CachingBehavior<TRequest, TResponse>
    where TRequest : ICommand<TResponse>  // Commands shouldn't be cached!

// ✅ CORRECT: Cache only queries
public sealed class CachingBehavior<TRequest, TResponse>
    where TRequest : ICachedQuery<TResponse>

Related Skills

  • cqrs-command-generator - Commands that flow through behaviors
  • cqrs-query-generator - Queries that flow through behaviors
  • result-pattern - Result types used by behaviors
  • dotnet-clean-architecture - Application layer placement

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

38.6%
按下载量换算26

Claude

29.16%
按下载量换算20

Cursor

19.08%
按下载量换算13

Gemini CLI

8.52%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills