بنقرة واحدة
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 adding caching to .NET APIs or optimizing response times with distributed cache, output cache, or ETags.
Use when configuring API response formats, custom formatters, or Accept header handling.
Use when building controller-based REST APIs with action results, model binding, or MediatR integration.
Use when creating RESTful API controllers with MediatR dispatch and ProblemDetails error responses.
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when building minimal API endpoints with route groups, filters, or TypedResults.
| 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 |