.NET bindings for the Nodora rule engine.
dotnet add package NodoraThe package targets netstandard2.0, so it works on .NET Framework 4.6.1+,
.NET Core, and .NET 5/6/7/8+.
The native engine ships inside the package under runtimes/<rid>/native and is
loaded automatically at run time. Prebuilt for:
| RID | |
|---|---|
linux-x64 |
✅ |
linux-arm64 |
✅ |
osx-x64 |
✅ |
osx-arm64 |
✅ |
win-x64 |
✅ |
Other platforms build from source (needs Go 1.24+ with cgo) — see Development.
using NodoraEngine;
var ruleset = Nodora.Compile(@"
rule AdultCheck {
out is_adult = input.age >= 18
}
");
using var evaluator = ruleset.CreateEvaluator();
var result = evaluator.Evaluate("AdultCheck", new { age = 21 });
Console.WriteLine(result.Outputs["is_adult"].GetBoolean()); // TrueEvaluate takes any object; it is serialized to JSON with
System.Text.Json and becomes the rule's input. Outputs are returned as
JsonElement values you read with the usual accessors
(GetBoolean(), GetInt32(), GetString(), …).
Signals emitted by a rule are returned in the result. You can read the raw list directly:
foreach (var signal in result.EmittedSignals)
{
Console.WriteLine($"{signal.Name}({string.Join(", ", signal.Args)})");
}Or subscribe listeners to specific signals with a SignalDispatcher, so you
don't have to switch over each signal by name:
var signals = new SignalDispatcher();
signals.On("Greet", s => Console.WriteLine($"Greetings, {s.Arg<string>(0)}!"));
signals.OnAny(s => logger.Debug($"signal {s.Name}")); // catch-all
var result = evaluator.Evaluate("AdultCheck", new { age = 21, name = "Alice" });
signals.Dispatch(result);On and OnAny return an IDisposable you can dispose to unsubscribe.
Arg<T>(index) deserializes a positional argument to the type you expect.
Dispatch is synchronous: the decision (rule outputs) is already returned before you dispatch, so keep handlers fast. For a slow or best-effort effect such as sending an email, have the handler hand the work off to a queue or outbox and return, rather than blocking in the handler.
A precompiled ruleset (e.g. the output of nodora compile) can be loaded
without recompiling the source:
var ruleset = Nodora.FromJson(rulesetJson);The evaluator owns a native handle — dispose it (a using statement is the
easiest way) when you are done. A single evaluator can be reused across many
inputs.
| Member | Purpose |
|---|---|
Nodora.Compile(source) |
Compile Nodora source to a ruleset. |
Nodora.FromJson(json) |
Load a precompiled ruleset. |
Ruleset.ToJson() |
Serialize the ruleset back to JSON. |
Ruleset.CreateEvaluator() |
Build a reusable evaluator. |
Evaluator.Evaluate(rule, input) |
Run a rule against any serializable input. |
Evaluator.EvaluateJson(rule, json) |
Run a rule against a raw JSON input. |
EvaluationResult { Outputs, EmittedSignals } |
Named outputs and emitted signals. |
SignalDispatcher |
Route emitted signals to named listeners. |
NodoraException |
Thrown on compile/evaluation errors. |
The managed library builds without the native engine, but running tests or the example requires it. Building the engine needs a Go 1.24+ toolchain with cgo (a working C compiler):
make native # build the native engine into runtimes/<host-rid>/native
make test # build the engine, then run the test suite
make example # run the exampleOn Windows, make native runs build/build-native.ps1; you can also run that
script directly. It requires a MinGW-w64 gcc on PATH for cgo.
To produce the NuGet package locally (with whatever runtimes you have built):
make pack # -> dist/Nodora.<version>.nupkgCI (.github/workflows/build.yml) builds the native engine for every supported
RID and packs them all into a single cross-platform package on tag pushes.
Apache-2.0.