Saturday, January 28, 2006

Microsoft RuleEngine - implementing IRuleSetTrackingInterceptor

When designing applications that utilize Microsoft RuleEngine as a standalone application it would often be nice to be able to track conditions, rules firing, etc. This can be done from within the Microsoft Business Rule Composer which ships with the ruleengine. If your rules are evaluating .NET objects then you have to supply instances of the objects you want the rules to evaluate. Supplying instances of object is not as easy as instances of XML schemas (XML documents) since your objects needed to test the policy have to implement the interface IFactCreator. You are however not anywhere nearer an integrated tracing feature in your application than before.

The interface IRuleSetTrackingInterceptor comes to the rescue here with flying colors.


// Sample code calling a policy with an object
Policy policy = new Policy("SomePolicy");
IRuleSetTrackingInterceptor interceptor = new TextRuleSetTrackingInterceptor();

// create the array of short-term facts
object[] shortTermFacts = new object[1];
shortTermFacts[0] = objSomeFact;

policy.Execute(shortTermFacts, interceptor);


Note that the only difference from a normal call to policy.Execute is the overloaded method which takes an IRuleSetTrackingInterceptor.

Here is a implementation of IRuleSetTrackingInterceptor which in a rather simple way tracks and keeps the trace information in an ArrayList. It is not beautiful but it works.

public class TextRuleSetTrackingInterceptor : IRuleSetTrackingInterceptor
{
    private ArrayList lines;

    public TextRuleSetTrackingInterceptor()
    {
        lines = new ArrayList();
    }

    public void SetTrackingConfig(TrackingConfiguration trackingConfig)
    {
    }

    public void TrackRuleSetEngineAssociation(RuleSetInfo ruleSetInfo, Guid ruleEngineGuid)
    {
    }

    public void TrackFactActivity(FactActivityType activityType, string classType, int classInstanceId)
    {
        lines.Add("------------------- Fact Activity ----------------------");
        lines.Add(activityType.ToString() + ": " + classType);
        lines.Add("");
    }

    public void TrackConditionEvaluation(string testExpression, string leftClassType, int leftClassInstanceId, object leftValue, string rightClassType, int rightClassInstanceId, object rightValue, bool result)
    {
        string[] expressionParts = testExpression.Split(' ');
        string condition = expressionParts[1];
        lines.Add("------------------- Condition Evaluation ---------------");
        lines.Add(testExpression + " : " + result.ToString());
        lines.Add(leftValue.ToString() + " " + condition + " " + rightValue.ToString() + " : " + result.ToString());
        lines.Add("");
    }

    public void TrackAgendaUpdate(bool isAddition, string ruleName, object conflictResolutionCriteria)
    {
        lines.Add("------------------- Agenda Updates ---------------------");
        if (isAddition)
        {
            lines.Add("Add: " + ruleName);
        }
        else
        {
            lines.Add("Remove: " + ruleName);
        }
        lines.Add("");
    }

    public void TrackRuleFiring(string ruleName, object conflictResolutionCriteria)
    {
        lines.Add("------------------- Rule Firing ------------------------");
        lines.Add("Rulename: " + ruleName);
        lines.Add("");
    }

    public ArrayList Trace
    {
        get { return lines; }
    }
}


The interceptor can then be used to output the trace information information to a textbox on a form.