基于 SOC 职业分类
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/rudironsoni/Synaxis --skill dotnet-validation-patterns命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
AI-powered wiki generation for code repositories with commands, agents, and skills
Routes .NET/C# work to domain skills. Loads coding-standards for code paths.
Skill manifest management for dotnet-agent-harness. Tracks skill dependencies, conflicts, version compatibility, and provides validation and resolution tools. Triggers on: skill manifest, dependency resolution, skill compatibility, version conflicts, build manifest, validate dependencies.
| name | dotnet-validation-patterns |
| description | Validates models and IOptions. DataAnnotations, IValidatableObject, IValidateOptions<T>. |
| allowed-tools | ["Read","Grep","Glob","Bash","Write","Edit"] |
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.
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.
Choose the validation approach based on complexity:
[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.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.General guidance: start with DataAnnotations. Add IValidatableObject when cross-property rules emerge. Introduce
FluentValidation only when rules outgrow declarative attributes.
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.
using System.ComponentModel.DataAnnotations;
public sealed class CreateProductRequest
{
[Required(ErrorMessage = "Product name is required")]
[StringLength(200, MinimumLength = 1)]
public required string Name { get; set; }
[Range(0.01, 1_000_000, ErrorMessage = "Price must be between {1} and {2}")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]{2,4}-\d{4,8}$",
ErrorMessage = "SKU format: AA-0000 to AAAA-00000000")]
public string? Sku { get; set; }
[EmailAddress]
public string? ContactEmail { get; set; }
[Url]
public string? WebsiteUrl { get; set; }
[Range(0, int.MaxValue, ErrorMessage = "Quantity cannot be negative")]
public int 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-
Primary approach: Use Serena symbol operations for efficient code navigation:
serena_find_symbol instead of text searchserena_get_symbols_overview for file organizationserena_find_referencing_symbols for impact analysisserena_replace_symbol_body for clean modificationsWhen to use Serena vs traditional tools:
Example workflow:
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"