بنقرة واحدة
vertical-slice
Use when implementing Vertical Slice Architecture with feature folders and co-located code.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Use when implementing Vertical Slice Architecture with feature folders and co-located code.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when writing async code, propagating CancellationTokens, or fixing async/await pitfalls.
Use when applying or enforcing C# coding style — namespaces, sealed classes, var usage, XML docs.
Use when applying modern C# idioms — records, pattern matching, primary constructors, collection expressions.
Use when registering services, choosing lifetimes, or implementing DI patterns like decorator or keyed services.
Use when selecting or implementing design patterns in C# — factory, builder, strategy, decorator, or mediator.
| name | vertical-slice |
| description | Use when implementing Vertical Slice Architecture with feature folders and co-located code. |
| metadata | {"category":"architecture","agent":"dotnet-architect"} |
| when_to_use | When organizing code by feature using Vertical Slice Architecture |
Common/ or Shared/ — extract only when duplication is provensrc/{Company}.{Domain}.WebApi/
Features/
Orders/
CreateOrder.cs # Command + Handler + Validator
GetOrder.cs # Query + Handler
ListOrders.cs # Query + Handler + Filter
OrderEndpoints.cs # Endpoint group registration
OrderResponse.cs # Shared response DTO (if reused)
Products/
CreateProduct.cs
GetProduct.cs
ProductEndpoints.cs
Common/
PagedList.cs
ValidationBehavior.cs
Program.cs
// Features/Orders/CreateOrder.cs
namespace {Company}.{Domain}.Features.Orders;
public static class CreateOrder
{
public sealed record Command(
string CustomerName,
List<LineItem> Items) : IRequest<Result>;
public sealed record LineItem(Guid ProductId, int Quantity);
public sealed record Result(Guid OrderId);
public sealed class Validator : AbstractValidator<Command>
{
public Validator()
{
RuleFor(x => x.CustomerName).NotEmpty().MaximumLength(200);
RuleFor(x => x.Items).NotEmpty();
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(x => x.Quantity).GreaterThan(0);
});
}
}
internal sealed class Handler(AppDbContext db)
: IRequestHandler<Command, Result>
{
public async Task<Result> Handle(
Command request, CancellationToken ct)
{
var order = new Order
{
CustomerName = request.CustomerName,
Items = request.Items.Select(i => new OrderItem
{
ProductId = i.ProductId,
Quantity = i.Quantity
}).ToList()
};
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
return new Result(order.Id);
}
}
}
// Features/Orders/ListOrders.cs
namespace {Company}.{Domain}.Features.Orders;
public static class ListOrders
{
public sealed record Query(
string? CustomerName,
int Page = 1,
int PageSize = 20) : IRequest<PagedList<OrderSummary>>;
public sealed record OrderSummary(
Guid Id, string CustomerName, decimal Total, DateTime CreatedAt);
internal sealed class Handler(AppDbContext db)
: IRequestHandler<Query, PagedList<OrderSummary>>
{
public async Task<PagedList<OrderSummary>> Handle(
Query request, CancellationToken ct)
{
var query = db.Orders.AsNoTracking();
if (!string.IsNullOrEmpty(request.CustomerName))
query = query.Where(o =>
o.CustomerName.Contains(request.CustomerName));
var totalCount = await query.CountAsync(ct);
var items = await query
.OrderByDescending(o => o.CreatedAt)
.Skip((request.Page - 1) * request.PageSize)
.Take(request.PageSize)
.Select(o => new OrderSummary(
o.Id, o.CustomerName, o.Total, o.CreatedAt))
.ToListAsync(ct);
return new PagedList<OrderSummary>(
items, totalCount, request.Page, request.PageSize);
}
}
}
// Features/Orders/OrderEndpoints.cs
namespace {Company}.{Domain}.Features.Orders;
public sealed class OrderEndpoints : IEndpointGroup
{
public void MapEndpoints(IEndpointRouteBuilder app)
{
var group = app.MapGroup("/orders")
.WithTags("Orders")
.RequireAuthorization();
group.MapPost("/", async (
CreateOrder.Command cmd, ISender sender) =>
{
var result = await sender.Send(cmd);
return TypedResults.Created(
$"/orders/{result.OrderId}", result);
}).WithSummary("Create a new order");
group.MapGet("/", async (
[AsParameters] ListOrders.Query query, ISender sender) =>
{
var result = await sender.Send(query);
return TypedResults.Ok(result);
}).WithSummary("List orders with filtering");
group.MapGet("/{id:guid}", async (
Guid id, ISender sender) =>
{
var result = await sender.Send(new GetOrder.Query(id));
return result is not null
? TypedResults.Ok(result)
: TypedResults.NotFound();
}).WithSummary("Get order by ID");
}
}
// Extract ONLY when the same logic appears in 3+ slices
// Common/Extensions/QueryableExtensions.cs
public static class QueryableExtensions
{
public static async Task<PagedList<T>> ToPagedListAsync<T>(
this IQueryable<T> query, int page, int pageSize,
CancellationToken ct)
{
var count = await query.CountAsync(ct);
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(ct);
return new PagedList<T>(items, count, page, pageSize);
}
}
internal)Features/ folder structure with operation-named filesCommand, Query, HandlerDbContext directlyIEndpointGroup or similar endpoint grouping patternsFeatures/ folder at the project rootCommon/ for truly cross-cutting utilities only| Question | VSA Answer |
|---|---|
| Where does the handler go? | Same file as the command/query |
| Do I need a repository? | No — use DbContext directly |
| When do I extract shared code? | After 3+ duplications |
| Can different slices use different data access? | Yes — EF, Dapper, raw SQL per slice |
| How do I handle cross-cutting? | MediatR pipeline behaviors |
| What about domain logic? | Keep in entity methods, call from handler |