بنقرة واحدة
request-response
Use when designing CQRS request/response contracts with FluentValidation and Result types.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Use when designing CQRS request/response contracts with FluentValidation and Result types.
التثبيت باستخدام 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 | request-response |
| description | Use when designing CQRS request/response contracts with FluentValidation and Result types. |
| metadata | {"category":"cqrs","agent":"ef-specialist","when-to-use":"When designing CQRS request/response patterns with FluentValidation and Result types"} |
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