Conditions and Rules


In earlier chapters, we alluded to rules functionality that would let more of the logic of WF programs be expressed declaratively to allow WF program authors to rely on code minimally or not at all. The rules capabilities of WF greatly strengthen the case to be made for fully declarative WF programs, for which markup alone carries sufficient expressive power to capture real-world processes.

The following discussion of rules capabilities in WF (essentially, the types defined in the System.Workflow.Activities.Rules namespace) is predicated upon a basic understanding of CodeDOM because rule conditions and actions are generally specified using System.CodeDom expressions and statements.

A broader discussion of rule-based systems is beyond the scope of this book. Inferencing enginesboth forward-chaining and backward-chaining varietiesand the algorithms that underlie them are an active area of work in academia as well as in industry. Software products that include inferencing engines are in the marketplace today. WF provides simple but powerful ways of melding program logic that is expressed using rules with the activity-oriented approach, which is at the heart of the WF programming model. This section provides a brief but pragmatic exploration of the rules capabilities of WF. We encourage you to consult the .NET Framework documentation and SDKas well as industry literatureto develop your expertise in this important domain.

Conditions

Before we get to WF rules, we must begin with conditions. A condition is an expression that, when evaluated, yields a Boolean value. We employed a condition in Chapter 4 as part of the activity metadata of our While composite activity. The concept of a condition is a fundamental abstraction that you will find useful again and again as an activity developer, wherever you require conditional logic to be expressed by the author of the WF program. Branching, looping, conditional transitions, and conditional (early) completion are a few examples of how conditions can be utilized by activities.

System.Workflow.ComponentModel.ActivityCondition is the base type for conditions that are associated with activities. ActivityCondition is an abstract class with a single method, Evaluate, as shown in Listing 8.1. The first parameter of the Evaluate method is the activity that is associated with the condition; the second parameter is a System.IServiceProvider that can be used by derivatives of ActivityCondition to obtain services required to complete the evaluation of the condition.

Listing 8.1. ActivityCondition

 namespace System.Workflow.ComponentModel {   public abstract class ActivityCondition : DependencyObject   {     public abstract bool Evaluate(Activity activity,       IServiceProvider provider);   } } 


As an activity developer, when you require a condition property for an activity you are developing, you can define a property of type ActivityCondition. Listing 8.2 defines a new composite activity called Conditional, with such a property.

Listing 8.2. Conditional Activity

 using System; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Compiler; namespace EssentialWF.Activities {   public class Conditional : CompositeActivity   {     public static readonly DependencyProperty ConditionProperty =       DependencyProperty.Register(         "Condition",         typeof(ActivityCondition),         typeof(Conditional),         new PropertyMetadata(           DependencyPropertyOptions.Metadata,           new Attribute[] { new ValidationOptionAttribute(             ValidationOption.Required) }         )       );     public ActivityCondition Condition     {       get { return GetValue(ConditionProperty)               as ActivityCondition; }       set { SetValue(ConditionProperty, value); }     }     ...   } } 


With this implementation of the Condition property, users of the Conditional composite activity are free to utilize whatever derivative of ActivityCondition is most appropriate for their WF program; meanwhile, the Conditional activity is shielded from the details of this choice because the activity execution logic (of Conditional) will only need to invoke the condition's Evaluate method.

WF includes two condition types that derive from ActivityCondition: System.Workflow.Activities.CodeCondition and System.Workflow.Activities.Rules.RuleConditionReference. We will look at these two classes next. Of course, you are also free to develop a custom condition type; so long as your custom condition type derives from ActivityCondition, WF program authors will be able to use it interchangeably with the two condition types that WF provides.

Code Conditions

The purpose of CodeCondition is to allow a WF program author to express conditional logic in terms of C# or VB.NET code compiled into a delegate. CodeCondition is shown in Listing 8.3.

