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; }

            }

        }

    }

}