Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Validates models and IOptions. DataAnnotations, IValidatableObject, IValidateOptions<T>.
allowed-tools
["Read","Grep","Glob","Bash","Write","Edit"]
dotnet-validation-patterns
Built-in .NET validation patterns that do not require third-party packages. Covers DataAnnotations attributes,
IValidatableObject for cross-property validation, IValidateOptions<T> for options validation at startup, custom
ValidationAttribute authoring, and Validator.TryValidateObject for manual validation. Prefer these built-in
mechanisms as the default; reserve FluentValidation for complex domain rules that outgrow declarative attributes.
Scope
DataAnnotations attributes and Validator.TryValidateObject
IValidatableObject for cross-property validation
IValidateOptions for options validation at startup
Custom ValidationAttribute authoring
Out of scope
API pipeline integration (endpoint filters, ProblemDetails, AddValidation) -- see [skill:dotnet-input-validation]
Options pattern binding and ValidateOnStart registration -- see [skill:dotnet-csharp-configuration]
Architectural placement of validation in layers -- see [skill:dotnet-architecture-patterns]
Cross-references: [skill:dotnet-input-validation] for API pipeline validation and FluentValidation,
[skill:dotnet-csharp-configuration] for Options pattern binding and ValidateOnStart(),
[skill:dotnet-architecture-patterns] for validation placement in architecture layers,
[skill:dotnet-csharp-coding-standards] for naming conventions.
Validation Approach Decision Tree
Choose the validation approach based on complexity:
DataAnnotations (default) -- declarative [Required], [Range], [StringLength], [RegularExpression]
attributes. Best for: simple property-level constraints on DTOs, request models, and options classes.
IValidatableObject -- implement Validate() for cross-property rules within the same object. Best for: date
range comparisons, conditional required fields, business rules that span multiple properties.
Custom ValidationAttribute -- subclass ValidationAttribute for reusable property-level rules. Best for:
domain-specific constraints (SKU format, postal code, currency code) applied across multiple models.
IValidateOptions<T> -- validate configuration/options classes at startup with access to DI services. Best for:
cross-property options checks, environment-dependent validation, fail-fast startup.
FluentValidation -- third-party library for complex, testable validation with fluent API. Best for: async
validators, database-dependent rules, deeply nested object graphs. See [skill:dotnet-input-validation] for
FluentValidation patterns.
General guidance: start with DataAnnotations. Add IValidatableObject when cross-property rules emerge. Introduce
FluentValidation only when rules outgrow declarative attributes.
DataAnnotations
The System.ComponentModel.DataAnnotations namespace provides declarative validation through attributes. These
attributes work with MVC model binding, Validator.TryValidateObject, and the .NET 10 source-generated validation
pipeline.
Standard Attributes
using System.ComponentModel.DataAnnotations;
publicsealedclassCreateProductRequest
{
[Required(ErrorMessage = "Product name is required")]
[StringLength(200, MinimumLength = 1)]
publicrequiredstring Name { get; set; }
[Range(0.01, 1_000_000, ErrorMessage = "Price must be between {1} and {2}")]
publicdecimal Price { get; set; }
[RegularExpression(@"^[A-Z]{2,4}-\d{4,8}$",
ErrorMessage = "SKU format: AA-0000 to AAAA-00000000")]
publicstring? Sku { get; set; }
[EmailAddress]
publicstring? ContactEmail { get; set; }
[Url]
publicstring? WebsiteUrl { get; set; }
[Range(0, int.MaxValue, ErrorMessage = "Quantity cannot be negative")]
publicint Quantity { get; set; }
}
```text
### Attribute Reference
| Attribute | Purpose | Example |
|-----------|---------|---------|
| `[Required]` | Non-null, non-empty | `[Required]` |
| `[StringLength]` | Min/max length | `[StringLength(200, MinimumLength = 1)]` |
| `[Range]` | Numeric/date range | `[Range(, )]` |
| `[RegularExpression]` | Pattern match | `[RegularExpression()]` |
| `[EmailAddress]` | Email format | `[EmailAddress]` |
| `[Phone]` | Phone format | `[Phone]` |
| `[Url]` | URL format | `[Url]` |
| `[CreditCard]` | Luhn check | `[CreditCard]` |
| `[Compare]` | Property equality | `[Compare((Password))]` |
| `[MaxLength]` / `[MinLength]` | Collection/ length | `[MaxLength()]` |
| `[AllowedValues]` (.NET +) | Value allowlist | `[AllowedValues(, )]` |
| `[DeniedValues]` (.NET +) | Value denylist | `[DeniedValues(, )]` |
| `[Length]` (.NET +) | Min max one | `[Length(, )]` |
| `[Base64String]` (.NET +) | Base64 format | `[Base64String]` |
---
Create reusable validation attributes domain-specific rules.
```csharp
[]
:
{
ValidationResult? IsValid(
? , ValidationContext validationContext)
{
( DateOnly date && date <= DateOnly.FromDateTime(DateTime.UtcNow))
{
ValidationResult(
ErrorMessage ?? ,
[]);
}
ValidationResult.Success;
}
}
{
[]
[]
Title { ; ; }
[]
DateOnly EventDate { ; ; }
}
```text
Apply validation across the entire multiple properties are involved:
```csharp
[]
:
{
StartProperty { ; ; } = ;
EndProperty { ; ; } = ;
ValidationResult? IsValid(
? , ValidationContext validationContext)
{
( ) ValidationResult.Success;
type = .GetType();
startValue = type.GetProperty(StartProperty)?.GetValue();
endValue = type.GetProperty(EndProperty)?.GetValue();
(startValue DateOnly start && endValue DateOnly end && end < start)
{
ValidationResult(
ErrorMessage ?? ,
[]);
}
ValidationResult.Success;
}
}
[]
{
[]
DateOnly StartDate { ; ; }
[]
DateOnly EndDate { ; ; }
}
```text
---
Implement `IValidatableObject` cross-property validation within the model itself.
{
[]
[]
CustomerId { ; ; }
[]
DateOnly OrderDate { ; ; }
DateOnly? ShipByDate { ; ; }
[]
[]
List<OrderLineItem> Lines { ; ; }
{
(ShipByDate.HasValue && ShipByDate.Value <= OrderDate)
{
;
}
(Lines.Sum(l => l.Quantity * l.UnitPrice) > _000_000)
{
;
}
(Lines.Any(l => l.RequiresShipping) && ShipByDate )
{
;
}
}
}
{
[]
ProductId { ; ; }
[]
Quantity { ; ; }
[]
UnitPrice { ; ; }
RequiresShipping { ; ; }
}
```text
**When to use `IValidatableObject` vs custom attribute:** Use `IValidatableObject` the validation logic specific to one model involves multiple properties. Use a custom `ValidationAttribute`
{
SectionName = ;
ConnectionString { ; ; } = ;
MaxRetryCount { ; ; } = ;
CommandTimeoutSeconds { ; ; } = ;
MaxPoolSize { ; ; } = ;
MinPoolSize { ; ; } = ;
}
: <>
{
{
failures = List<>();
(.IsNullOrWhiteSpace(options.ConnectionString))
{
failures.Add();
}
(options.
{
failures.Add();
}
(options.CommandTimeoutSeconds < )
{
failures.Add();
}
(options.MinPoolSize > options.MaxPoolSize)
{
failures.Add(
+
);
}
failures.Count >
? ValidateOptionsResult.Fail(failures)
: ValidateOptionsResult.Success;
}
}
```text
```csharp
builder.Services
.AddOptions<DatabaseOptions>()
.BindConfiguration(DatabaseOptions.SectionName)
.ValidateOnStart();
builder.Services.AddSingleton<
IValidateOptions<DatabaseOptions>, DatabaseOptionsValidator>();
```text
Use DataAnnotations simple property constraints `IValidateOptions<T>` cross-property environment-dependent logic:
```csharp
{
SectionName = ;
[]
Host { ; ; } = ;
[]
Port { ; ; } = ;
[]
FromAddress { ; ; } = ;
UseSsl { ; ; } = ;
}
: <>
{
{
(options.UseSsl && options.Port == )
{
ValidateOptionsResult.Fail(
);
}
ValidateOptionsResult.Success;
}
}
builder.Services
.AddOptions<SmtpOptions>()
.BindConfiguration(SmtpOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
builder.Services.AddSingleton<
IValidateOptions<SmtpOptions>, SmtpOptionsValidator>();
```text
---
Run DataAnnotations validation programmatically outside the MVC/Minimal API pipeline. Useful validating objects background services, console apps, domain logic.
```csharp
{
{
results = List<ValidationResult>();
context = ValidationContext(instance);
isValid = Validator.TryValidateObject(
instance, context, results, validateAllProperties: );
(isValid, results);
}
}
{
{
(!stoppingToken.IsCancellationRequested)
{
order = ReadNextOrderFromQueue(stoppingToken);
(isValid, errors) = ValidationHelper.Validate(order);
(!isValid)
{
logger.LogWarning(
,
.Join(, errors.Select(e => e.ErrorMessage)));
;
}
scope = scopeFactory.CreateScope();
db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Orders.Add(order);
db.SaveChangesAsync(stoppingToken);
}
}
=>
NotImplementedException();
}
```text
**Critical:** Without `validateAllProperties: `, `Validator.TryValidateObject` only checks `[Required]` attributes, silently skipping `[Range]`, `[StringLength]`, `[RegularExpression]`, all other attributes.
---
`Validator.TryValidateObject` does recurse nested objects collections . Implement recursive validation models contain nested complex types:
```csharp
{
{
visited = HashSet<>(ReferenceEqualityComparer.Instance);
ValidateRecursive(instance, results, visited, prefix: );
}
{
(!visited.Add(instance))
;
context = ValidationContext(instance);
isValid = Validator.TryValidateObject(
instance, context, results, validateAllProperties: );
( property instance.GetType().GetProperties())
{
(IsSimpleType(property.PropertyType))
;
= property.GetValue(instance);
( ) ;
memberPrefix = .IsNullOrEmpty(prefix)
? property.Name
: ;
( IEnumerable<> collection)
{
index = ;
( item collection)
{
itemResults = List<ValidationResult>();
(!ValidateRecursive(
item, itemResults, visited,
))
{
isValid = ;
( result itemResults)
{
results.Add( ValidationResult(
result.ErrorMessage,
result.MemberNames.Select(
m => ).ToArray()));
}
}
index++;
}
}
(property.PropertyType.IsClass)
{
nestedResults = List<ValidationResult>();
(!ValidateRecursive(, nestedResults, visited, memberPrefix))
{
isValid = ;
( result nestedResults)
{
results.Add( ValidationResult(
result.ErrorMessage,
result.MemberNames.Select(
m => ).ToArray()));
}
}
}
}
isValid;
}
=>
type.IsPrimitive
|| type.IsEnum
|| type == ()
|| type == ()
|| type == (DateTime)
|| type == (DateTimeOffset)
|| type == (DateOnly)
|| type == (TimeOnly)
|| type == (TimeSpan)
|| type == (Guid)
|| (Nullable.GetUnderlyingType(type) { } underlying
&& IsSimpleType(underlying));
}
```text
**Note:** This implementation tracks visited objects via `HashSet<>` `ReferenceEqualityComparer` to safely handle circular reference graphs without stack overflow.
---
**Always pass `validateAllProperties: `** to `Validator.TryValidateObject`. Without it, only `[Required]` checked; `[Range]`, `[StringLength]`, custom attributes are silently skipped.
**Options classes must use `{ ; ; }` `{ ; ; }`** because the configuration binder `PostConfigure` need to mutate properties after construction. Use `[Required]` mandatory fields instead of ``.
**`IValidatableObject.Validate()` runs only after all attribute validations pass.** This requires MVC model binding `Validator.TryValidateObject` `validateAllProperties: `. If attribute validation fails, `Validate()` never called. Do rely it primary validation.
**Do inject services `ValidationAttribute` via constructor.** Attributes are instantiated the runtime cannot participate DI. Use `validationContext.GetService<T>()` inside `IsValid()` service access needed, but prefer `IValidateOptions<T>` DI-dependent validation.
**Do use `[RegularExpression]` without `[GeneratedRegex]` awareness.** The attribute internally creates `Regex` instances. For performance-critical paths, validate `[GeneratedRegex]` a custom attribute `IValidatableObject` instead. See [skill:dotnet-input-validation] ReDoS prevention.
**Register `IValidateOptions<T>` singleton.** The options validation infrastructure resolves validators singletons. Registering transient causes resolution failures.
**Do forget `ValidateOnStart()`.** Without it, options validation only runs first access to `IOptions<T>.Value`, which may be minutes the application lifecycle. Always chain `.ValidateOnStart()` fail-fast behavior.
---
- .NET + (LTS baseline `[AllowedValues]`, `[DeniedValues]`, `[Length]`, `[Base64String]`)
- `System.ComponentModel.DataAnnotations` (included .NET SDK, no extra package)
- `Microsoft.Extensions.Options` (included ASP.NET Core shared framework, no extra package)
- .NET `[ValidatableType]` source-
Code Navigation (Serena MCP)
Primary approach: Use Serena symbol operations for efficient code navigation:
Find definitions: serena_find_symbol instead of text search
Understand structure: serena_get_symbols_overview for file organization
Track references: serena_find_referencing_symbols for impact analysis
Precise edits: serena_replace_symbol_body for clean modifications
When to use Serena vs traditional tools:
Use Serena: Navigation, refactoring, dependency analysis, precise edits
Use Read/Grep: Reading full files, pattern matching, simple text operations
Fallback: If Serena unavailable, traditional tools work fine
This interface runs after all individual attribute validations pass (whenusing MVC model binding or `Validator.TryValidateObject` with `validateAllProperties: true`).
```csharp
publicsealedclass CreateOrderRequest : IValidatableObject
Required
StringLength(50)
public
required
string
get
set
Required
public
get
set
public
get
set
Required
MinLength(1, ErrorMessage = "At least one line item is required")
public
required
get
set
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
if
yieldreturnnewValidationResult("Ship-by date must be after order date",
[nameof(ShipByDate)])
if
1
yieldreturnnewValidationResult("Total order value cannot exceed 1,000,000",
[nameof(Lines)])
// Conditional required field
if
is
null
yieldreturnnewValidationResult("Ship-by date is required when order contains shippable items",
[nameof(ShipByDate)])
public
sealed
class
OrderLineItem
Required
public
required
string
get
set
Range(1, 10_000)
public
int
get
set
Range(0.01, 100_000)
public
decimal
get
set
public
bool
get
set
when
is
and
when the same rule applies across multiple models (reusable).
---
## IValidateOptions<T>
Use `IValidateOptions<T>` for complex validation of options/configuration classes at startup. Unlike DataAnnotations, thisinterface supports cross-property checks, DI-injected dependencies, and programmatic logic. See [skill:dotnet-csharp-configuration] for Options pattern binding and `ValidateOnStart()` registration.
### Basic IValidateOptions
```csharp
publicsealedclass DatabaseOptions
public
const
string
"Database"
public
string
get
set
""
public
int
get
set
3
public
int
get
set
30
public
int
get
set
100
public
int
get
set
0
public
sealed
class
DatabaseOptionsValidator
IValidateOptions
DatabaseOptions
public ValidateOptionsResult Validate(string? name, DatabaseOptions options)