Nedo.AspNet.Common.Validation.Dsl
1.9.1
dotnet add package Nedo.AspNet.Common.Validation.Dsl --version 1.9.1
NuGet\Install-Package Nedo.AspNet.Common.Validation.Dsl -Version 1.9.1
<PackageReference Include="Nedo.AspNet.Common.Validation.Dsl" Version="1.9.1" />
<PackageVersion Include="Nedo.AspNet.Common.Validation.Dsl" Version="1.9.1" />
<PackageReference Include="Nedo.AspNet.Common.Validation.Dsl" />
paket add Nedo.AspNet.Common.Validation.Dsl --version 1.9.1
#r "nuget: Nedo.AspNet.Common.Validation.Dsl, 1.9.1"
#:package Nedo.AspNet.Common.Validation.Dsl@1.9.1
#addin nuget:?package=Nedo.AspNet.Common.Validation.Dsl&version=1.9.1
#tool nuget:?package=Nedo.AspNet.Common.Validation.Dsl&version=1.9.1
Nedo.AspNet.Common.Validation.Dsl
A text-based DSL for defining validation rules at runtime — no recompilation required. Built on top of Nedo.AspNet.Common.Validation.Core, the DSL compiles rule text into IValidator<T> instances using System.Linq.Expressions for near-native performance.
Install
dotnet add package Nedo.AspNet.Common.Validation.Dsl
Quick Start
using Nedo.AspNet.Common.Validation.Dsl.Compilation;
var dsl = @"
rule UsernameRequired code ""VAL-001"" {
when true
then require Username else ""Username is required""
}
rule EmailFormat code ""VAL-002"" {
when Email is not null
then assert Email matches ""^[^@\s]+@[^@\s]+\.[^@\s]+$"" else ""Invalid email format""
}
rule AdultCheck code ""VAL-003"" {
when Region == ""US""
then assert Age >= 21 else ""Must be at least 21 in the US""
}";
// Compile DSL into IValidator<T>
var validator = RuleCompiler.CreateValidator<UserDto>(dsl);
// Validate
var result = validator.Validate(user);
if (!result.IsValid)
foreach (var e in result.Errors)
Console.WriteLine($"[{e.Code}] {e.Message}");
Architecture
DSL Text
│
▼
Tokenizer ──▶ Token Stream
│
▼
Parser (recursive-descent) ──▶ AST (Abstract Syntax Tree)
│
▼
RuleCompiler ──▶ System.Linq.Expressions
│
▼
IValidator<T> (compiled, reusable)
- Zero external dependencies — The tokenizer and parser are hand-written (no ANTLR or third-party grammar tools)
- Expression-compiled — Rules are compiled into
Expression<Func<...>>trees, not interpreted at runtime IValidator<T>output — The compiled validator implements the same interface as Core and Fluent validators
DSL Grammar Reference
Rule Structure
rule <Name> code "<ErrorCode>" {
when <Condition>
then <Action> else "<ErrorMessage>"
}
| Part | Description |
|---|---|
Name |
Identifier for the rule (used in debugging/logging) |
code |
Machine-readable error code attached to ValidationError.Code |
when |
Boolean condition — if true, the then action is evaluated |
then |
Validation check to perform |
else |
Error message (literal string or resource key for localization) |
Conditions
Comparison Operators
Age >= 18
Status == "Active"
Score != 0
Price < 1000
Quantity <= 100
Rating > 3.5
| Operator | Description |
|---|---|
== |
Equal |
!= |
Not equal |
> |
Greater than |
>= |
Greater than or equal |
< |
Less than |
<= |
Less than or equal |
Logical Operators
Age >= 18 and Status == "Active"
Role == "Admin" or Role == "Manager"
not (IsDeleted)
| Operator | Description |
|---|---|
and |
Both conditions must be true |
or |
At least one condition must be true |
not |
Inverts the condition |
Null Checks
Email is null
MiddleName is not null
Regex Matching
Email matches "^[^@\s]+@[^@\s]+\.[^@\s]+$"
Phone matches "^\+?[0-9]{10,15}$"
Boolean Fields
IsActive
not IsDeleted
true
false
Arithmetic Expressions
Price * Quantity > 1000
TotalAmount - Discount >= 0
Score / MaxScore > 0.5
| Operator | Description |
|---|---|
+ |
Addition |
- |
Subtraction |
* |
Multiplication |
/ |
Division |
Nested Object Access
Address.City is not null
Address.ZipCode matches "^[0-9]{5}$"
Null-Safe Navigation
Address?.City is not null
Contact?.Phone?.Number is not null
Actions
require — Field Must Not Be Empty
rule NameRequired code "VAL-001" {
when true
then require Name else "Name is required"
}
Fails if the field is null, an empty string, or a whitespace-only string.
assert — Condition Must Be True
rule MinAge code "VAL-002" {
when Country == "US"
then assert Age >= 21 else "Must be 21+ in the US"
}
Fails if the condition evaluates to false.
forbid — Unconditional Failure
rule BlockSuspended code "VAL-003" {
when Status == "Suspended"
then forbid else "Suspended accounts cannot perform this action"
}
Always fails when the when condition is met. Useful for "deny" rules.
cardinality — Collection Count Checks
rule MaxItems code "VAL-004" {
when true
then cardinality OrderItems <= 50 else "Maximum 50 items per order"
}
rule HasActiveItems code "VAL-005" {
when true
then cardinality Items[IsActive] > 0 else "At least one active item required"
}
| Syntax | Description |
|---|---|
cardinality List == N |
Exact count |
cardinality List >= N |
At least N items |
cardinality List <= N |
At most N items |
cardinality List[Filter] > 0 |
Filtered count (where Filter is a boolean field) |
Advanced Examples
Cross-Field Validation
rule PasswordMatch code "VAL-010" {
when Password is not null
then assert Password == ConfirmPassword else "Passwords do not match"
}
rule DateRange code "VAL-011" {
when StartDate is not null and EndDate is not null
then assert EndDate >= StartDate else "End date must be after start date"
}
Conditional Required Fields
rule CompanyRequired code "VAL-020" {
when AccountType == "Business"
then require CompanyName else "Company name is required for business accounts"
}
rule TaxIdRequired code "VAL-021" {
when AccountType == "Business" and Country == "US"
then require TaxId else "Tax ID is required for US business accounts"
}
Nested Object Validation
rule AddressRequired code "VAL-030" {
when ShippingMethod != "Digital"
then require Address.Street else "Street address is required for physical delivery"
}
rule ZipCodeFormat code "VAL-031" {
when Address.Country == "US"
then assert Address.ZipCode matches "^[0-9]{5}(-[0-9]{4})?$"
else "Invalid US zip code format"
}
Arithmetic Validation
rule MaxOrderValue code "VAL-040" {
when true
then assert UnitPrice * Quantity <= 100000
else "Order value cannot exceed 100,000"
}
rule DiscountLimit code "VAL-041" {
when DiscountPercent is not null
then assert DiscountPercent >= 0 and DiscountPercent <= 50
else "Discount must be between 0% and 50%"
}
Localization
Pass a ResourceManager to resolve else strings as resource keys:
using System.Resources;
var validator = RuleCompiler.CreateValidator<UserDto>(
dsl,
MyResources.ResourceManager);
// If MyResources has a key "Username is required" with translations,
// the localized string is returned based on culture
var resultId = validator.Validate(user, "id"); // Indonesian
var resultEn = validator.Validate(user, "en"); // English
How It Works
- The
elsestring (e.g."Username is required") is used as a resource key lookup - If a translation exists for the current culture, the localized message is returned
- If no translation found, the literal
elsestring is used as-is - This allows gradual localization — start with hardcoded strings, add translations later
Composing with Other Validators
The DSL-compiled validator implements IValidator<T>, so it works with CompositeValidator<T>:
using Nedo.AspNet.Common.Validation.Core;
var dslValidator = RuleCompiler.CreateValidator<UserDto>(dsl);
var fluentValidator = new UserValidator(); // AbstractValidator<UserDto>
var composite = new CompositeValidator<UserDto>()
.Add(fluentValidator) // Compile-time rules
.Add(dslValidator) // Runtime-defined rules
.Add((dto, culture) => // Inline rule
RequiredValidator.Validate(dto.Id, "Id", culture ?? "en"));
var result = composite.Validate(user, "id");
Loading Rules from Files or Database
Since DSL rules are plain text, they can be loaded from any source:
// From a file
var dsl = File.ReadAllText("validation-rules.dsl");
var validator = RuleCompiler.CreateValidator<OrderDto>(dsl);
// From database
var rules = await db.ValidationRules
.Where(r => r.EntityType == "Order" && r.IsActive)
.Select(r => r.RuleText)
.ToListAsync();
var combinedDsl = string.Join("\n", rules);
var validator = RuleCompiler.CreateValidator<OrderDto>(combinedDsl);
This enables:
- Admin-configurable rules without code deployment
- Per-tenant validation in multi-tenant applications
- A/B testing different validation rule sets
Related Packages
- Nedo.AspNet.Common.Validation.Core — Core engine, 120+ static validators,
CompositeValidator<T> - Nedo.AspNet.Common.Validation.Fluent — Fluent
AbstractValidator<T>API with chained rules
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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. |
-
net9.0
- Nedo.AspNet.Common.Validation.Core (>= 1.9.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.