<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged rulengine</title>
<link>https://coderlegion.com/tag/rulengine</link>
<description>Powered by Question2Answer</description>
<item>
<title>Building a Database-Driven Rules Engine for C# Applications</title>
<link>https://coderlegion.com/26002/building-a-database-driven-rules-engine-for-c-applications</link>
<description>&lt;h2 id=&quot;stop-hardcoding-business-rules-building-a-dynamic-workflow-engine-in-c-sharp&quot;&gt;Stop Hardcoding Business Rules: Building a Dynamic Workflow Engine in C Sharp&lt;/h2&gt;
&lt;p&gt;Every growing C# application eventually hits a wall: business logic becomes a tangle of deeply nested &lt;code&gt;if/else&lt;/code&gt; checks scattered across service layers. Every time marketing tweaks a promotion or compliance updates an age restriction, developers have to modify compiled C#, run regression tests, and push a new deployment.&lt;/p&gt;
&lt;p&gt;What if your code handled the execution flow while your database controlled the rules?&lt;/p&gt;
&lt;p&gt;By combining the &lt;strong&gt;Chain of Responsibility pattern&lt;/strong&gt;, &lt;strong&gt;Keyed Dependency Injection&lt;/strong&gt;, and &lt;strong&gt;Microsoft’s open-source `RulesEngine&lt;/strong&gt;`, you can build a flexible pipeline that evaluates dynamic business rules on the fly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-architecture-code-vs-configuration&quot;&gt;The Architecture: Code vs. Configuration&lt;/h3&gt;
&lt;p&gt;To keep systems maintainable, separate &lt;strong&gt;procedural execution&lt;/strong&gt; (what your system &lt;em&gt;does&lt;/em&gt;) from &lt;strong&gt;business constraints&lt;/strong&gt; (what your system &lt;em&gt;allows&lt;/em&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ Incoming Request ]
         │
         ▼
[ Dynamic Workflow Engine ]
         │
         ├── 1. Fetch Steps &amp;amp; JSON Rules from Database
         │
         ├── 2. Evaluate Dynamic Rules (Microsoft.RulesEngine)
         │        ├── Passed ──► Proceed
         │        └── Failed ──► Abort &amp;amp; Return Reason
         │
         └── 3. Execute Step Handler (via Keyed DI)

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Chain of Responsibility:&lt;/strong&gt; Keeps each processing step (Inventory, Payment, Shipping) modular and isolated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft RulesEngine:&lt;/strong&gt; Parses readable expression strings (like &lt;code&gt;Amount &amp;gt; 5000 &amp;amp;&amp;amp; CreditScore &amp;lt; 700&lt;/code&gt;) stored as JSON in your database and evaluates them against your C# objects at runtime.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-defining-the-workflow-context&quot;&gt;1. Defining the Workflow Context&lt;/h3&gt;
&lt;p&gt;First, create a state object passed down the execution chain. This context carries order details and tracks pipeline status.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class OrderContext
{
    public string OrderId { get; set; } = string.Empty;
    public decimal Amount { get; set; }
    public int CustomerAge { get; set; }
    public int CreditScore { get; set; }

    public bool IsAborted { get; private set; }
    public string AbortReason { get; private set; } = string.Empty;

    public void Abort(string reason)
    {
        IsAborted = true;
        AbortReason = reason;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-setting-up-dynamic-rules-in-json&quot;&gt;2. Setting Up Dynamic Rules in JSON&lt;/h3&gt;
&lt;p&gt;Instead of writing hardcoded conditions in C#, store rules as JSON objects—either in SQL tables, a NoSQL store, or configuration files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  {
    &quot;WorkflowName&quot;: &quot;PaymentRules&quot;,
    &quot;Rules&quot;: [
      {
        &quot;RuleName&quot;: &quot;HighValueCreditCheck&quot;,
        &quot;ErrorMessage&quot;: &quot;Orders over $5,000 require a Credit Score of at least 700.&quot;,
        &quot;Expression&quot;: &quot;Amount &amp;lt;= 5000 || CreditScore &amp;gt;= 700&quot;
      }
    ]
  }
]

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-building-the-pipeline-engine&quot;&gt;3. Building the Pipeline Engine&lt;/h3&gt;
&lt;p&gt;Using .NET 8/9 Keyed Services, handlers are resolved dynamically using string keys fetched from your database schema.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public interface IWorkflowHandler
{
    Task HandleAsync(OrderContext context, Func&amp;lt;Task&amp;gt; next);
}

