Token导航 LogoToken导航

Agent Skill · 研究检索

dotnet-architecture-patterns(点网架构模式)

dotnet-architecture-patterns 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

dotnet-architecture-patterns 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 58,252。

总安装 419周安装 18GitHub Stars 15下载 147核验 2026-05-01

安装说明

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

安装方式

通过对话安装

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

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

命令行安装

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

skills.shnpx skills
npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-architecture-patterns

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

dotnet-architecture-patterns 提供现代 .NET 应用程序的架构模式指导,涵盖最小 API 规模化组织、垂直切片架构等实用方法。适用于需要构建可维护、可扩展后端服务或重构现有系统的开发场景。通过关键词检索和模式匹配,快速定位相关架构方案并给出实施建议。

安装前需确认项目目标框架版本和维护状态,避免在不兼容环境中使用。涉及生产环境改造时,应先评估迁移成本和回滚方案。

来源仓库内路径:skills/dotnet-architecture-patterns

上手步骤

4 步
  1. 1

    确认自己的宿主工具

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

  2. 2

    拿到安装命令

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

  3. 3

    安装后重启宿主

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

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

dotnet-architecture-patterns 怎么安装?

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

dotnet-architecture-patterns 支持哪些 AI 工具?

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

dotnet-architecture-patterns 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

dotnet-architecture-patterns 是免费的吗?

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

dotnet-architecture-patterns 用的人多吗?

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

dotnet-architecture-patterns 安全吗?

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

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

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

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

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。

不太适合

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

研究检索类 Skill 对比

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

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
dotnet-architecture-patterns

点网架构模式 · 本页

41915需要联网未明确通过

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

SKILL.md 原文

dotnet-architecture-patterns

Modern architecture patterns for.NET applications. Covers practical approaches to organizing minimal APIs at scale, vertical slice architecture, request pipeline composition, validation strategies, caching, error handling, and idempotency/outbox patterns.

Out of scope: DI container mechanics and async/await patterns -- see [skill:dotnet-csharp-dependency-injection] and [skill:dotnet-csharp-async-patterns]. Project scaffolding and file layout -- see [skill:dotnet-scaffold-project]. Testing strategies -- see [skill:dotnet-testing-strategy] for decision guidance and [skill:dotnet-integration-testing] for WebApplicationFactory patterns.

Cross-references: [skill:dotnet-csharp-dependency-injection] for service registration and lifetimes, [skill:dotnet-csharp-async-patterns] for async pipeline patterns, [skill:dotnet-csharp-configuration] for Options pattern in configuration, [skill:dotnet-solid-principles] for SOLID/DRY design principles governing class and interface design.


Vertical Slice Architecture

Organize code by feature (vertical slice) rather than by technical layer (controllers, services, repositories). Each slice owns its endpoint, handler, validation, and data access.

Directory Structure

Features/
  Orders/
    CreateOrder/
      CreateOrderEndpoint.cs
      CreateOrderHandler.cs
      CreateOrderRequest.cs
      CreateOrderValidator.cs
    GetOrder/
      GetOrderEndpoint.cs
      GetOrderHandler.cs
    ListOrders/
      ListOrdersEndpoint.cs
      ListOrdersHandler.cs
  Products/
    GetProduct/
      ...

Why Vertical Slices

  • Low coupling: changing one feature does not ripple through shared layers
  • Easy navigation: everything for a feature is in one place
  • Independent testability: each slice has a clear input/output contract
  • Team scalability: different developers can work on different features without merge conflicts

Slice Anatomy

Each slice typically contains:

  1. Request/Response DTOs -- the contract
  2. Validator -- input validation rules
  3. Handler -- business logic
  4. Endpoint -- HTTP mapping (route, method, status codes)
// Features/Orders/CreateOrder/CreateOrderRequest.cs
public sealed record CreateOrderRequest(
    string CustomerId,
    List<OrderLineRequest> Lines);

public sealed record OrderLineRequest(
    string ProductId,
    int Quantity);

// Features/Orders/CreateOrder/CreateOrderResponse.cs
public sealed record CreateOrderResponse(
    string OrderId,
    decimal Total,
    DateTimeOffset CreatedAt);

Minimal API Organization at Scale

Route Group Pattern

Use MapGroup to organize related endpoints and apply shared filters:

// Program.cs
var app = builder.Build();

app.MapGroup("/api/orders")
   .WithTags("Orders")
   .MapOrderEndpoints();

app.MapGroup("/api/products")
   .WithTags("Products")
   .MapProductEndpoints();

