Token导航 LogoToken导航

Agent Skill · 待分类

validation-patterns(验证模式)

validation-patterns 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

validation-patterns 是待分类类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在待分类类的 11,209 个 Skill 里排第 1,285。

总安装 4,039周安装 170GitHub Stars 15下载 1,414核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:validation-patterns(验证模式)
来源仓库:https://github.com/wshaddix/dotnet-skills
仓库路径:skills/validation-patterns
安装命令:
npx skills add https://github.com/wshaddix/dotnet-skills --skill validation-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/wshaddix/dotnet-skills --skill validation-patterns

这个 Skill 是做什么的

这个 Skill 的分类还没最终确定,用法以来源仓库的说明为准。

validation-patterns 处理 GitHub 仓库、Issue、Pull Request 等协作信息,支持代码变更跟踪与整理。适用于围绕项目状态、代码审查或协作事项进行信息梳理的场景。通过 GitHub 安装,使用 npx skills add 命令添加指定仓库的技能模块。

建议确认权限范围和维护状态,避免触发不必要的联网、命令执行或文件读写操作。适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/validation-patterns

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/wshaddix/dotnet-skills --skill validation-patt…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

validation-patterns 怎么安装?

在终端执行 npx skills add https://github.com/wshaddix/dotnet-skills --skill validation-patterns。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

validation-patterns 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

validation-patterns 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

validation-patterns 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

validation-patterns 用的人多吗?

公开数据是累计安装 4,039 次,近一周安装 170 次,下载 1,414 次,GitHub 15 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

validation-patterns 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理待分类类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

待分类类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
infsh-cli

infsh CLI · Codex、Claude

4,374,576382权限未标注未明确未收录
ai-video-generation

AI 视频生成 · Codex、Claude

4,313,376382调用外部服务未明确有提醒
lark-openapi-explorer

云雀 OpenAPI 浏览器 · Codex、Claude

2,064,1209,011需要联网未明确有提醒
lark-workflow-standup-report

Lark 工作流程站立报告 · Codex、Claude

2,041,6328,978需要联网未明确通过
use-my-browser

使用我的浏览器 · Codex、Claude

1,848,49140操作浏览器未明确未通过
validation-patterns

验证模式 · 本页

4,03915需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Validation Patterns in ASP.NET Core

Rationale

Validation is critical for both security and user experience. Poor validation leads to invalid data, security vulnerabilities, and confusing error messages. These patterns provide a comprehensive approach to validation at multiple layers.

Validation Strategy

LayerPurposeTechnology
Client-SideImmediate feedback, reduce server loadjQuery Validation, HTML5
Model BindingData type/format validationModel Binders
ApplicationBusiness rule validationFluentValidation, DataAnnotations
ConfigurationStartup validationIValidateOptions
DatabaseConstraint enforcementEF Core Configurations

Validation Approach Decision Tree

Choose the validation approach based on complexity:

  1. DataAnnotations (default) -- declarative [Required], [Range], [StringLength], [RegularExpression] attributes. Best for simple property-level constraints.
  2. IValidatableObject -- implement Validate() for cross-property rules. Best for date range comparisons, conditional required fields.
  3. Custom ValidationAttribute -- subclass ValidationAttribute for reusable property-level rules.
  4. IValidateOptions<T> -- validate configuration/options classes at startup with access to DI services.
  5. FluentValidation -- third-party library for complex, testable validation with fluent API. Best for async validators, database-dependent rules.

Pattern 1: DataAnnotations

The System.ComponentModel.DataAnnotations namespace provides declarative validation through attributes.

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; }
}

Attribute Reference

AttributePurposeExample
[Required]Non-null, non-empty[Required]
[StringLength]Min/max length[StringLength(200, MinimumLength = 1)]
[Range]Numeric/date range[Range(1, 100)]
[RegularExpression]Pattern match[RegularExpression(@"^\d{5}$")]
[EmailAddress]Email format[EmailAddress]
[Phone]Phone format[Phone]
[Url]URL format[Url]
[CreditCard]Luhn check[CreditCard]
[Compare]Property equality[Compare(nameof(Password))]
[MaxLength] / [MinLength]Collection/string length[MaxLength(50)]
[AllowedValues] (.NET 8+)Value allowlist[AllowedValues("Draft", "Published")]
[DeniedValues] (.NET 8+)Value denylist[DeniedValues("Admin", "Root")]
[Length] (.NET 8+)Min and max in one[Length(1, 200)]
[Base64String] (.NET 8+)Base64 format[Base64String]

