RuleFlow

Lightweight, developer-friendly rule engine for .NET

View the Project on GitHub rubenrichtering/RuleFlow

RuleFlow supports user-defined, structured conditions that are stored as data (JSON, database, UI) and evaluated safely at runtime—no C# expression parsing, no dynamic compilation, no external scripting libraries.

Playground: DynamicConditionsScenario.cs.

Overview

A ConditionNode is either:

Types live in RuleFlow.Abstractions and serialize with System.Text.Json. Polymorphic JSON uses a kind discriminator: leaf or group (see examples below).

Execution uses IConditionEvaluator<T> (ConditionEvaluator<T> in RuleFlow.Core), which combines:

Persisted rules use RuleDefinition.Condition; RuleDefinitionMapper<T> maps that tree to .When((input, ctx) => evaluator.Evaluate(input, node, ctx)). See Persistence.

ConditionLeaf

Member Purpose
field Property path on T (see nested paths below).
operator Operator name (e.g. equals, greater_than).
value Literal right-hand side (omit when using compareToField).
compareToField Other property path for field-to-field comparison (do not set a literal value at the same time).

ConditionGroup

Member Purpose
operator AND or OR (case-insensitive).
conditions Child ConditionNode list (non-empty).

Supported operators

Name Meaning
equals Equality (with basic numeric/string normalization).
greater_than Left > right.
less_than Left < right.
between Left within inclusive [min, max]; value is a two-element array.
in Left is in value (array of allowed values).

Nested property paths

ReflectionFieldResolver<T> treats field and compareToField as dot-separated paths. It walks the object graph with simple reflection and caches PropertyInfo per (declaring type, segment).

Examples:

Null handling: if any intermediate object is null, the resolved value is null (no exception). Unknown property names on a type throw FieldResolutionException with the full path.

Example (JSON)

Logical AND over a nested field and a top-level field. Note kind on each node (matches System.Text.Json polymorphism in code):

{
  "kind": "group",
  "operator": "AND",
  "conditions": [
    {
      "kind": "leaf",
      "field": "Customer.Name",
      "operator": "equals",
      "value": "John"
    },
    {
      "kind": "leaf",
      "field": "Amount",
      "operator": "greater_than",
      "value": 100
    }
  ]
}

Field-to-field comparison:

{
  "kind": "leaf",
  "field": "Amount",
  "operator": "greater_than",
  "compareToField": "MaxOrderValue"
}

Validation

ConditionValidator.Validate ensures leaves and groups are well-formed before execution (required fields, AND/OR only, no conflicting value + compareToField).