app.Run();
// Features/Orders/OrderEndpoints.cs
public static class OrderEndpoints
{
    public static RouteGroupBuilder MapOrderEndpoints(this RouteGroupBuilder group)
    {
        group.MapPost("/", CreateOrderEndpoint.Handle)
             .WithName("CreateOrder")
             .Produces<CreateOrderResponse>(StatusCodes.Status201Created)
             .ProducesValidationProblem();

        group.MapGet("/{id}", GetOrderEndpoint.Handle)
             .WithName("GetOrder")
             .Produces<OrderResponse>()
             .ProducesProblem(StatusCodes.Status404NotFound);

        group.MapGet("/", ListOrdersEndpoint.Handle)
             .WithName("ListOrders")
             .Produces<PagedResult<OrderSummary>>();

        return group;
    }
}

Endpoint Classes

Keep each endpoint in its own static class with a single Handle method:

public static class CreateOrderEndpoint
{
    public static async Task<IResult> Handle(
        CreateOrderRequest request,
        IValidator<CreateOrderRequest> validator,
        IOrderService orderService,
        CancellationToken ct)
    {
        var validation = await validator.ValidateAsync(request, ct);
        if (!validation.IsValid)
        {
            return Results.ValidationProblem(validation.ToDictionary());
        }

        var order = await orderService.CreateAsync(request, ct);

        return Results.Created($"/api/orders/{order.OrderId}", order);
    }
}

Request Pipeline Composition

Endpoint Filters (Middleware for Endpoints)

Use endpoint filters for cross-cutting concerns scoped to specific routes:

// Validation filter applied to a route group
public sealed class ValidationFilter<TRequest> : IEndpointFilter
    where TRequest : class
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var request = context.Arguments.OfType<TRequest>().FirstOrDefault();
        if (request is null)
        {
            return Results.BadRequest();
        }

        var validator = context.HttpContext.RequestServices
            .GetService<IValidator<TRequest>>();

        if (validator is not null)
        {
            var result = await validator.ValidateAsync(request);
            if (!result.IsValid)
            {
                return Results.ValidationProblem(result.ToDictionary());
            }
        }

        return await next(context);
    }
}

// Usage
group.MapPost("/", CreateOrderEndpoint.Handle)
     .AddEndpointFilter<ValidationFilter<CreateOrderRequest>>();

Pipeline Order

The standard middleware pipeline order matters:

app.UseExceptionHandler();       // 1. Global error handling
app.UseStatusCodePages();        // 2. Status code formatting
app.UseRateLimiter();            // 3. Rate limiting
app.UseAuthentication();         // 4. Authentication
app.UseAuthorization();          // 5. Authorization
// Endpoint routing happens here

Error Handling

Problem Details (RFC 9457)

Use the built-in Problem Details support for consistent error responses:

// Program.cs
builder.Services.AddProblemDetails(options =>
{
    options.CustomizeProblemDetails = context =>
    {
        context.ProblemDetails.Extensions["traceId"] =
            context.HttpContext.TraceIdentifier;
    };
});

app.UseExceptionHandler();
app.UseStatusCodePages();

Result Pattern for Business Logic

Return a result type from handlers instead of throwing exceptions for expected business failures:

public abstract record Result<T>
{
    public sealed record Success(T Value) : Result<T>;
    public sealed record NotFound(string Message) : Result<T>;
    public sealed record ValidationFailed(IDictionary<string, string[]> Errors) : Result<T>;
    public sealed record Conflict(string Message) : Result<T>;
}

// In the handler
public async Task<Result<Order>> CreateAsync(
    CreateOrderRequest request,
    CancellationToken ct)
{
    var customer = await _db.Customers.FindAsync([request.CustomerId], ct);
    if (customer is null)
    {
        return new Result<Order>.NotFound($"Customer {request.CustomerId} not found");
    }

    // ... create order
    return new Result<Order>.Success(order);
}

// In the endpoint -- map result to HTTP response
return result switch
{
    Result<Order>.Success s => Results.Created($"/api/orders/{s.Value.Id}", s.Value),
    Result<Order>.NotFound n => Results.Problem(n.Message, statusCode: 404),
    Result<Order>.ValidationFailed v => Results.ValidationProblem(v.Errors),
    Result<Order>.Conflict c => Results.Problem(c.Message, statusCode: 409),
    _ => Results.Problem("Unexpected error", statusCode: 500)
};

Validation Strategy

