RoyalCode.SmartValidations
1.0.0-preview-4.0
dotnet add package RoyalCode.SmartValidations --version 1.0.0-preview-4.0
NuGet\Install-Package RoyalCode.SmartValidations -Version 1.0.0-preview-4.0
<PackageReference Include="RoyalCode.SmartValidations" Version="1.0.0-preview-4.0" />
<PackageVersion Include="RoyalCode.SmartValidations" Version="1.0.0-preview-4.0" />
<PackageReference Include="RoyalCode.SmartValidations" />
paket add RoyalCode.SmartValidations --version 1.0.0-preview-4.0
#r "nuget: RoyalCode.SmartValidations, 1.0.0-preview-4.0"
#:package RoyalCode.SmartValidations@1.0.0-preview-4.0
#addin nuget:?package=RoyalCode.SmartValidations&version=1.0.0-preview-4.0&prerelease
#tool nuget:?package=RoyalCode.SmartValidations&version=1.0.0-preview-4.0&prerelease
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
Problemsyou 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
CallerArgumentExpressionand generic math (INumber<T>). - Depends on SmartProblems for
ProblemandProblems.
Installation
- Add SmartValidations and SmartProblems (NuGet when available).
Core concepts
Rules.Set()/Rules.Set<T>(): creates aRuleSetfor applying rules.RuleSet: fluent DSL that accumulatesProblemswhenever a rule fails.IValidableandValidateFunc: plug-in points for nested validations.- Implicit conversion: a
RuleSetcan be treated asProblems?or queried viaHasProblems(out var problems). - Conditional rules:
WhenandUnlesslet 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)
.GreaterThan(TotalAmount, 0)
.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);
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)
.LessThan(StartDate, EndDate)
.GreaterThanOrEqual(Quantity, 1);
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)
.GreaterThan(item.Quantity, 0)
.GreaterThanOrEqual(item.Price, 0))
.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>()
.GreaterThanOrEqual(Amount, 0)
.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).
SmartProblems integration
- Every failing rule adds a
ProblemviaProblems.InvalidParameter(...)with metadata like:rule(Rules.RuleProperty): the rule name (e.g.,min,max,lessThan).current(Rules.CurrentValueProperty): the current value.expected(Rules.ExpectedValueProperty): expected value(s), when applicable.pattern(Rules.PatternProperty): regex used inMatches/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/ValidateFuncto validate aggregates and nested objects. - Prefer message templates from
Rfor localization consistency. - Use explicit
StringComparisonfor string rules. - Avoid throwing for validation flow�return
Problemsand let callers decide.
License
- See repository license.
| Product | Versions 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. |
-
net10.0
- RoyalCode.SmartProblems (>= 1.0.0-preview-6.0)
-
net8.0
- RoyalCode.SmartProblems (>= 1.0.0-preview-6.0)
-
net9.0
- RoyalCode.SmartProblems (>= 1.0.0-preview-6.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on RoyalCode.SmartValidations:
| Package | Downloads |
|---|---|
|
RoyalCode.SmartCommands
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-preview-4.0 | 19 | 1/12/2026 |
| 1.0.0-preview-3.0 | 37 | 1/6/2026 |
| 1.0.0-preview-2.2 | 103 | 10/3/2025 |
| 1.0.0-preview-2.0 | 516 | 7/18/2025 |
| 1.0.0-preview-1.0 | 165 | 5/6/2025 |
| 1.0.0-preview-0.2 | 133 | 8/12/2024 |
| 1.0.0-preview-0.1 | 75 | 7/31/2024 |