SYSK 293: Business Rules – Easy, Flexible, Decoupled
From what I see, in this version, Microsoft Windows Workflow Foundation doesn’t allow you to author and use business rules without a link to an activity or a workflow… Also, the types of rules you can create are somewhat limited… After spending some time trying to fit WF into what I needed, I recalled the simplicity of the Microsoft ScriptControl object that allowed you to run VBScript or JavaScript code snippets, and even allowed to pass in parameters…
I wanted to have a mechanism where I can execute a .NET code snippet that was not previously compiled with the simplicity of the following statement:
ScriptEngine.Evaluate(codeBody, methodToInvoke, methodParameters);
So, I decided to create a class that can compile and run C# code on the fly… Imagine being able to create your business rules in C# (or VB.NET), maintain them in some secure but easy to edit place like database, and execute them when needed…
Here is my code (as always, use at your own risk, etc…):
using System;
using System.Collections.Generic;
using System.Text;
namespace YourCompany.YourProject
{
public class ScriptEngine
{
private static Dictionary<long, CachedCode> _cache = new Dictionary<long, CachedCode>();
public static object Evaluate(string code, string methodName, object[] parameters)
{
object snippetInstance = null;
System.Reflection.MethodInfo method = null;
// Did we already execute this code before?
long hashCode = code.GetHashCode();
if (_cache.ContainsKey(hashCode))
{
snippetInstance = _cache[hashCode].SnippetInstance;
method = _cache[hashCode].Method;
}
else
{
string codeToExecute =
"using System;" +
"public class CodeSnippetWrapper { " +
code +
"}";
Microsoft.CSharp.CSharpCodeProvider cp = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.CompilerParameters p = new System.CodeDom.Compiler.CompilerParameters();
p.ReferencedAssemblies.Add("System.dll");
p.GenerateExecutable = false;
p.CompilerOptions = "/t:library";
p.GenerateInMemory = true;
System.CodeDom.Compiler.CompilerResults cr = cp.CompileAssemblyFromSource(p, new string[] { codeToExecute });
if (cr.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder(64);
foreach(System.CodeDom.Compiler.CompilerError e in cr.Errors)
{
sb.AppendLine(e.ToString());
}
throw new ApplicationException("Errors in rule " + methodName, new object[] { sb.ToString() });
}
System.Reflection.Assembly assembly = cr.CompiledAssembly;
snippetInstance = assembly.CreateInstance("CodeSnippetWrapper");
method = snippetInstance.GetType().GetMethod(methodName);
// Cache it for the future
_cache.Add(hashCode, new CachedCode(snippetInstance, method));
}
return method.Invoke(snippetInstance, parameters);
}
private class CachedCode
{
private object _snippetInstance = null;
private System.Reflection.MethodInfo _method = null;
public CachedCode(object snippetInstance, System.Reflection.MethodInfo method)
{
_snippetInstance = snippetInstance;
_method = method;
}
public object SnippetInstance
{
get { return _snippetInstance; }
}
public System.Reflection.MethodInfo Method
{
get { return _method; }
}
}
}
}