Pattern 2: Custom ValidationAttribute

Create reusable validation attributes for domain-specific rules.

Property-Level

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class FutureDateAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        if (value is DateOnly date && date <= DateOnly.FromDateTime(DateTime.UtcNow))
        {
            return new ValidationResult(
                ErrorMessage ?? "Date must be in the future",
                [validationContext.MemberName!]);
        }

        return ValidationResult.Success;
    }
}

public sealed class CreateEventRequest
{
    [Required]
    [StringLength(200)]
    public required string Title { get; set; }

    [FutureDate(ErrorMessage = "Event date must be in the future")]
    public DateOnly EventDate { get; set; }
}

Class-Level

[AttributeUsage(AttributeTargets.Class)]
public sealed class DateRangeAttribute : ValidationAttribute
{
    public string StartProperty { get; set; } = "StartDate";
    public string EndProperty { get; set; } = "EndDate";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        if (value is null) return ValidationResult.Success;

        var type = value.GetType();
        var startValue = type.GetProperty(StartProperty)?.GetValue(value);
        var endValue = type.GetProperty(EndProperty)?.GetValue(value);

        if (startValue is DateOnly start && endValue is DateOnly end && end < start)
        {
            return new ValidationResult(
                ErrorMessage ?? $"{EndProperty} must be after {StartProperty}",
                [EndProperty]);
        }

        return ValidationResult.Success;
    }
}

Pattern 3: IValidatableObject

Implement IValidatableObject for cross-property validation within the model:

public sealed class CreateOrderRequest : IValidatableObject
{
    [Required]
    [StringLength(50)]
    public required string CustomerId { get; set; }

    [Required]
    public DateOnly OrderDate { get; set; }

    public DateOnly? ShipByDate { get; set; }

    [Required]
    [MinLength(1, ErrorMessage = "At least one line item is required")]
    public required List<OrderLineItem> Lines { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (ShipByDate.HasValue && ShipByDate.Value <= OrderDate)
        {
            yield return new ValidationResult(
                "Ship-by date must be after order date",
                [nameof(ShipByDate)]);
        }

        if (Lines.Sum(l => l.Quantity * l.UnitPrice) > 1_000_000)
        {
            yield return new ValidationResult(
                "Total order value cannot exceed 1,000,000",
                [nameof(Lines)]);
        }

        if (Lines.Any(l => l.RequiresShipping) && ShipByDate is null)
        {
            yield return new ValidationResult(
                "Ship-by date is required when order contains shippable items",
                [nameof(ShipByDate)]);
        }
    }
}

When to use IValidatableObject vs custom attribute: Use IValidatableObject when validation logic is specific to one model. Use custom ValidationAttribute when the same rule applies across multiple models.


Pattern 4: IValidateOptions

Validate configuration/options classes at startup with access to DI services:

public sealed class DatabaseOptions
{
    public const string SectionName = "Database";

    public string ConnectionString { get; set; } = "";
    public int MaxRetryCount { get; set; } = 3;
    public int CommandTimeoutSeconds { get; set; } = 30;
    public int MaxPoolSize { get; set; } = 100;
    public int MinPoolSize { get; set; } = 0;
}

public sealed class DatabaseOptionsValidator : IValidateOptions<DatabaseOptions>
{
    public ValidateOptionsResult Validate(string? name, DatabaseOptions options)
    {
        var failures = new List<string>();

        if (string.IsNullOrWhiteSpace(options.ConnectionString))
        {
            failures.Add("Database connection string is required.");
        }

        if (options.MaxRetryCount is < 0 or > 10)
        {
            failures.Add("MaxRetryCount must be between 0 and 10.");
        }

        if (options.MinPoolSize > options.MaxPoolSize)
        {
            failures.Add($"MinPoolSize ({options.MinPoolSize}) cannot exceed MaxPoolSize ({options.MaxPoolSize}).");
        }

        return failures.Count > 0
            ? ValidateOptionsResult.Fail(failures)
            : ValidateOptionsResult.Success;
    }
}

