SYSK 358: Run Rules Automatically When an Event Is Triggered

Imagine this: you’re writing a custom user control and you’d like to execute a WWF ruleset when a certain event happens, e.g. a data context changes. Of course, you could create a delegate for the event and write a few lines of code to validate and execute a desired rule… But what if you had to do it for several events? Yes, putting reusable code into a function would help, but I think there is a better way – through attributes.

With the helper class I created, your code would look as simple as this:

[Rule("DataContextChanged", "YourNamespace.YourControl.rules", "YourRuleNameHere")]

public partial class YourControl : RulesBasedUserControl

{

      . . .

}

Whenever the DataContextChanged event is fired, the rule specified by the third parameter in the RuleAttribute will be automatically executed.

Note: the example above was written using WPF and the rule file embedded as a resource; however, you can easily adapt the code to better suite your needs.

As you can see, the control class inherits from RulesBasedUserControl rather than System.Windows.Controls.UserControl.

All the “magic” is in the RulesBasedUserControl class, which is listed below.

public class RulesBasedUserControl : System.Windows.Controls.UserControl

{

    public RulesBasedUserControl()

    {

        // Get all rules

        RuleAttribute[] rules = this.GetType().GetCustomAttributes(typeof(RuleAttribute), true) as RuleAttribute[];

        if (rules != null && rules.Length > 0)

        {

            // Set up the rules to fire as needed

            foreach (RuleAttribute rule in rules)

            {

                EventInfo info = this.GetType().GetEvent(rule.EventName);

                if (info != null)

                {

                    MethodInfo mi = info.EventHandlerType.GetMethod("Invoke");

                    List<Type> parameters = new List<Type>();

                    foreach (ParameterInfo p in mi.GetParameters())

                    {

                        parameters.Add(p.ParameterType);

                    }

                    DynamicMethod m = new DynamicMethod(info.Name + "Handler", typeof(void), parameters.ToArray(), this.GetType(), false);

        ILGenerator generator = m.GetILGenerator();

                    generator.Emit(OpCodes.Ldarg_0);

                    generator.Emit(OpCodes.Ldstr, rule.RuleResourceFile);

                    generator.Emit(OpCodes.Ldstr, rule.RuleSetName);

                                          

                    generator.EmitCall(OpCodes.Call,

                        typeof(RulesBasedUserControl).GetMethod("ExecuteRule", new Type[] { typeof(string), typeof(string) }),

                        null);

                    generator.Emit(OpCodes.Ret);

                    info.AddEventHandler(this, m.CreateDelegate(info.EventHandlerType));

                }

                else

                {

                    System.Diagnostics.Debug.WriteLine("Non-existing event");

                    // TODO: add logging, exception throwing, etc.

                }

            }

        }

    }

   

    public RuleSet DeserializeRuleSet(string resourceName, string ruleSetName)

    {

        RuleSet result = null;

        using (Stream data = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))

        {

            using (XmlTextReader reader = new XmlTextReader(data, new NameTable()))

            {

                RuleDefinitions defs = (new WorkflowMarkupSerializer()).Deserialize(reader) as RuleDefinitions;

                result = defs.RuleSets[ruleSetName];

            }

        }

        return result;

    }

    public void ExecuteRule(string resourceName, string ruleSetName)

    {

        try

        {

            RuleSet rules = DeserializeRuleSet(resourceName, ruleSetName);

            if (rules != null)

         {

                RuleValidation ruleValidation = new RuleValidation(this.GetType(), null);

                if ((null != ruleValidation) && (!rules.Validate(ruleValidation)))

                {

                    foreach (System.Workflow.ComponentModel.Compiler.ValidationError validationError in ruleValidation.Errors)

                    {

                        // TODO: add handling

                        System.Diagnostics.Debug.WriteLine(validationError.ErrorText);

                    }

            }

                else

                {

                    rules.Execute(new RuleExecution(ruleValidation, this));

                }

            }

        }

        catch (Exception ex)

        {

            System.Diagnostics.Debug.WriteLine(ex.Message);

            // TODO: add handling and logging

        }

    }

}

[AttributeUsage(AttributeTargets.Class, Inherited = true)]

public sealed class RuleAttribute : Attribute

{

    private string eventName;

    private string ruleResourceFile;

    private string ruleSetName;

    public RuleAttribute(string eventName, string ruleResourceFile, string ruleSetName)

    {

        this.eventName = eventName;

        this.ruleResourceFile = ruleResourceFile;

        this.ruleSetName = ruleSetName;

    }

    public string EventName

    {

        get { return this.eventName; }

    }

    public string RuleResourceFile

    {

        get { return this.ruleResourceFile; }

    }

    public string RuleSetName

    {

        get { return this.ruleSetName; }

    }

}