diff --git a/.github/workflows/uniffi.yaml b/.github/workflows/uniffi.yaml
index b4193941..8052c534 100644
--- a/.github/workflows/uniffi.yaml
+++ b/.github/workflows/uniffi.yaml
@@ -137,6 +137,82 @@ jobs:
run: ./gradlew publishMavenKotlinPublicationToCentralPortal publishMavenJavaPublicationToCentralPortal
+ release-csharp:
+ runs-on: ubuntu-latest
+ needs: [ build ]
+ permissions:
+ id-token: write # Required for OIDC
+ if: "startsWith(github.event.head_commit.message, 'chore(release): publish uniffi')"
+ defaults:
+ run:
+ working-directory: ${{ env.UNIFFI_DIRECTORY }}
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.0.x'
+
+ - name: Setup Rust Toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: build/generated/resources
+
+ - name: Move artifacts
+ run: mv ../../build ./
+
+ - name: Install uniffi-bindgen-cs
+ run: cargo install uniffi-bindgen-cs --git https://github.com/bcrevar-gorules/uniffi-bindgen-cs --branch fix/async-callback-interface-return-type
+
+ - name: Generate C# bindings
+ run: |
+ uniffi-bindgen-cs \
+ --library build/generated/resources/darwin-x86-64/libzen_uniffi.dylib \
+ --config uniffi.toml \
+ --out-dir build/generated/csharp
+
+ - name: Upload generated C# source
+ uses: actions/upload-artifact@v4
+ with:
+ name: csharp-generated-source
+ path: ${{ env.UNIFFI_DIRECTORY }}/build/generated/csharp/zen_uniffi.cs
+ if-no-files-found: error
+
+ - name: Extract version from Cargo.toml
+ id: version
+ run: |
+ VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
+ echo "Detected version: $VERSION"
+
+ - name: Pack NuGet package
+ run: dotnet pack nuget/GoRules.ZenEngine.csproj -c Release -p:Version=$VERSION
+
+ - name: Upload NuGet package artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package
+ path: ${{ env.UNIFFI_DIRECTORY }}/nuget/bin/Release/*.nupkg
+ if-no-files-found: error
+
+ - name: NuGet login (trusted publishing)
+ uses: NuGet/login@v1
+ id: nuget-login
+ with:
+ user: ${{ secrets.NUGET_USER }}
+
+ - name: Push to NuGet.org
+ run: |
+ dotnet nuget push nuget/bin/Release/*.nupkg \
+ --api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} \
+ --source https://api.nuget.org/v3/index.json \
+ --skip-duplicate
+
build-ios:
if: "!contains(github.event.head_commit.message, 'skip ci')"
env:
diff --git a/bindings/uniffi/.gitignore b/bindings/uniffi/.gitignore
index 6eb8b6a7..dd20902a 100644
--- a/bindings/uniffi/.gitignore
+++ b/bindings/uniffi/.gitignore
@@ -1,3 +1,5 @@
.idea
.gradle
build/
+build-android/
+jniLibs/
\ No newline at end of file
diff --git a/bindings/uniffi/lib/csharp/GoRules/ZenEngine/JsonBuffer.cs b/bindings/uniffi/lib/csharp/GoRules/ZenEngine/JsonBuffer.cs
new file mode 100644
index 00000000..d63fc8d6
--- /dev/null
+++ b/bindings/uniffi/lib/csharp/GoRules/ZenEngine/JsonBuffer.cs
@@ -0,0 +1,23 @@
+namespace GoRules.ZenEngine;
+
+using System.Text;
+
+public class JsonBuffer
+{
+ public byte[] Value { get; }
+
+ public JsonBuffer(byte[] value)
+ {
+ Value = value;
+ }
+
+ public JsonBuffer(string json)
+ {
+ Value = Encoding.UTF8.GetBytes(json);
+ }
+
+ public override string ToString()
+ {
+ return Encoding.UTF8.GetString(Value);
+ }
+}
diff --git a/bindings/uniffi/nuget/GoRules.ZenEngine.csproj b/bindings/uniffi/nuget/GoRules.ZenEngine.csproj
new file mode 100644
index 00000000..9698645c
--- /dev/null
+++ b/bindings/uniffi/nuget/GoRules.ZenEngine.csproj
@@ -0,0 +1,66 @@
+
+
+
+ net8.0
+ GoRules.ZenEngine
+ GoRules
+ GoRules
+ ZEN Engine - Business Rules Engine for .NET. Execute JSON Decision Models (JDM) with native performance via Rust FFI bindings.
+ MIT
+ https://github.com/gorules/zen
+ icon.png
+ https://github.com/gorules/zen
+ rules-engine;business-rules;decision-engine;jdm;gorules
+ enable
+ enable
+ true
+ true
+ $(NoWarn);CS1591
+ README.md
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bindings/uniffi/nuget/README.md b/bindings/uniffi/nuget/README.md
new file mode 100644
index 00000000..10c9a593
--- /dev/null
+++ b/bindings/uniffi/nuget/README.md
@@ -0,0 +1,98 @@
+# GoRules.ZenEngine
+
+Open-source Business Rules Engine for .NET. Execute JSON Decision Models (JDM) with native performance powered by Rust.
+
+## Installation
+
+```bash
+dotnet add package GoRules.ZenEngine
+```
+
+## Quick Start
+
+```csharp
+using GoRules.ZenEngine;
+
+// Create an engine and evaluate
+var engine = new ZenEngine(loader: null, customNode: null);
+var decision = engine.CreateDecision(new JsonBuffer(File.ReadAllBytes("my-decision.json")));
+var context = new JsonBuffer("""{"input": 42}""");
+var response = await decision.Evaluate(context, null);
+Console.WriteLine(response.result);
+
+```
+
+## Features
+
+- **Decision Tables** - Rule tables with first/collect hit policies
+- **Expression Language** - Built-in ZEN expression language with functions like `sum()`, `filter()`, `map()`
+- **Custom Nodes** - Extend the engine with custom node handlers
+- **Tracing** - Full execution trace for debugging and auditing
+- **Cross-platform** - Native libraries for Windows (x64), macOS (x64/ARM), Linux (x64/ARM)
+
+## Tracing
+
+Enable tracing to inspect the execution of each node:
+
+```csharp
+var options = new ZenEvaluateOptions(maxDepth: null, trace: true);
+var decided = await decision.Evaluate(context, options);
+
+foreach (var (nodeId, trace) in decided.trace!)
+{
+ Console.WriteLine($"{trace.name}: {trace.output}");
+}
+```
+
+## Expression Evaluation
+
+Evaluate expressions directly without a decision file:
+
+```csharp
+// One-off evaluation
+var result = ZenUniffiMethods.EvaluateExpression(
+ "sum(items) * multiplier",
+ new JsonBuffer("{\"items\": [10, 20, 30], \"multiplier\": 2}")
+);
+
+// Compiled expression (reusable, better performance)
+var expr = ZenExpression.Compile("a + b * 2");
+var output = expr.Evaluate(new JsonBuffer("{\"a\": 1, \"b\": 10}"));
+Console.WriteLine($"output: {output}");
+expr.Dispose();
+```
+
+## Custom Nodes
+
+Extend the engine with custom logic:
+
+```csharp
+
+using var myEngine = new ZenEngine(loader: new FileLoader(), customNode: new MyCustomNode());
+var myResponse = await myEngine.Evaluate("custom.json", context, options);
+Console.WriteLine(myResponse.result);
+
+// Custom node handler
+class MyCustomNode : ZenCustomNodeCallback
+{
+ public Task Handle(ZenEngineHandlerRequest request) =>
+ Task.FromResult(new ZenEngineHandlerResponse(
+ output: new JsonBuffer("""{"result": "custom"}"""),
+ traceData: null
+ ));
+}
+
+// Implement a loader to resolve decision files
+class FileLoader : ZenDecisionLoaderCallback
+{
+ public Task Load(string key) =>
+ Task.FromResult(new JsonBuffer(File.ReadAllBytes(key)));
+}
+
+```
+
+## Links
+
+- [GitHub Repository](https://github.com/gorules/zen)
+- [GoRules Documentation](https://docs.gorules.io/developers/sdks/csharp)
+- [JDM Editor](https://editor.gorules.io)
diff --git a/bindings/uniffi/nuget/icon.png b/bindings/uniffi/nuget/icon.png
new file mode 100644
index 00000000..b3199c44
Binary files /dev/null and b/bindings/uniffi/nuget/icon.png differ
diff --git a/bindings/uniffi/uniffi.toml b/bindings/uniffi/uniffi.toml
index ed2a9997..c030c133 100644
--- a/bindings/uniffi/uniffi.toml
+++ b/bindings/uniffi/uniffi.toml
@@ -19,7 +19,8 @@ from_custom = "{}"
# C#
[bindings.csharp]
namespace = "GoRules.ZenEngine"
-cdylib_name = "Libs/zen_uniffi"
+cdylib_name = "zen_uniffi"
+access_modifier = "public"
[bindings.csharp.custom_types.JsonBuffer]
into_custom = "new JsonBuffer({})"