Skip to main content

Getting Started with Vali-Flow.Core

What Is Vali-Flow.Core?

Vali-Flow.Core is a dependency-free .NET library for building Expression<Func<T, bool>> expression trees through a fluent API. It allows you to compose validation and filtering rules in a readable, type-safe way — without writing raw LINQ expression code.

Key properties:

  • Zero NuGet dependencies.
  • Targets net8.0 and net9.0.
  • Produces standard Expression<Func<T, bool>> trees consumable by LINQ, Entity Framework Core, and any other LINQ provider.
  • Supports in-memory validation with detailed error reporting.
loading...

Examples: ValiFlow · Evaluators


ValiFlow<T> vs ValiFlowQuery<T>

ValiFlow<T>ValiFlowQuery<T>
Use caseIn-memory objects and EF CoreEF Core queries only
In-memory validation (IsValid, Validate, BuildCached)YesNo
EF Core translatableMost methodsAll methods
Methods like IsInEnum, IsDefaultYes (in-memory only)No
Regex / Contains with StringComparisonYes (in-memory only)No

Use ValiFlow<T> when you need both in-memory validation and expression-tree output. Use ValiFlowQuery<T> when you are building queries exclusively for an EF Core DbSet<T> and need guaranteed SQL translatability.


Installation

dotnet add package Vali-Flow.Core

Or via the NuGet Package Manager:

Install-Package Vali-Flow.Core

Quick Start

using ValiFlow.Builder;

var rule = new ValiFlow<Order>()
.NotNull(o => o.CustomerId)
.GreaterThan(o => o.Total, 0m)
.IsNotNullOrEmpty(o => o.Reference);

// In-memory: returns true if the object satisfies all conditions
bool valid = rule.IsValid(order);

// Get a compiled delegate (result is cached after first compilation)
Func<Order, bool> predicate = rule.BuildCached();

// Get the raw expression tree (for EF Core / LINQ providers)
Expression<Func<Order, bool>> expr = rule.Build();

// Detailed validation with error reporting
var result = rule.Validate(order);
if (!result.IsValid)
Console.WriteLine(result.ErrorMessage);

More Examples

1) Conditional rules with When

var rule = new ValiFlow<User>()
.NotNull(x => x.Email)
.When(x => x.IsActive,
b => b.IsNotNullOrEmpty(x => x.Phone));

2) Nested rules for collections

var rule = new ValiFlow<Order>()
.NotNull(o => o.Items)
.ForEach(o => o.Items, item => item
.GreaterThan(x => x.Quantity, 0)
.IsNotNullOrEmpty(x => x.Sku));

Fluent Chaining

Conditions are chained with And() (the default) or Or().

// AND is implicit — no call needed
var rule = new ValiFlow<Product>()
.NotNull(p => p.Name)
.GreaterThan(p => p.Price, 0m)
.LessThanOrEqualTo(p => p.Stock, 10_000);

// OR must be explicit
var rule = new ValiFlow<User>()
.EqualTo(u => u.Role, "Admin")
.Or()
.EqualTo(u => u.Role, "Manager");

Operator precedence follows standard logic: AND binds more tightly than OR. Groups of AND-connected conditions separated by OR are evaluated as (A && B) || (C && D).


WithMessage and WithError

Attach a human-readable message or a structured error object to any condition:

var rule = new ValiFlow<Order>()
.NotNull(o => o.CustomerId)
.WithMessage("Customer ID is required.")
.GreaterThan(o => o.Total, 0m)
.WithMessage("Order total must be greater than zero.")
.IsNotNullOrEmpty(o => o.Reference)
.WithError(new ValidationError("REF_001", "Reference cannot be empty."));

Validate() Result Structure

Validate(T instance) returns a ValidationResult with:

MemberTypeDescription
IsValidbooltrue if all conditions pass
ErrorMessagestring?Message from the first failing condition, or null
Errorobject?Structured error from WithError(...), or null
var result = rule.Validate(order);

if (!result.IsValid)
{
// Use the message
Console.WriteLine(result.ErrorMessage);

// Or cast the structured error
if (result.Error is ValidationError err)
logger.LogError("[{Code}] {Message}", err.Code, err.Message);
}

When / Unless — Conditional Conditions

Apply a condition only when a predicate is satisfied (When) or not satisfied (Unless):

var rule = new ValiFlow<Order>()
.NotNull(o => o.CustomerId)
// Only validate ShippingAddress when the order requires shipping
.IsNotNullOrEmpty(o => o.ShippingAddress)
.When(o => o.RequiresShipping)
// Only validate DiscountCode when the order does NOT have a fixed price
.IsNotNullOrEmpty(o => o.DiscountCode)
.Unless(o => o.HasFixedPrice);

AddSubGroup — Grouped Logic

AddSubGroup lets you nest a self-contained block of conditions as a single logical unit. This is equivalent to wrapping conditions in parentheses:

// Equivalent to: (Status == "Active" || Status == "Pending") && Total > 0
var rule = new ValiFlow<Order>()
.AddSubGroup(g => g
.EqualTo(o => o.Status, "Active")
.Or()
.EqualTo(o => o.Status, "Pending"))
.GreaterThan(o => o.Total, 0m);

Sub-groups compose with the outer builder using And or Or just like any other condition.


Next steps