Imported from Avanade/CoreEx (
.github/skills/coreex-validator/SKILL.md). Install upstream withnpx skills add Avanade/CoreEx --skill coreex-validator. Copyright stays with the author.
CoreEx: Validator
Guides you through creating or modifying a CoreEx validator (Application/Validators/) for a contract or request type. Covers declarative rules, ref-data validation, async database checks, collection and dictionary validators.
When to Use
- New validator for a contract or request type (no database calls needed)
- New validator that requires a repository or other Application-layer dependency
- Adding property rules or an async check to an existing validator
- Nested validator for a sub-property (
.Entity()), a collection, or a dictionary - Switching to FluentValidation-compatible
AbstractValidatorsyntax
When Not to Use
- Domain invariants (aggregates, entities, value objects) — those belong in the Domain layer
- Infrastructure-level data checks that are not accessed via an Application-layer interface
FluentValidationNuGet package —AbstractValidatorhere isCoreEx.Validation.AbstractValidator
Quick Reference
Clarifying questions to ask before writing any code:
- Does the validator need a constructor-injected dependency (e.g. a repository)? (determines base class)
- Which properties need validation? (list upfront — batch type resolution, don't interrupt per-property)
- Are any ref-data properties required? Optional? (
.Mandatory().IsValid()vs.IsValid()) - Are any checks async (e.g. confirming an entity exists in the database)?
- Does the user prefer FluentValidation-style
RuleFor(x => ...)syntax?
Base class decision:
| Situation | Base class | Notes |
|---|---|---|
| No injection needed | Validator<T, TSelf> |
Exposes Default singleton; invoke via .Default.ValidateAndThrowAsync(...) |
| Constructor injection needed | Validator<T> |
No Default; instantiate at the call site with already-injected dependencies: new {Name}Validator(_dep).ValidateAndThrowAsync(...) |
| FluentValidation-style preferred | AbstractValidator<T, TSelf> |
RuleFor(x => ...) / NotEmpty() / IsValid() — still CoreEx; has Default |
Key rules at a glance:
- Ref-data:
.IsValid()on the navigation property (Gender), never on*Codestring Mandatory()on a non-nullable value type errors ondefault(0,MinValue) — use a range rule if0is valid- Runtime-computed thresholds: use delegate overloads (
.LessThanOrEqualTo(_ => ...)) — prefer overOnValidateAsyncimperative logic context.HasErrorsguard before async I/O;context.HasError(x => x.Prop)for per-property guardscontext.AddError(x => x.Prop, ...)— member-access expression, nevernameof(...)- Message text argument is a
{2}suffix substitution — not a full sentence; use.Error("...")to override the whole message - Always generate or update the matching
{Validator}Testsin*.Test.Unit/Validators/— cover conditional/business rules and cross-field logic thoroughly, plus a representative error/success pair for simple rules; skip dedicated boundary tests for framework-guaranteed constraints like length rules (MaximumLength/MinimumLength/Length/String)
For full workflow, rule reference, and code examples see references/workflow.md.
Key References
/.github/instructions/coreex-validators.instructions.md— full rule set, comparison operators, localization, DependsOn, DI registration/.github/instructions/coreex-tests.instructions.md— validator unit test conventions:Test.Scoped,AssertErrors, expected message text- Related skills:
coreex-app-service(invokes the validator),coreex-policy(async I/O guard sibling for checks a validator cannot do),coreex-contract(the type being validated) - Illustrative examples (CoreEx sample — not present in your project):
ProductValidator+MovementRequestValidator— simple, plus injection + dictionary + async- Shopping validators —
AbstractValidatorstyle, plain request validators OrderValidator— collection validator