بنقرة واحدة
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)