원클릭으로
validation
Use when adding FluentValidation to gRPC requests with Calzolari integration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when adding FluentValidation to gRPC requests with Calzolari integration.
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 | validation |
| description | Use when adding FluentValidation to gRPC requests with Calzolari integration. |
| metadata | {"category":"microservice/grpc","agent":"command-architect"} |
| when_to_use | When writing FluentValidation validators for gRPC request messages |
AbstractValidator<TRequest> validates gRPC requests before handlers executeStatusCode.InvalidArgument with detailsPhrases.xxx) for localizationAddAppValidators() scans assembly for all validatorsnamespace {Company}.{Domain}.Grpc.Validators;
public sealed class CreateOrderRequestValidator
: AbstractValidator<CreateOrderRequest>
{
public CreateOrderRequestValidator()
{
RuleFor(x => x.CustomerName)
.NotEmpty()
.WithMessage(Phrases.CustomerNameRequired)
.MaximumLength(200)
.WithMessage(Phrases.CustomerNameTooLong);
RuleFor(x => x.Total)
.GreaterThan(0)
.WithMessage(Phrases.InvalidTotal);
RuleFor(x => x.Items)
.NotEmpty()
.WithMessage(Phrases.ItemsRequired);
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(x => x.ProductId)
.NotEmpty()
.WithMessage(Phrases.ProductIdRequired);
item.RuleFor(x => x.Quantity)
.GreaterThan(0)
.WithMessage(Phrases.InvalidQuantity);
item.RuleFor(x => x.UnitPrice)
.GreaterThan(0)
.WithMessage(Phrases.InvalidUnitPrice);
});
}
}
namespace {Company}.{Domain}.Grpc.Validators;
public sealed class UpdateOrderRequestValidator
: AbstractValidator<UpdateOrderRequest>
{
public UpdateOrderRequestValidator()
{
RuleFor(x => x.OrderId)
.NotEmpty()
.Must(BeValidGuid)
.WithMessage(Phrases.InvalidOrderId);
// Conditional validation
When(x => x.CustomerName is not null, () =>
{
RuleFor(x => x.CustomerName.Value)
.NotEmpty()
.MaximumLength(200);
});
}
private static bool BeValidGuid(string value)
=> Guid.TryParse(value, out _);
}
// Phone number validation (Libyan format)
public sealed class PhoneValidator : AbstractValidator<string>
{
public PhoneValidator()
{
RuleFor(x => x)
.Matches(@"^0(91|92|93|94|95)\d{7}$")
.WithMessage(Phrases.InvalidPhoneNumber);
}
}
namespace {Company}.{Domain}.Grpc;
public static class ValidationRegistration
{
public static IServiceCollection AddAppValidators(
this IServiceCollection services)
{
// Calzolari gRPC validation integration
services.AddGrpcValidation();
// Scan assembly for all validators
services.AddValidatorsFromAssemblyContaining<CreateOrderRequestValidator>();
return services;
}
}
// In Program.cs
builder.Services.AddGrpc(options =>
{
options.EnableMessageValidation();
});
builder.Services.AddAppValidators();
When validation fails, Calzolari returns:
StatusCode: InvalidArgument
Detail: "Validation failed"
Trailers:
- validation-errors-text: JSON array of validation failures
[
{ "propertyName": "CustomerName", "errorMessage": "Customer name is required" },
{ "propertyName": "Total", "errorMessage": "Total must be greater than 0" }
]
// For cases needing manual validation control
public sealed class OrderCommandsService(
IMediator mediator,
IValidator<CreateOrderRequest> validator)
: OrderCommands.OrderCommandsBase
{
public override async Task<CreateOrderResponse> CreateOrder(
CreateOrderRequest request, ServerCallContext context)
{
var validationResult = await validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors
.Select(e => $"{e.PropertyName}: {e.ErrorMessage}");
throw new RpcException(new Status(
StatusCode.InvalidArgument,
string.Join("; ", errors)));
}
var command = request.ToCommand();
var output = await mediator.Send(command);
return output.ToCreateResponse();
}
}
| Anti-Pattern | Correct Approach |
|---|---|
| Hardcoded error messages | Use resource strings (Phrases.xxx) |
| Validation in handler instead of validator | Validate at gRPC boundary |
| Missing validator for a request type | Every request needs a validator |
| Not using ChildRules for repeated | Use RuleForEach with ChildRules |
| Catching validation exceptions | Let Calzolari handle the response |
# Find validators
grep -r "AbstractValidator<.*Request" --include="*.cs" src/
# Find Calzolari registration
grep -r "AddGrpcValidation\|EnableMessageValidation" --include="*.cs" src/
# Find AddAppValidators
grep -r "AddAppValidators\|AddValidatorsFrom" --include="*.cs" src/
# Find resource strings in validators
grep -r "Phrases\." --include="*.cs" src/Grpc/Validators/
Grpc/Validators/ directory matching request namePhrases resource for error messagesAddAppValidators() is called (scans assembly automatically)