Registration

builder.Services
    .AddOptions<DatabaseOptions>()
    .BindConfiguration(DatabaseOptions.SectionName)
    .ValidateDataAnnotations()
    .ValidateOnStart();

builder.Services.AddSingleton<IValidateOptions<DatabaseOptions>, DatabaseOptionsValidator>();

Pattern 5: FluentValidation Setup

NuGet Packages

<PackageReference Include="FluentValidation" Version="11.9.*" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.*" />

Configuration

builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
builder.Services.AddValidatorsFromAssemblyContaining<Program>();

Basic Validator

public class SignUpRequest
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
    public required string Email { get; set; }
    public required string Username { get; set; }
    public required string Password { get; set; }
    public required string ConfirmPassword { get; set; }
    public required bool AcceptsTos { get; set; }
}

internal sealed class SignUpValidator : AbstractValidator<SignUpRequest>
{
    public SignUpValidator()
    {
        RuleFor(x => x.FirstName)
            .NotEmpty().WithMessage("Please enter a first name")
            .MinimumLength(3).WithMessage("First name must be at least 3 characters long")
            .MaximumLength(50).WithMessage("First name cannot exceed 50 characters")
            .Matches(@"^[a-zA-Z\s'-]+$").WithMessage("First name contains invalid characters");

        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("Please enter an email")
            .EmailAddress().WithMessage("Please enter a valid email address")
            .MustAsync(BeUniqueEmail).WithMessage("An account with this email already exists");

        RuleFor(x => x.Password)
            .NotEmpty().WithMessage("Please enter a password")
            .MinimumLength(8).WithMessage("Password must be at least 8 characters long")
            .Matches(@"[A-Z]").WithMessage("Password must contain at least one uppercase letter")
            .Matches(@"[a-z]").WithMessage("Password must contain at least one lowercase letter")
            .Matches(@"[0-9]").WithMessage("Password must contain at least one number")
            .Matches(@"[^a-zA-Z0-9]").WithMessage("Password must contain at least one special character");

        RuleFor(x => x.ConfirmPassword)
            .Equal(x => x.Password).WithMessage("Passwords do not match");

        RuleFor(x => x.AcceptsTos)
            .Equal(true).WithMessage("You must accept our Terms of Service to sign up");
    }

    private async Task<bool> BeUniqueEmail(string email, CancellationToken cancellationToken)
    {
        return !await _dbContext.Users.AnyAsync(u => u.Email == email, cancellationToken);
    }
}

Conditional Validation

public class OrderValidator : AbstractValidator<OrderRequest>
{
    public OrderValidator()
    {
        RuleFor(x => x.ShippingAddress)
            .NotEmpty()
            .When(x => x.RequiresShipping)
            .WithMessage("Shipping address is required when shipping is needed");

        RuleFor(x => x.PickupLocation)
            .NotEmpty()
            .When(x => !x.RequiresShipping)
            .WithMessage("Pickup location is required for in-store pickup");

        When(x => x.IsExpressShipping, () =>
        {
            RuleFor(x => x.ShippingAddress.Country)
                .Must(BeSupportedCountry)
                .WithMessage("Express shipping is not available for this country");
        });
    }
}

Collection Validation

public class OrderRequestValidator : AbstractValidator<OrderRequest>
{
    public OrderRequestValidator()
    {
        RuleFor(x => x.Items)
            .NotEmpty().WithMessage("Order must contain at least one item")
            .Must(items => items.Count <= 100).WithMessage("Order cannot contain more than 100 items");

        RuleForEach(x => x.Items).ChildRules(item =>
        {
            item.RuleFor(x => x.ProductId)
                .NotEmpty().WithMessage("Product is required");

            item.RuleFor(x => x.Quantity)
                .GreaterThan(0).WithMessage("Quantity must be greater than 0")
                .LessThanOrEqualTo(999).WithMessage("Quantity cannot exceed 999");
        });
    }
}

Pattern 6: MediatR Validation Pipeline Behavior

