RoyalCode.SmartValidations 1.0.0-preview-8.0

This is a prerelease version of RoyalCode.SmartValidations.
dotnet add package RoyalCode.SmartValidations --version 1.0.0-preview-8.0
                    
NuGet\Install-Package RoyalCode.SmartValidations -Version 1.0.0-preview-8.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RoyalCode.SmartValidations" Version="1.0.0-preview-8.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RoyalCode.SmartValidations" Version="1.0.0-preview-8.0" />
                    
Directory.Packages.props
<PackageReference Include="RoyalCode.SmartValidations" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RoyalCode.SmartValidations --version 1.0.0-preview-8.0
                    
#r "nuget: RoyalCode.SmartValidations, 1.0.0-preview-8.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RoyalCode.SmartValidations@1.0.0-preview-8.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RoyalCode.SmartValidations&version=1.0.0-preview-8.0&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=RoyalCode.SmartValidations&version=1.0.0-preview-8.0&prerelease
                    
Install as a Cake Tool

SmartValidations

Fluent, model-first validation for .NET that produces structured Problems instead of exceptions. Built on top of SmartProblems to deliver actionable, localized, and machine-readable validation results.

Why SmartValidations

  • Single-pass model validation: express all rules fluently in one RuleSet.
  • No exceptions for control flow: return Problems you can serialize and show to users.
  • Strongly-typed and fluent: compile-time safety with INumber<T>, CallerArgumentExpression, and generics.
  • First-class nested validation: validate objects and collections, automatically chaining property paths (with indexes for lists).
  • Ready for APIs and UI: consistent error shapes, localization-friendly message templates, and rich metadata.

Targets and requirements

  • .NET 8, .NET 9, .NET 10.
  • C# 12+ using CallerArgumentExpression and generic math (INumber<T>).
  • Depends on SmartProblems for Problem and Problems.

Installation

  • Add SmartValidations and SmartProblems (NuGet when available).

Core concepts

  • Rules.Set() / Rules.Set<T>(): creates a RuleSet for applying rules.
  • RuleSet: fluent DSL that accumulates Problems whenever a rule fails. It is a readonly ref struct, intended for local synchronous validation scopes.
  • IValidable and ValidateFunc: plug-in points for nested validations.
  • Implicit conversion: a RuleSet can be treated as Problems? or queried via HasProblems(out var problems).
  • Conditional rules: When and Unless let you apply rule groups conditionally or as alternatives.

Quick start

using RoyalCode.SmartValidations;
using RoyalCode.SmartProblems;

public class CreateOrderRequest
{
    public string CustomerName { get; set; } = string.Empty;
    public decimal TotalAmount { get; set; }

    public bool HasProblems(out Problems? problems)
    {
        return Rules.Set<CreateOrderRequest>()
            .NotEmpty(CustomerName)
            .Min(TotalAmount, 0m)
            .HasProblems(out problems);
    }
}

String and pattern rules

var set = Rules.Set()
    .NotEmpty(Name)
    .MinLength(Name, 3)
    .MaxLength(Name, 100)
    .OnlyLettersOrDigits(Username)
    .NoWhiteSpace(Username)
    .Matches(Email, @"^.+@.+\..+$", "email pattern")
    .Email(Email)
    .Url(Website)
    .HttpsUrl(WebhookUrl)
    .RelativeUrl(ReturnPath);

if (set.HasProblems(out var problems))
{
    // Serialize problems to your API response
}

Comparisons and ranges

var set = Rules.Set()
    .Equal(Code, "ABC", StringComparison.OrdinalIgnoreCase)
    .NotEqual(Status, "inactive")
    .Min(Age, 18)
    .Max(ItemsCount, 100)
    .MinMax(Score, 0, 100)
    .Positive(Quantity)
    .NotZero(ExternalId)
    .LessThan(StartDate, EndDate)
    .After(EndDate, StartDate)
    .InFuture(ExpiresAt);

Conditional rules (When/Unless)

// Apply rules only when a condition is true
var set = Rules.Set()
    .When(isGuestCheckout,
        s => s.NotEmpty(Email).Email(Email));

// Skip rules when a condition is true
set = set.Unless(hasAddressOnFile,
    s => s.NotEmpty(ShippingAddress.Street)
         .NotEmpty(ShippingAddress.City)
         .NotEmpty(ShippingAddress.ZipCode));

// Alternative groups: add problems from both if both fail
set = set.Unless(
    s => s.NotEmpty(PromoCode),        // condition group
    s => s.Min(TotalAmount, 100));     // alternative group

// Using factories/builders with prefixes preserved/normalized
set = Rules.Set<object>()
    .WithPropertyPrefix("order")
    .Unless(
        () => Rules.Set().WithPropertyPrefix("order").NotEmpty(order.CustomerId),
        s => s.NotEmpty(order.CustomerId)); // Property becomes "CustomerId" (prefix removed)