Listing 8.3. CodeCondition

 namespace System.Workflow.Activities {   public class CodeCondition : ActivityCondition   {     public CodeCondition();     public event EventHandler<ConditionalEventArgs> Condition;     public override bool Evaluate(Activity activity,       IServiceProvider provider);     /* *** other members *** */   } } 


The CodeCondition.Condition event is raised during the execution of the Evaluate method. The result of condition evaluation (the return value of Evaluate) will be the value of the Result property of the System.Workflow.Activities.ConditionalEventArgs (see Listing 8.4) at the conclusion of the event handler invocation.

Listing 8.4. ConditionalEventArgs

 namespace System.Workflow.Activities {   public sealed class ConditionalEventArgs : System.EventArgs   {     public ConditionalEventArgs();     public bool Result { get; set; }   } } 


A CodeCondition is expressed in XAML as shown in Listing 8.5 (using the Conditional activity we defined previously as the context for the example).

Listing 8.5. Condition Expressed in Terms of Imperative Code Using x:Code

 <Conditional x:Name="c1" x: xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow">   <Conditional.Condition>     <wf:CodeCondition Condition="LoopCondition" />   </Conditional.Condition>   <x:Code>     <![CDATA[         void LoopCondition(object sender, ConditionalEventArgs e)         {           e.Result = true;         }     ]]>   </x:Code> </Conditional> 


In the preceding simple example, the condition always evaluates to TRue, but in general your code can execute whatever logic (and whatever examination of WF program instance state) is required to determine the outcome of condition evaluation.

The XAML program in Listing 8.5 uses the x:Code feature of XAML in order to directly embed code within a XAML document. This is entirely equivalent to (in other words, the result of compilation is the same as) separating the code into a separate file that defines a partial type, just as we have shown in previous examples. For this reason, use of x:Code requires use of x:Class.

There is a validator component associated with the CodeCondition type, which ensures that the Condition event is always handled. The event handler subscription must be established at design-time and therefore is part of the metadata of the WF program.

Declarative Conditions

The second derivative of ActivityCondition provided by WF is System.Workflow.Activities.Rules.RuleConditionReference. The purpose of RuleConditionReference is to allow a WF program author to express conditional logic without writing any imperative code. RuleConditionReference is shown in Listing 8.6.

Listing 8.6. RuleConditionReference

 namespace System.Workflow.Activities.Rules {   public class RuleConditionReference : ActivityCondition   {     public RuleConditionReference();     public string ConditionName { get; set; }     public override bool Evaluate(Activity activity,       IServiceProvider provider);     /* *** other members *** */   } } 


RuleConditionReference is just a simple level of indirection. Its ConditionName property names a System.Workflow.Activities.Rules.RuleCondition that is found within an associated System.Workflow.Activities.Rules.RuleDefinitions object, which acts as a container for all of the declarative conditions and rules associated with a WF program. This is depicted in Figure 8.1.

Figure 8.1. A RuleConditionReference points to a RuleCondition.


The RuleDefinitions type is shown in Listing 8.7. The Conditions property of RuleDefinitions is where we find the actual condition named by a RuleConditionReference; we will discuss the RuleSets property of RuleDefinitions in the next section.