internal sealed class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        if (!validators.Any())
        {
            return await next(cancellationToken).ConfigureAwait(false);
        }

        var context = new ValidationContext<TRequest>(request);

        var validationResults = await Task.WhenAll(
            validators.Select(v => v.ValidateAsync(context, cancellationToken)))
            .ConfigureAwait(false);

        var failures = validationResults
            .SelectMany(r => r.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count == 0)
        {
            return await next(cancellationToken).ConfigureAwait(false);
        }

        throw new ValidationException(failures);
    }
}

public class ValidationException : Exception
{
    public IReadOnlyList<ValidationFailure> Errors { get; }

    public ValidationException(IEnumerable<ValidationFailure> failures)
        : base("Validation failed")
    {
        Errors = failures.ToList().AsReadOnly();
    }

    public IDictionary<string, string[]> ToDictionary()
    {
        return Errors
            .GroupBy(e => e.PropertyName)
            .ToDictionary(
                g => g.Key,
                g => g.Select(e => e.ErrorMessage).ToArray());
    }
}

Registration

builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<Program>();
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
});

Pattern 7: Manual Validation

Run DataAnnotations validation programmatically:

public static class ValidationHelper
{
    public static (bool IsValid, IReadOnlyList<ValidationResult> Errors) Validate<T>(
        T instance) where T : notnull
    {
        var results = new List<ValidationResult>();
        var context = new ValidationContext(instance);

        bool isValid = Validator.TryValidateObject(
            instance, context, results, validateAllProperties: true);

        return (isValid, results);
    }
}

Critical: Without validateAllProperties: true, Validator.TryValidateObject only checks [Required] attributes.


Pattern 8: Validating File Uploads

public class FileUploadValidator : AbstractValidator<FileUploadRequest>
{
    private readonly string[] _allowedExtensions = [".jpg", ".jpeg", ".png", ".pdf"];
    private const long MaxFileSize = 10 * 1024 * 1024; // 10MB

    public FileUploadValidator()
    {
        RuleFor(x => x.File)
            .NotNull()
            .Must(BeValidSize).WithMessage("File size must not exceed 10MB")
            .Must(BeValidExtension).WithMessage("Invalid file type. Allowed: .jpg, .jpeg, .png, .pdf");

        RuleFor(x => x.Description)
            .MaximumLength(500).When(x => !string.IsNullOrEmpty(x.Description));
    }

    private bool BeValidSize(IFormFile file) => file.Length <= MaxFileSize;

    private bool BeValidExtension(IFormFile file)
    {
        var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
        return _allowedExtensions.Contains(extension);
    }
}

Anti-Patterns

Duplicate Validation

// BAD: Validation in multiple places
if (string.IsNullOrEmpty(model.Email))
    ModelState.AddModelError("Email", "Required");

RuleFor(x => x.Email).NotEmpty(); // Duplicate!

// GOOD: Centralize validation in validators

Silent Validation Failures

// BAD: Ignoring validation results
catch (ValidationException)
{
    return Page();
}

// GOOD: Always add errors to ModelState
catch (ValidationException ex)
{
    foreach (var error in ex.Errors)
    {
        ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
    }
    return Page();
}

Trusting Client-Side Validation

// BAD: Only client-side validation
public IActionResult OnPost(UserInput input)
{
    SaveToDatabase(input);
}

// GOOD: Server-side always validates
public IActionResult OnPost(UserInput input)
{
    if (!ModelState.IsValid)
        return Page();

    SaveToDatabase(input);
}

Agent Gotchas

  1. Always pass validateAllProperties: true to Validator.TryValidateObject.
  2. Options classes must use {get; set;} not {get; init;} -- configuration binder needs to mutate properties.
  3. IValidatableObject.Validate() runs only after all attribute validations pass -- do not rely on it for primary validation.
  4. Do not inject services into ValidationAttribute via constructor -- use validationContext.GetService<T>() inside IsValid().
  5. Register IValidateOptions<T> as singleton -- the options validation infrastructure resolves validators as singletons.
  6. Do not forget ValidateOnStart() -- without it, options validation only runs on first access.

References

平台分布

Codex

36.67%
按下载量换算519

Claude

27.77%
按下载量换算393

Cursor

17.3%
按下载量换算245

Gemini CLI

8.75%
按下载量换算124

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills