Command/Query Responsibility Segregation basics. Separate read and write models, MediatR integration, and pipeline behaviors. Trigger: CQRS, command query separation, read model, write model.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
// Commands represent intent to change state
// Naming: {Verb}{Noun}Command
public sealed record CreateOrderCommand(
string CustomerName,
List<CreateOrderCommand.OrderItemDto> Items) : IRequest<Result<Guid>>
{
public sealed record OrderItemDto(Guid ProductId, int Quantity);
}
public sealed record UpdateOrderStatusCommand(
Guid OrderId,
OrderStatus NewStatus) : IRequest<Result>;
public sealed record DeleteOrderCommand(Guid OrderId) : IRequest<Result>;
// Queries represent requests for data — never modify state
// Naming: Get{Noun}Query or List{Noun}Query
public sealed record GetOrderQuery(Guid OrderId)
: IRequest<OrderResponse?>;
public sealed record ListOrdersQuery(
string? CustomerName,
OrderStatus? Status,
int Page = 1,
int PageSize = 20) : IRequest<PagedList<OrderSummaryResponse>>;
internal sealed class CreateOrderCommandHandler(
IOrderRepository repository,
IUnitOfWork unitOfWork,
ILogger<CreateOrderCommandHandler> logger)
: IRequestHandler<CreateOrderCommand, Result<Guid>>
{
public async Task<Result<Guid>> Handle(
CreateOrderCommand request, CancellationToken ct)
{
var order = Order.Create(request.CustomerName);
foreach (var item in request.Items)
order.AddItem(item.ProductId, item.Quantity);
repository.Add(order);
await unitOfWork.SaveChangesAsync(ct);
logger.LogInformation("Order {OrderId} created", order.Id);
return Result<Guid>.Success(order.Id);
}
}
internal sealed class ListOrdersQueryHandler(AppDbContext db)
: IRequestHandler<ListOrdersQuery, PagedList<OrderSummaryResponse>>
{
public async Task<PagedList<OrderSummaryResponse>> Handle(
ListOrdersQuery request, CancellationToken ct)
{
var query = db.Orders.AsNoTracking();
if (!string.IsNullOrEmpty(request.CustomerName))
query = query.Where(o =>
o.CustomerName.Contains(request.CustomerName));
if (request.Status.HasValue)
query = query.Where(o => o.Status == request.Status.Value);
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 OrderSummaryResponse(
o.Id, o.CustomerName, o.Total, o.Status.ToString()))
.ToListAsync(ct);
return new PagedList<OrderSummaryResponse>(
items, totalCount, request.Page, request.PageSize);
}
}
public sealed record OrderResponse(
Guid Id,
string CustomerName,
decimal Total,
string Status,
DateTimeOffset CreatedAt,
List<OrderItemResponse> Items);
public sealed record OrderSummaryResponse(
Guid Id, string CustomerName, decimal Total, string Status);
public sealed record OrderItemResponse(
Guid ProductId, string ProductName, int Quantity, decimal UnitPrice);
public sealed record PagedList<T>(
List<T> Items,
int TotalCount,
int Page,
int PageSize)
{
public int TotalPages =>
(int)Math.Ceiling(TotalCount / (double)PageSize);
public bool HasNext => Page < TotalPages;
public bool HasPrevious => Page > 1;
}
// Program.cs
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
// Pipeline behaviors (order matters: first = outermost)
cfg.AddBehavior(typeof(IPipelineBehavior<,>),
typeof(LoggingBehavior<,>));
cfg.AddBehavior(typeof(IPipelineBehavior<,>),
typeof(ValidationBehavior<,>));
});
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
// Minimal API
app.MapPost("/orders", async (
CreateOrderCommand cmd, ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(cmd, ct);
return result.Match(
id => Results.Created($"/orders/{id}", new { id }),
error => Results.BadRequest(error.ToProblemDetails()));
});
app.MapGet("/orders", async (
[AsParameters] ListOrdersQuery query,
ISender sender, CancellationToken ct) =>
{
return Results.Ok(await sender.Send(query, ct));
});
// Controller
[ApiController]
[Route("api/orders")]
public sealed class OrdersController(ISender sender) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Create(
CreateOrderCommand command, CancellationToken ct)
{
var result = await sender.Send(command, ct);
return result.Match<IActionResult>(
id => CreatedAtAction(nameof(Get), new { id }, null),
error => BadRequest(error.ToProblemDetails()));
}
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(Guid id, CancellationToken ct)
{
var result = await sender.Send(new GetOrderQuery(id), ct);
return result is not null ? Ok(result) : NotFound();
}
}
// Clean Architecture + CQRS
Application/
Orders/
Commands/
CreateOrder/
CreateOrderCommand.cs
CreateOrderCommandHandler.cs
CreateOrderCommandValidator.cs
Queries/
GetOrder/
GetOrderQuery.cs
GetOrderQueryHandler.cs
ListOrders/
ListOrdersQuery.cs
ListOrdersQueryHandler.cs
// VSA + CQRS
Features/
Orders/
CreateOrder.cs # Command + Handler + Validator in one file
GetOrder.cs # Query + Handler in one file
ListOrders.cs # Query + Handler in one file
IRequest< and IRequestHandler< implementationsCommand and Query suffixes in class namesMediatR package reference in .csprojCommands/ and Queries/ folder structureIPipelineBehavior< implementations{Verb}{Noun}Command / Get{Noun}QueryAsNoTracking() in all query handlers| Scenario | Use |
|---|---|
| Creating/updating data | Command |
| Reading/listing data | Query |
| Cross-cutting validation | Pipeline behavior |
| Cross-cutting logging | Pipeline behavior |
| Transaction wrapping | Pipeline behavior on commands only |