CQRS request/response patterns, FluentValidation integration, Result types, and response DTO design. Trigger: request response, FluentValidation, validator, DTO, Result.
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.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
public sealed record CreateOrderCommand(
string CustomerName,
string ShippingEmail,
List<CreateOrderCommand.LineItem> Items)
: IRequest<Result<CreateOrderCommand.Response>>
{
public sealed record LineItem(
Guid ProductId,
int Quantity,
decimal UnitPrice);
public sealed record Response(
Guid OrderId,
decimal Total,
DateTimeOffset CreatedAt);
}
public sealed class CreateOrderCommandValidator
: AbstractValidator<CreateOrderCommand>
{
public CreateOrderCommandValidator()
{
RuleFor(x => x.CustomerName)
.NotEmpty()
.MaximumLength(200)
.WithMessage("Customer name is required (max 200 chars)");
RuleFor(x => x.ShippingEmail)
.NotEmpty()
.EmailAddress()
.WithMessage("Valid shipping email is required");
RuleFor(x => x.Items)
.NotEmpty()
.WithMessage("Order must contain at least one item");
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(x => x.ProductId)
.NotEmpty();
item.RuleFor(x => x.Quantity)
.GreaterThan(0)
.LessThanOrEqualTo(1000);
item.RuleFor(x => x.UnitPrice)
.GreaterThan(0);
});
}
}
public sealed class CreateOrderCommandValidator
: AbstractValidator<CreateOrderCommand>
{
public CreateOrderCommandValidator(AppDbContext db)
{
RuleFor(x => x.CustomerName)
.NotEmpty()
.MustAsync(async (name, ct) =>
await db.Customers.AnyAsync(
c => c.Name == name, ct))
.WithMessage("Customer not found");
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(x => x.ProductId)
.MustAsync(async (id, ct) =>
await db.Products.AnyAsync(
p => p.Id == id && p.IsActive, ct))
.WithMessage("Product not found or inactive");
});
}
}
public sealed record ListOrdersQuery(
string? CustomerName = null,
OrderStatus? Status = null,
DateTime? FromDate = null,
DateTime? ToDate = null,
string? SortBy = "CreatedAt",
bool SortDescending = true,
int Page = 1,
int PageSize = 20) : IRequest<PagedList<OrderSummaryResponse>>;
// Simple response
public sealed record OrderResponse(
Guid Id,
string CustomerName,
decimal Total,
string Status,
DateTimeOffset CreatedAt,
List<OrderItemResponse> Items);
public sealed record OrderItemResponse(
Guid ProductId,
string ProductName,
int Quantity,
decimal UnitPrice,
decimal LineTotal);
// Paged response
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;
}
// Summary response (for list views)
public sealed record OrderSummaryResponse(
Guid Id,
string CustomerName,
decimal Total,
string Status,
DateTimeOffset CreatedAt);
// Result type
public sealed class Result<T>
{
private Result(T value) { Value = value; IsSuccess = true; }
private Result(Error error) { Error = error; IsSuccess = false; }
public bool IsSuccess { get; }
public T Value { get; } = default!;
public Error Error { get; } = default!;
public static Result<T> Success(T value) => new(value);
public static Result<T> Failure(Error error) => new(error);
public TResult Match<TResult>(
Func<T, TResult> onSuccess,
Func<Error, TResult> onFailure)
=> IsSuccess ? onSuccess(Value) : onFailure(Error);
}
// Handler returning Result
internal sealed class GetOrderHandler(IOrderRepository repo)
: IRequestHandler<GetOrderQuery, Result<OrderResponse>>
{
public async Task<Result<OrderResponse>> Handle(
GetOrderQuery request, CancellationToken ct)
{
var order = await repo.FindAsync(request.OrderId, ct);
if (order is null)
return Result<OrderResponse>.Failure(
Error.NotFound("Order.NotFound",
$"Order {request.OrderId} not found"));
return Result<OrderResponse>.Success(order.ToResponse());
}
}
// Endpoint consuming Result
app.MapGet("/orders/{id}", async (Guid id, ISender sender) =>
{
var result = await sender.Send(new GetOrderQuery(id));
return result.Match(
value => Results.Ok(value),
error => error.Type switch
{
ErrorType.NotFound => Results.NotFound(
error.ToProblemDetails()),
_ => Results.BadRequest(error.ToProblemDetails())
});
});
// Register all validators from assembly
builder.Services.AddValidatorsFromAssembly(
typeof(CreateOrderCommandValidator).Assembly);
// FluentValidation runs automatically via ValidationBehavior
record or readonly)null instead of Result.Failure for expected failuresAbstractValidator< implementationsResult<T> or ErrorOr<T> typesIRequest<>RuleFor, RuleForEach validation rulesFluentValidation package referencedotnet add package FluentValidation.DependencyInjectionExtensionsAddValidatorsFromAssembly