Choose validation based on complexity. Prefer built-in mechanisms as the default; reserve FluentValidation for complex business rules that outgrow declarative attributes. For detailed framework guidance, see [skill:dotnet-input-validation]. For SOLID principles governing where validation belongs in your architecture, see [skill:dotnet-solid-principles].

Built-in: Data Annotations + MiniValidation (Default)

Start with Data Annotations for simple property-level constraints. Use MiniValidation for lightweight validation in Minimal APIs without MVC model binding overhead. For.NET 10+ projects, prefer the built-in AddValidation() source-generator pipeline (see [skill:dotnet-input-validation]).

public sealed record CreateProductRequest(
    [Required, MaxLength(200)] string Name,
    [Range(0.01, double.MaxValue)] decimal Price);

// In endpoint
if (!MiniValidator.TryValidate(request, out var errors))
{
    return Results.ValidationProblem(errors);
}

FluentValidation (Opt-in for Complex Rules)

When validation rules outgrow annotations -- cross-property rules, conditional logic, database-dependent checks -- use FluentValidation. Register via assembly scanning and apply through endpoint filters or manual validation:

// Register validators by assembly scanning
builder.Services.AddValidatorsFromAssemblyContaining<Program>(ServiceLifetime.Scoped);

// Validator implementation
public sealed class CreateOrderValidator : AbstractValidator<CreateOrderRequest>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.CustomerId)
            .NotEmpty()
            .MaximumLength(50);

        RuleFor(x => x.Lines)
            .NotEmpty()
            .WithMessage("Order must have at least one line item");

        RuleForEach(x => x.Lines)
            .ChildRules(line =>
            {
                line.RuleFor(l => l.ProductId).NotEmpty();
                line.RuleFor(l => l.Quantity).GreaterThan(0);
            });
    }
}

Caching Strategy

Output Caching (HTTP Response Caching)

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(p => p.NoCache());

    options.AddPolicy("ProductList", p =>
        p.Expire(TimeSpan.FromMinutes(5))
         .Tag("products"));

    options.AddPolicy("ProductDetail", p =>
        p.Expire(TimeSpan.FromMinutes(10))
         .SetVaryByRouteValue("id")
         .Tag("products"));
});

app.UseOutputCache();

// Apply to endpoints
group.MapGet("/", ListProductsEndpoint.Handle)
     .CacheOutput("ProductList");

group.MapGet("/{id}", GetProductEndpoint.Handle)
     .CacheOutput("ProductDetail");

// Invalidate by tag
app.MapPost("/api/products", async (
    IOutputCacheStore cache,
    /* ... */) =>
{
    // ... create product
    await cache.EvictByTagAsync("products", ct);
    return Results.Created(/* ... */);
});

Distributed Caching (Application-Level)

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration
        .GetConnectionString("Redis");
});

// Usage with IDistributedCache
public sealed class ProductService(
    IDistributedCache cache,
    AppDbContext db)
{
    public async Task<Product?> GetByIdAsync(
        string id, CancellationToken ct = default)
    {
        var cacheKey = $"product:{id}";
        var cached = await cache.GetStringAsync(cacheKey, ct);

        if (cached is not null)
        {
            return JsonSerializer.Deserialize<Product>(cached);
        }

        var product = await db.Products.FindAsync([id], ct);
        if (product is not null)
        {
            await cache.SetStringAsync(
                cacheKey,
                JsonSerializer.Serialize(product),
                new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
                },
                ct);
        }

        return product;
    }
}

HybridCache (.NET 9+)

HybridCache combines L1 (in-memory) and L2 (distributed) caching with stampede protection:

builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(10),
        LocalCacheExpiration = TimeSpan.FromMinutes(2)
    };
});

// Usage -- stampede-safe, two-tier
public sealed class ProductService(HybridCache cache, AppDbContext db)
{
    public async Task<Product?> GetByIdAsync(
        string id, CancellationToken ct = default)
    {
        return await cache.GetOrCreateAsync(
            $"product:{id}",
            async cancel => await db.Products.FindAsync([id], cancel),
            cancellationToken: ct);
    }
}

Idempotency and Outbox Pattern

Idempotency Keys

Prevent duplicate processing of retried requests. A robust idempotency implementation must:

  1. Scope keys by route + user/tenant to prevent cross-endpoint collisions
  2. Atomically claim the key before executing, so concurrent duplicates are rejected
  3. Store a concrete response envelope (not an IResult reference) for safe replay
Database-Backed Idempotency (Recommended)