public class DynamicWorkflowEngine
{
    private readonly IServiceProvider _serviceProvider;

    public DynamicWorkflowEngine(IServiceProvider serviceProvider) =&amp;gt; _serviceProvider = serviceProvider;

    public async Task ExecuteAsync(OrderContext context, List&amp;lt;DbWorkflowStep&amp;gt; steps)
    {
        foreach (var step in steps)
        {
            if (context.IsAborted) break;

            // Step 1: Evaluate DB-stored Rules
            if (!string.IsNullOrWhiteSpace(step.StepRulesJson))
            {
                var workflowList = JsonConvert.DeserializeObject&amp;lt;List&amp;lt;WorkflowRules&amp;gt;&amp;gt;(step.StepRulesJson);
                var re = new RulesEngine.RulesEngine(workflowList.ToArray());
                
                var results = await re.ExecuteAllRulesAsync(workflowList[0].WorkflowName, context);
                
                if (results.Any(r =&amp;gt; !r.IsSuccess))
                {
                    var failedRule = results.First(r =&amp;gt; !r.IsSuccess);
                    context.Abort($&quot;Rule failed at {step.HandlerKey}: {failedRule.Rule.ErrorMessage}&quot;);
                    break;
                }
            }

            // Step 2: Execute Handler from DI
            var handler = _serviceProvider.GetKeyedService&amp;lt;IWorkflowHandler&amp;gt;(step.HandlerKey);
            if (handler != null)
            {
                await handler.HandleAsync(context, () =&amp;gt; Task.CompletedTask);
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-pattern-wins&quot;&gt;Why This Pattern Wins&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero-Downtime Rule Updates:&lt;/strong&gt; Non-developers or admin dashboards can edit business expressions in the database. Updated rules apply instantly without recompiling C#.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditable Failure Reasons:&lt;/strong&gt; &lt;code&gt;RulesEngine&lt;/code&gt; explicitly reports &lt;em&gt;which&lt;/em&gt; rule failed and &lt;em&gt;why&lt;/em&gt;, giving clear feedback to users and log aggregators.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expression Safety:&lt;/strong&gt; Unlike executing raw string scripts, &lt;code&gt;RulesEngine&lt;/code&gt; safely compiles expressions into Abstract Syntax Trees (ASTs), avoiding code injection vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;production-tip-performance&quot;&gt;Production Tip: Performance&lt;/h3&gt;
&lt;p&gt;Evaluating JSON rules introduces lightweight parsing overhead. In production, wrap DB calls and &lt;code&gt;RulesEngine&lt;/code&gt; instances in an &lt;strong&gt;&lt;code&gt;IMemoryCache&lt;/code&gt;&lt;/strong&gt;. Invalidate the cache only when an administrator updates a rule in your database.&lt;/p&gt;
&lt;p&gt;By shifting fast-changing business rules into dynamic configuration, your codebase stays lean, maintainable, and resilient to change.&lt;/p&gt;
&lt;p&gt;Source code : &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/DynamicWorkflowConsole&quot;&gt;https://github.com/stevsharp/DynamicWorkflowConsole&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26002/building-a-database-driven-rules-engine-for-c-applications</guid>
<pubDate>Tue, 01 Sep 2026 14:45:37 +0000</pubDate>
</item>
</channel>
</rss>