Listing 8.7. RuleDefinitions

 namespace System.Workflow.Activities.Rules { public sealed class RuleDefinitions {   public RuleDefinitions();   public RuleConditionCollection Conditions { get; }   public RuleSetCollection RuleSets { get; } /**** other members *** */ } 


A RuleConditionReference is expressed in XAML like this (using the Conditional activity we defined previously):

 <Conditional>   <Conditional.Condition>   <wf:RuleConditionReference ConditionName="LoopCondition"/>   </Conditional.Condition>   ... </Conditional> 


The RuleDefinitions data for the WF program (which, when authoring a WF program, is specified in a separate file that by convention has an extension of .rules) will contain the actual condition definition, as shown in Listing 8.8.

Listing 8.8. CodeDOM Conditional Expressions Serialized to XAML

 <RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow" xmlns:ns0="clr-name- space:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKey- Token=b77a5c561934e089" xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">   <RuleDefinitions.Conditions>     <RuleExpressionCondition Name="LoopCondition">       <RuleExpressionCondition.Expression>         <ns0:CodePrimitiveExpression>           <ns0:CodePrimitiveExpression.Value>             <ns1:Boolean>true</ns1:Boolean>           </ns0:CodePrimitiveExpression.Value>         </ns0:CodePrimitiveExpression>       </RuleExpressionCondition.Expression>     </RuleExpressionCondition>   </RuleDefinitions.Conditions> </RuleDefinitions> 


The RuleDefinitions.Conditions property is a collection of objects of type System.Workflow.Activities.Rules.RuleCondition. RuleCondition is the base class for declarative condition implementations. WF provides a single derivative of RuleCondition, System.Workflow.Activities.Rules.RuleExpression-Condition.

Shown in Listing 8.9, RuleExpressionCondition is simply a wrapper around a System.CodeDom.CodeExpression. Specifically, it is XAML serialization of the CodeDOM expression tree. In the preceding XAML snippet, which is functionally equivalent to the CodeCondition implementation we provided earlier (because it always evaluates to TRue), we use a System.CodeDom.CodePrimitiveExpression to express the constant System.Boolean value TRue. The markup is verbose, but it is actually specifying the initialization of a very simple object:

 CodePrimitiveExpression expression =   new CodePrimitiveExpression(true); RuleExpressionCondition condition =   new RuleExpressionCondition("LoopCondition", expression); RuleDefinitions definitions = new RuleDefinitions(); definitions.Conditions.Add(condition); 


Listing 8.9. RuleExpressionCondition

 namespace System.Workflow.Activities.Rules {   public sealed class RuleExpressionCondition : RuleCondition   {     public RuleExpressionCondition();     public RuleExpressionCondition(CodeExpression expression);     public RuleExpressionCondition(string conditionName);     public RuleExpressionCondition(string conditionName,       CodeExpression expression);     public CodeExpression Expression { get; set; }     public override string Name { get; set; }     /* *** other members *** */   } } 


To summarize, condition properties on activities are of type ActivityCondition. A condition can be implemented as an event handler in terms of imperative code, or declaratively using markup that specifies CodeDOM expressions. You may also develop custom condition types that derive from ActivityCondition.

In general, activities can expose properties that represent either expressions in textual form or compiled code (delegates). We just saw how CodeDOM expressions, serialized as XAML, can be associated with activities but it is also possible to associate other expression representations with activities, including XPath or System. Expression based expressions. If activities in a WF program do not rely upon compiled code, but instead are provided with any necessary expressions in some textual form, then execution of the program can occur without a compilation step.

Rules

In the previous section, we learned that a RuleConditionReference is a markup-based alternative to a CodeCondition. More broadly, though, rules are a markup-based alternative to expressing program logic in code. A rule in WF is just a statement (requiring no compilation) that is expressed according to the following pattern:

  IF condition THEN actions ELSE other-actions 


A rule set is a collection of rules that act in concert to form a single computational unit.

Expressing program logic as a set of rules instead of as compiled code has two important advantages. First, by embracing rules, you can build design environments for WF programs that do not require the author to do any coding. Second, by expressing logic declaratively (rules are just part of the metadata of a WF program) you allow that logic to be modified on the fly, in the context of a single WF program instance. Making changes to running instances of WF programs is a topic we will discuss later in this chapter.

For the WF program developer, the System.Workflow.Activities.PolicyActivity activity type is the gateway to rules functionality. A policy is a wrapper around a rule set. The PolicyActivity type is shown in Listing 8.10.

Listing 8.10. PolicyActivity

 namespace System.Workflow.Activities {   public sealed class PolicyActivity : Activity   {     public PolicyActivity();     public PolicyActivity(string name);     public RuleSetReference RuleSetReference { get; set; }     /* *** other members *** */   } } 


When a PolicyActivity executes, it simply executes the rule set that is named by its RuleSetReference property. Thus, the purpose of PolicyActivity is to allow the execution of a rule set to be modeled as an activity within any WF program.

System.Workflow.Activities.Rules.RuleSetReference, shown in Listing 8.11, names a rule set that is found in the collection of rule sets held by the Rule-Definitions object (refer to Listing 8.7) associated with the WF program. This is depicted in Figure 8.2.

Listing 8.11. RuleSetReference

 public sealed class RuleSetReference : DependencyObject {   public RuleSetReference();   public RuleSetReference(string ruleSetName);   public string RuleSetName { get; set; }   /* *** other members *** */ } 


Figure 8.2. A RuleSetReference points to a RuleSet.


A WF rule set holds a collection of System.Workflow.Activities. Rules.Rule objects. The System.Workflow.Activities.Rules.RuleSet type is shown in Listing 8.12.

Listing 8.12. RuleSet

 namespace System.Workflow.Activities.Rules {   public class RuleSet   {     public RuleSet();     public RuleSet(string name);     public RuleSet(string name, string description);     public string Name { get; set; }     public string Description { get; set; }     public ICollection<Rule> Rules { get; }     public RuleChainingBehavior ChainingBehavior { get; set; }     /* *** other members *** */   } } 


The Rule type is shown in Listing 8.13. A WF rule is a condition implementationof type RuleCondition, discussed earlierplus a set of actions (the Rule.Then-Actions property) to be taken if the condition evaluates to true. A WF rule can also specify a set of actions (the Rule.ElseActions property) to be taken if the condition evaluates to false.

Listing 8.13. Rule

 namespace System.Workflow.Activities.Rules {   public class Rule   {     public Rule();     public Rule(string name);     public Rule(string name, RuleCondition condition,       IList<RuleAction> thenActions);     public Rule(string name, RuleCondition condition,       IList<RuleAction> thenActions, IList<RuleAction> elseActions);     public string Name { get; set; }     public string Description { get; set; }     public int Priority { get; set; }     public RuleReevaluationBehavior ReevaluationBehavior { get; set; }     public RuleCondition Condition { get; set; }     public IList<RuleAction> ThenActions { get; }     public IList<RuleAction> ElseActions { get; }   } } 


Thus, a WF rule is essentially a construction that takes the following form:

 if (rule.condition == true)   execute rule.then-actions else   execute rule.else-actions 


As a simple illustration of the concept of a rule, consider the following code snippet which, functionally, does exactly what a single rule can do:

 if (package.Urgency == "High") {   package.ShippingType = "Overnight";   DoSpecialPreparation(package); } else {   package.ShippingType = "Ground"; } 


The (package.Urgency== "High") expression is a condition, and each of the statements within the first set of {} braces is an action to be performed if the condition evaluates to TRue. The statements within the second set of {} braces are the actions to be performed if the condition evaluates to false.

A rule set, as we know, is a collection of rules. Because the rules in a rule set can influence one another (by modifying shared data), there are several different ways in which a rule set can be executed. Before we discuss the choices you have that govern WF rule set execution, though, let's take a look at the kinds of actions that can be associated with a rule.

The System.Workflow.Activities.Rules.RuleAction type is the base class for rule actions, and WF provides three derivatives, RuleHaltAction, Rule-UpdateAction, and RuleStatementAction, all of which are also defined in the System.Workflow.Activities.Rules namespace.

If you use a RuleHaltAction in a rule, execution of this action will cause the execution of the current rule set to immediately end.

RuleUpdateAction is used to achieve explicit chaining of rules, which will be discussed shortly. Essentially, execution of this kind of action explicitly tells the rules engine that a specific piece of data (indicated by the Path property of RuleUpdateAction) should be considered to have had its value changed as a consequence of the execution of that action.

RuleStatementAction is the general-purpose action type. A RuleStatement- Action wraps a System.CodeDom.CodeStatement, which is executed when the action executes. Among other things, a RuleStatementAction can be used to set the value of a field or property of a WF program (or of an activity within the program), invoke a method available on the WF program (or on an activity within the program), or invoke a static method. Essentially, a RuleStatementAction specifies what otherwise might be expressed as a line of code.

Rule Set Execution

Rules in a rule set are evaluated one at a timeby default, in priority order according to the value of Rule.Priorityuntil there are no more rules to evaluate, but there a couple of ways in which you can influence the manner in which a rule set is executed. The ChainingBehavior property of RuleSet indicates whether the rule set should execute without chaining, with explicit chaining only, or with full chaining. The kind of chaining we are referring to here is called forward chaining because the basic idea is that the execution of a rule can change some data that then prompts reevaluation of other (previously evaluated) rules in the rule set whose conditions depend upon that data.

If a rule set executes without chaining, each of its rules is evaluated once. The order of evaluation is determined by the value of the Priority property of each Rule object. If two or more rules carry the same priority, their ordering is not guaranteed. Because you will typically be using actions of type RuleStatementAction in your rule set, this option basically amounts to execution of an ordered list of CodeDOM expressions.

If a rule set executes with explicit chaining only, the execution of an action of type RuleUpdateAction (which indicates the data that should be considered changed) can cause chaining to occur. Specifically, if a rule "E" modifies data that is read by the condition of a previously evaluated rule "B" (a rule with higher priority), then "B" will be the next rule (after "E") to be evaluated.

If a rule set is configured to execute with full chaining, then, in addition to chaining via RuleUpdateAction, chaining can occur implicitly (when the WF rules engine infers that a rule condition's dependent data has changed) or based upon the presence of CLR attributes that serve as hints to the rules engine about when reevaluation of specific rules is needed.

To achieve implicit chaining, the WF rules engine parses the CodeDOM statements underlying each rule's RuleCondition along with each rule's set of RuleStatementAction objects so that it knows what data that rule depends upon and what data that rule changes.

To realize chaining based upon CLR attributes, you simply decorate methods (that are called by actions of type RuleStatementAction or by the code expression of a RuleCondition) to indicate what data that method reads, what data it writes, and what other methods it calls. The RuleReadAttribute, RuleWriteAttribute, and RuleInvokeAttribute types are defined (in the System.Workflow.Activities.Rules namespace) for this purpose. By default, parameters of a method are assumed to be read by the method (unless they are out or ref parameters in which case they are assumed to be written).

As an example of a rule set that uses forward chaining, consider the following set of rules:

 Rule1 (Priority=3)   if (x==2)   then z++   else y+=4 Rule2 (Priority=2)   if (y==6)   then z++, x++   else x=0 Rule3 (Priority=1)   if (z==5)   then output="red"   else output="green" 


Let's evaluate this rule set (using forward chaining) with the following input data:

 x = 1 y = 2 z = 3 output = "" 


The rule evaluation proceeds as follows:

 Rule1 (else-actions are executed, y is now 6) Rule2 (then-actions are executed, z is now 4, x is now 2) Rule1 since x changed (then-actions are executed, z is now 5) Rule3 (then-actions are executed, output is now "red") 


To familiarize yourself with the mechanism of rule evaluation, try to work out how rule evaluation unfolds for different sets of input values.

There is one additional lever for controlling the execution behavior of a rule set, and that is the ReevaluationBehavior property of Rule. If this property carries a value of Never, reevaluation of the rule will never happen once the ThenActions (or the ElseActions, if present) of such a rule has executed. If the ReevaluationBehavior property carries a value of Always, there is no restriction on how many times the rule can be reevaluated.




Essential Windows Workflow Foundation
Essential Windows Workflow Foundation
ISBN: 0321399838
EAN: 2147483647
Year: 2006
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net