Token导航 LogoToken导航

Agent Skill · 待分类

pipeline-behaviors(管道行为)

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

pipeline-behaviors 是待分类类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为只读;许可证信息还不明确,第三方扫描已通过,按安装量在待分类类的 11,209 个 Skill 里排第 10,789。

总安装 190周安装 8GitHub Stars 50下载 67核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 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

这个 Skill 是做什么的

这个 Skill 的分类还没最终确定,用法以来源仓库的说明为准。

pipeline-behaviors 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息。适合在 Codex、Claude、Cursor、Gemini CLI 中围绕仓库状态、代码变更或协作事项进行整理。通过 npx skills add 命令从指定 GitHub 仓库安装并使用。

安装前需确认权限范围、维护状态,注意是否涉及联网、命令执行或文件读写操作。建议结合原始 README 和仓库内容进一步核验具体功能和使用边界。

来源仓库内路径:skills/pipeline-behaviors

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/ronnythedev/dotnet-clean-architecture-skills -…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它标注为只读,先用只读任务试一次,确认行为符合预期再扩大使用范围。

常见问题

6 条

pipeline-behaviors 怎么安装?

在终端执行 npx skills add https://github.com/ronnythedev/dotnet-clean-architecture-skills --skill pip…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

pipeline-behaviors 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

pipeline-behaviors 需要什么权限?

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

pipeline-behaviors 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

pipeline-behaviors 用的人多吗?

公开数据是累计安装 190 次,近一周安装 8 次,下载 67 次,GitHub 50 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

pipeline-behaviors 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。
  • 标注为只读权限,试用阶段不容易误改本地文件。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理待分类类任务、又不想每次都手写提示词的人。
  • 只想先试试效果、不希望它改动本地文件的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

待分类类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
infsh-cli

infsh CLI · Codex、Claude

4,374,576382权限未标注未明确未收录
ai-video-generation

AI 视频生成 · Codex、Claude

4,313,376382调用外部服务未明确有提醒
lark-openapi-explorer

云雀 OpenAPI 浏览器 · Codex、Claude

2,064,1209,011需要联网未明确有提醒
lark-workflow-standup-report

Lark 工作流程站立报告 · Codex、Claude

2,041,6328,978需要联网未明确通过
use-my-browser

使用我的浏览器 · Codex、Claude

1,848,49140操作浏览器未明确未通过
pipeline-behaviors

管道行为 · 本页

19050只读未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

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

平台分布

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