Use a database row with a unique constraint for atomic claim-then-execute:

// Idempotency record stored alongside domain data
public sealed class IdempotencyRecord
{
    public required string Key { get; init; }         // Scoped key
    public required string RequestRoute { get; init; }
    public required string? UserId { get; init; }
    public int StatusCode { get; set; }
    public string? ResponseBody { get; set; }         // Serialized JSON
    public string? ContentType { get; set; }
    public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
    public bool IsCompleted { get; set; }
}

public sealed class IdempotencyFilter(AppDbContext db) : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var httpContext = context.HttpContext;
        if (!httpContext.Request.Headers.TryGetValue(
            "Idempotency-Key", out var keyValues))
        {
            return await next(context);
        }

        var clientKey = keyValues.ToString();
        if (string.IsNullOrWhiteSpace(clientKey) || clientKey.Length > 256)
        {
            return Results.Problem("Invalid Idempotency-Key", statusCode: 400);
        }

        // Scope key by route + user to prevent cross-endpoint/cross-tenant collisions
        var route = $"{httpContext.Request.Method}:{httpContext.Request.Path}";
        var userId = httpContext.User.FindFirst("sub")?.Value ?? "anonymous";
        var scopedKey = $"{route}:{userId}:{clientKey}";

        // Check for existing record (completed = replay, in-progress = reject)
        var existing = await db.IdempotencyRecords
            .FirstOrDefaultAsync(r => r.Key == scopedKey);

        if (existing is { IsCompleted: true })
        {
            // Replay: value responses get body, non-value responses get status only
            if (existing.ResponseBody is not null)
            {
                return Results.Text(
                    existing.ResponseBody,
                    existing.ContentType ?? "application/json",
                    statusCode: existing.StatusCode);
            }

            return Results.StatusCode(existing.StatusCode);
        }

        if (existing is { IsCompleted: false })
        {
            // Another request claimed this key but hasn't completed yet.
            // Reject to prevent duplicate execution.
            return Results.Problem(
                "Duplicate request in progress", statusCode: 409);
        }

        // Atomic claim: insert with unique constraint -- concurrent duplicate
        // requests will throw DbUpdateException and get a 409 Conflict
        {
            var record = new IdempotencyRecord
            {
                Key = scopedKey,
                RequestRoute = route,
                UserId = userId,
                IsCompleted = false
            };
            db.IdempotencyRecords.Add(record);

            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateException)
            {
                return Results.Problem(
                    "Duplicate request in progress", statusCode: 409);
            }

            existing = record;
        }

        // Execute the actual handler
        var result = await next(context);

        // Always finalize the record -- handles both value and non-value results
        // (Results.Ok(obj), Results.NoContent(), Results.Accepted(), etc.)
        existing.StatusCode = result is IStatusCodeHttpResult statusResult
            ? statusResult.StatusCode ?? 200
            : 200;
        existing.ResponseBody = result is IValueHttpResult valueResult
            ? JsonSerializer.Serialize(valueResult.Value)
            : null;  // No body for non-value results (204, 202, etc.)
        existing.ContentType = existing.ResponseBody is not null
            ? "application/json"
            : null;
        existing.IsCompleted = true;
        await db.SaveChangesAsync();

        return result;
    }
}

Key design choices:

  • Three states: no record (claim it), in-progress (reject 409), completed (replay cached response)
  • Unique constraint on Key column provides atomic claim without distributed locks
  • Scoped key (route:userId:clientKey) prevents cross-endpoint and cross-tenant collisions
  • Response envelope stores serialized body + status code + content type (not IResult references)
  • In-progress records (claimed but not completed) return 409 to concurrent duplicates
  • Consider adding a stale-record cleanup job to handle abandoned in-progress records (e.g., process crashed mid-execution)

Transactional Outbox Pattern

Guarantee at-least-once delivery of domain events alongside database writes:

// 1. Store outbox messages in the same transaction as the domain write
public sealed class OutboxMessage
{
    public Guid Id { get; init; } = Guid.NewGuid();
    public required string EventType { get; init; }
    public required string Payload { get; init; }
    public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
    public DateTimeOffset? ProcessedAt { get; set; }
}