Custom rules with Must

var set = Rules.Set()
    .Must(Password,
        p => p is { Length: >= 8 } && p.Any(char.IsDigit) && p.Any(char.IsUpper),
        (prop, _) => $"{prop} must contain at least 8 chars, an uppercase letter and a digit.",
        ruleName: "password.policy")
    .BothMust(Start, End,
        (s, e) => s < e,
        (p1, p2, _, _) => $"{p1} must be before {p2}.",
        ruleName: "period.order");

Nested validation (objects)

public class CheckoutRequest : IValidable
{
    public string CustomerId { get; set; } = string.Empty;
    public Address? ShippingAddress { get; set; }
    public Address? BillingAddress { get; set; }

    public bool HasProblems(out Problems? problems)
    {
        return Rules.Set<CheckoutRequest>()
            .NotEmpty(CustomerId)
            .NotNullNested(ShippingAddress, addr => Rules.Set<Address>()
                .WithPropertyPrefix("addr")
                .NotEmpty(addr.Street)
                .NotEmpty(addr.City)
                .NotEmpty(addr.ZipCode)
                .NotEmpty(addr.Country))
            .Nested(BillingAddress, addr => Rules.Set<Address>()
                .WithPropertyPrefix("addr")
                .NotEmpty(addr.Street)
                .NotEmpty(addr.City)
                .NotEmpty(addr.ZipCode)
                .NotEmpty(addr.Country))
            .HasProblems(out problems);
    }
}

Nested validation (collections)

public class Order : IValidable
{
    public List<OrderItem>? Items { get; set; }

    public bool HasProblems(out Problems? problems)
    {
        return Rules.Set<Order>()
            .NotEmpty(Items)
            .Nested(Items, item => Rules.Set<OrderItem>()
                .WithPropertyPrefix("item")
                .NotEmpty(item.ProductId)
                .Positive(item.Quantity)
                .Min(item.Price, 0m))
            .HasProblems(out problems);
    }
}

Validating structs (value objects)

public readonly struct Price : IValidable
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Price(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public bool HasProblems(out Problems? problems)
    {
        return Rules.Set<Price>()
            .Min(Amount, 0m)
            .NotEmpty(Currency)
            .HasProblems(out problems);
    }
}

// Replace property name with the argument name automatically
var prices = new[] { new Price(-1, ""), new Price(10, "USD") };
var set = Rules.Set().Validate((IEnumerable<Price>)prices);

Property names and prefixes

  • Property names are captured by CallerArgumentExpression, so refactors keep error paths accurate.
  • Use WithPropertyPrefix("prefix") to remove a known prefix from nested paths when chaining problems.
  • Collections automatically include an index (e.g., Items[2].Quantity).
  • Use Nested for optional nested objects/collections and NotNullNested when null should be reported as a problem.

SmartProblems integration

  • Every failing rule adds a Problem via Problems.InvalidParameter(...) with metadata like:
    • rule (Rules.RuleProperty): the rule name (e.g., min, max, less-than).
    • current (Rules.CurrentValueProperty): the current value.
    • expected (Rules.ExpectedValueProperty): expected value(s), when applicable.
    • pattern (Rules.PatternProperty): regex used in Matches/NotMatches.
    • For dual-operand rules (Both*, comparisons), properties and values are attached for both operands.
    • Conditional rules (When/Unless) simply control whether rule groups run; metadata remains consistent for each failing rule.

Best practices

  • Centralize validation per request/DTO in a single function that returns Problems?.
  • Favor IValidable/ValidateFunc to validate aggregates and nested objects.
  • Prefer message templates from R for localization consistency.
  • Use explicit StringComparison for string rules.
  • Prefer Min/Max/MinMax and numeric sign rules for fixed limits; use comparison rules for comparing two values.
  • Use HttpsUrl, AbsoluteUrl, RelativeUrl, InPast, InFuture, Today, After, Before and Between directly before falling back to Must.
  • Avoid throwing for validation flow; return Problems and let callers decide.

Documentation

  • See .docs/validations.md for the full API guide.
  • See .docs/validations.ai-rules.md for AI-oriented usage rules.

License

  • See repository license.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on RoyalCode.SmartValidations:

Package Downloads
RoyalCode.SmartCommands

Runtime contracts and attributes for generated command handlers, validation pipelines, persistence access and Minimal API mapping.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-preview-8.0 200 7/19/2026
1.0.0-preview-7.0 281 7/10/2026
1.0.0-preview-4.0 799 1/12/2026
1.0.0-preview-3.0 81 1/6/2026
1.0.0-preview-2.2 140 10/3/2025
1.0.0-preview-2.0 574 7/18/2025
1.0.0-preview-1.0 197 5/6/2025
1.0.0-preview-0.2 169 8/12/2024
1.0.0-preview-0.1 110 7/31/2024