RuleSharp - C# Rule Engine/Rule Runner Library






4.79/5 (18 votes)
A library that evaluates C# conditional string expressions dynamically and returns a Boolean result
Introduction
There are scenarios where it is useful to dynamically execute a C# string
expression. For example, suppose you are building a rule engine. You feed some inputs into the engine. The engine retrieves a predefined set of rules stored somewhere such as a database. It then compares your input to the set of rules and returns a Boolean result indicating whether or not the input passes the rule, based on which you execute some logic in your application.
Sometimes, you may run into situations where you find it difficult to break down all your rules into a table-like configuration to be stored in a database, etc., because the condition to be evaluated might be quite arbitrary. You might wish if only you could store a portion of your rule as a C# expression in the database and evaluate it at runtime, and based on its result, execute the rest of the application logic. This library, RuleSharp
, enables you to do exactly the same thing.
Background
C# is a statically typed, compiled language. For this reason, dynamic execution of source code is not possible. Recently, Microsoft introduced a new concept called compiler as a service, which they named Roslyn. Roslyn is available only in .NET Framework 4.6 and above, but any applications targeting an earlier version of the framework cannot take advantage of Roslyn. Luckily, there is a project called Mono.CSharp, which you can use with earlier versions of .NET Framework, which offers compiler as a service. Mono.CSharp is part of the Mono project. Mono.CSharp is available on Nuget here. If you want to learn more about the Mono itself, visit this link.
RuleSharp
is a wrapper library I wrote around Mono.CSharp. The library is especially useful when you build a rule engine, where you want to execute at runtime a piece of uncompiled C# code stored somewhere such as a database. All you have to do is feed an input object (a custom class) and a C# expression as string
into the library. The expression represents a rule, which can be either a single-line C# statement or multiple statements. The library will try to execute the code upon the input object and return a true
or false
. True
indicates that the rule is passed.
Using the Code
The source code of the solution is attached. The solution contains a class library called RuleSharp.RuleEngine
, which is the library itself. The library has a single class named RuleRunner
. RuleRunner
class hosts the following methods:
bool IsStatementTrue<TInput>(string statement, TInput input);
bool IsBlockTrue<TInput>(string block, TInput input);
The difference between them is that the IsStatementTrue
method expects a single-line C# expression, whereas the IsBlockTrue
method can take multiple statements followed by a return;
statement. Both methods take a custom object as the second input parameter.
To use the library in your application, add a reference to the library and create an instance of the RuleRunner
class. The following example demonstrates how the methods can be used. Please refer to the TestConsoleApp
in the same solution for a complete example.
var runner = new RuleRunner();
var statementResult = runner.IsStatementTrue("input.Id == 1", employee);
var blockResult = runner.IsBlockTrue(@"if(input.Designation.Id == 1)" +
@"return true;" +
@"else " +
@"return false;", employee);
In the above example, we pass in an input object (employee
) into the methods. We also pass a string
expression (also called a rule or statement). The word "input
" in the expression represents the input object (in our case, the employee
object). What the methods do is evaluate the expression using the employee
object as the data source and return a Boolean result indicating the rule/statement success/failure.
Caveats and Points of Interest
- When the rule is examined, the library internally looks for the word "
input
" as the alias for the input object. So, it is mandatory that you use the word "input
" in thestring
expression to represent your input object. In the above example, "input
" represents theemployee
object. If I had written the rule as "employee.Id == 1
", it would have failed to parse the rule. - You should pass in a POCO object. Arrays, enumerables, enums, types are not supported as the outer object. This is because the parser is unable to correctly figure out the type of such objects. If you want to pass an array, enumerable, etc., wrap them in a custom class and pass in.
- If you use any types defined in a third assembly other than the calling or called ones, you must tell the library to add references to such assemblies. To do this, the
RuleRunner
constructor supports adding assembly references and namespace references. Again, look at the example solution attached along with this article for reference.
History
- 6th December, 2016: Initial version