// 2. In the handler -- same DbContext transaction
public async Task<Order> CreateOrderAsync(
    CreateOrderRequest request,
    CancellationToken ct)
{
    await using var transaction = await _db.Database
        .BeginTransactionAsync(ct);

    var order = new Order { /* ... */ };
    _db.Orders.Add(order);

    _db.OutboxMessages.Add(new OutboxMessage
    {
        EventType = "OrderCreated",
        Payload = JsonSerializer.Serialize(
            new OrderCreatedEvent(order.Id, order.Total))
    });

    await _db.SaveChangesAsync(ct);
    await transaction.CommitAsync(ct);

    return order;
}

// 3. Background processor publishes outbox messages
// See [skill:dotnet-background-services] for the Channels-based
// processor that polls and publishes these messages.

The outbox pattern ensures that if the database write succeeds, the event is guaranteed to be published (eventually), even if the message broker is temporarily unavailable.


Key Principles

  • Apply SOLID principles -- Single Responsibility (one reason to change per class), Open/Closed (extend via new types, not modifying existing code), Dependency Inversion (depend on abstractions at module boundaries). See [skill:dotnet-solid-principles] for anti-patterns, fixes, and compliance tests.
  • Prefer composition over inheritance -- use endpoint filters, middleware, and pipeline composition rather than base classes
  • Keep slices independent -- avoid shared abstractions that couple features together; DRY applies to knowledge duplication, not code similarity across bounded contexts
  • Validate early, fail fast -- validate at the boundary (endpoint filters) before entering business logic
  • Use Problem Details everywhere -- consistent error format across all endpoints
  • Cache at the right level -- output cache for HTTP responses, distributed cache for shared state, HybridCache for both
  • Make writes idempotent -- use idempotency keys for any non-idempotent operation clients may retry

Agent Gotchas

  1. Idempotency must handle three states -- An idempotency implementation must distinguish no-record (claim it), in-progress (reject duplicate), and completed (replay cached response). Check-then-act without guarding the in-progress state allows concurrent duplicate execution.
  2. Always finalize idempotency records unconditionally -- Do NOT gate completion on specific IResult subtypes (e.g., IValueHttpResult). Non-value results like Results.NoContent() or Results.Accepted() would be left permanently stuck in the in-progress state.
  3. Cache invalidation must be explicit -- When using output caching or distributed caching, ALWAYS invalidate (evict by tag or key) after write operations. Forgetting invalidation causes stale reads that are hard to debug.
  4. HybridCache stampede protection only works with GetOrCreateAsync -- Do NOT use separate get-then-set patterns with HybridCache; use the factory overload so the library serializes concurrent requests for the same key.
  5. Outbox messages must be written in the same transaction as domain data -- If you write the outbox message outside the domain transaction, a crash between the two writes loses the event. ALWAYS use BeginTransactionAsync to wrap both writes atomically.
  6. Endpoint filter order matters -- Filters added first run outermost. A validation filter must run before an idempotency filter, otherwise invalid requests get cached as idempotent responses.
  7. Do NOT share DbContext across concurrent requests -- DbContext is not thread-safe. Each request must resolve its own scoped instance from DI. Using a singleton or static DbContext causes data corruption under concurrency.

Knowledge Sources

Architecture patterns in this skill are grounded in publicly available content from:

  • Jimmy Bogard's Vertical Slice Architecture -- Organizing code by feature instead of by technical layer. Bogard advocates that each vertical slice owns its own request, handler, validation, and data access, reducing cross-feature coupling. He originated the popular MediatR library for request/handler dispatch in.NET, though MediatR is now commercial for commercial use. When applying vertical slice guidance, prefer the built-in IEndpointFilter and handler pattern shown above rather than introducing a third-party mediator dependency for simple scenarios. Source: https://www.jimmybogard.com/vertical-slice-architecture/
  • Jimmy Bogard's Domain-Driven Design Patterns -- Rich domain model guidance including entity design, value objects, domain events, and aggregate boundaries. Key insight: domain events should be dispatched after the aggregate state change is persisted (not before), to avoid inconsistency if persistence fails. Source: https://www.jimmybogard.com/
  • Nick Chapsas' Modern.NET Patterns -- Practical patterns for modern.NET including result types for error handling, structured validation pipelines, and modern C# feature adoption in production codebases. Source: https://www.youtube.com/@nickchapsas
Note: This skill applies publicly documented guidance. It does not represent or speak for the named sources. MediatR is a commercial product for commercial use; the patterns here are demonstrated with built-in.NET mechanisms.

References

平台分布

Codex

36.89%
按下载量换算54

Claude

26.65%
按下载量换算39

Cursor

19.26%
按下载量换算28

Gemini CLI

9.27%
按下载量换算14

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

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

继续浏览同类 Skills