Creating Rules


There are a few ways to create rules: through the UI provided in Visual Studio (the most common method), by using XML, and programmatically by using standard .NET code.

The Rules UI

You can access the rules dialog boxes defined in the Windows Workflow Foundation API from a few predefined points in Visual Studio. The following sections describe these dialog boxes and how you can access and use them.

The Rule Set Editor

The Rule Set Editor dialog box (shown in Figure 9-4) provides all the functionality necessary to create new rule sets or modify existing ones. You access this dialog box from the Policy activity’s RuleSetReference property.

image from book
Figure 9-4

Keep in mind that just because only one out-of-the-box activity uses the concept of rule sets does not mean that you can’t develop custom activities to use them as well.

You can accomplish quite a few things in this dialog box, including adding new rules to the current set, editing existing rules, and setting rule set chaining options. The list view toward the top of the screen lists all rules currently defined in the set. You can manipulate individual rules by selecting an item in the list and either deleting (by clicking the Delete button) or editing the rule’s properties, which are populated in the area below the list.

The properties area provides essentially everything you need to modify a rule. The rule’s IF, THEN, and ELSE statements are displayed here. In addition, the name, priority, and reevaluation properties can be modified. There is also an Active check box that specifies whether the currently selected rule should be evaluated during the rule set’s evaluation.

One of the nice features of the rules UI in Windows Workflow Foundation is the inclusion of IntelliSense. When a rule’s IF, THEN, or ELSE statements are being edited, contextual hints are given using the workflow’s code-beside class as a baseline. This means that if you type this. in the IF box, you are presented with a list of variables from the workflow definition’s class. For example, if a workflow’s class had a member called order, it would appear in the IntelliSense list as shown in Figure 9-5.

image from book
Figure 9-5

Even though there is only one custom field defined in this example, the list is quite long. This is because the list is showing all members of the workflow class and its parent classes.

The Rule Condition Editor

The declarative Rule Condition Editor, shown in Figure 9-6, allows you to specify simple rule conditions for use in conditional activities such as IfElse, While, and ConditionedActivityGroup. Not surprisingly, there is not a lot to this dialog box, because rule conditions are simply a Boolean expression. However, the UI provides nice features such as IntelliSense and rule validation. The red exclamation icon on the right side of the screen indicates a problem with the current condition. In this case, nothing has been entered in the required editor area.

image from book
Figure 9-6

Embedding the Rules UI in Custom Applications

The workflow API exposes the rules UI dialog boxes for use in custom applications. These classes are located in the System.Workflow.Activities.Rules.Design namespace and include RuleCondition Dialog and RuleSetDialog.

The RuleConditionDialog is the UI that allows users to create declarative rule conditions based on the workflow that is passed to its constructor, as shown in the following code listing. Any members that have been defined in the MyWorkflow class are accessible in the rules UI, just as in Visual Studio. After the ShowDialog method is called and the user has created the desired condition, it is accessed with the Expression property. A RuleDefinitions instance is then created and the condition is added after being wrapped in a RuleExpressionCondition object. Finally, the RuleSetDefinition is serialized and written to an XML file.

  private void CreateRuleCondition() {     MyWorkflow wf = new MyWorkflow();     RuleConditionDialog rdc = new RuleConditionDialog(wf, null);     rdc.ShowDialog();     CodeExpression ce = rdc.Expression;     RuleDefinitions ruleDefinitions = new RuleDefinitions();     RuleExpressionCondition condition =         new RuleExpressionCondition("MyCondition", ce);     ruleDefinitions.Conditions.Add(condition);     WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();     serializer.Serialize(new XmlTextWriter(@"C:\rules.xml", Encoding.Default),         ruleDefinitions); } 

In addition to creating declarative rule conditions, the workflow API provides you with a dialog box where you can create more complex rule sets by using the RuleSetDialog class. The following code listing shows you how to use this class to create a rule set in a custom application:

  private void CreateRuleSet() {     RuleSet ruleSet = new RuleSet("MyRuleSet");     MyWorkflow wf = new MyWorkflow();     RuleSetDialog rsd = new RuleSetDialog(wf, ruleSet);     rsd.ShowDialog();     ruleSet = rsd.RuleSet;     RuleDefinitions ruleDefinitions = new RuleDefinitions();     ruleDefinitions.RuleSets.Add(ruleSet);     WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();     serializer.Serialize(new XmlTextWriter(@"C:\rules.xml", Encoding.Default),         ruleDefinitions); } 

This code first creates an empty RuleSet instance simply to give it a name. The RuleSetDialog is then initialized by passing an instance of the MyWorkflow workflow class. As with the RuleConditionDialog, passing this workflow reference allows the user to access, with IntelliSense, any members defined in the workflow class.

After the rule set has been defined by the user, it is accessible through the RuleSet property. A Rule SetDefinitions class is created to hold the RuleSet object, and then it is serialized to XML on the filesystem.

Creating Rules Programmatically

Even with the rich UI experience provided out of the box with Windows Workflow Foundation, there might be situations when rules need to be created programmatically. Think about a project that requires end users to create rules without the assistance of IT. In many cases, you can just embed the provided dialog boxes in custom applications, but this is not always feasible. If there are very specific guidelines about how business rules can be defined, you may need to write custom code to fulfill the requirements.

The following sections describe how you can define rules in code and how these rules are subsequently evaluated.

Rules and the CodeDom

Internally, rules are created using classes defined in the .NET CodeDom namespaces (System.CodeDom). The CodeDom is not specific to Windows Workflow Foundation and is used for many different applications within and using .NET.

Essentially, the CodeDom enables you to create code by using code. Think about how you would describe a C# if-else statement using code. First, you need an object representing the if statement that has a construct for producing a value of true or false based on an expression. Next, you need an object representing the actions to be taken if the condition is true and another object representing the else portion of the code. Each discrete code construct and action should be able to be represented by objects that can then be compiled into actual executable code. This is what the CodeDom is for.

Table 9-4 lists the .NET CodeDom expression class types that are used to support the programmatic creation of workflow rules. Keep in mind that the classes listed here represent only a subset of what exists in the CodeDom namespaces. There are plenty of great resources available on the Web that discuss this topic in depth and in a more generic context.

Table 9-4: Relevant CodeDom Expression Classes
Open table as spreadsheet

CodeDom Type

Description

CodeAssignStatement

Supports the assignment of a value to a variable.

Example: myName = "Todd"

CodeBinaryOperatorExpression

Allows two code expressions to be operated on with a binary expression such as a comparison or mathematic operation.

Example: myAge == 25

CodeDirectionExpression

Specifies the direction of a parameter being passed to a method.

Example: MyMethod(out int val)

CodeExpressionStatement

Represents a single expression or line of code.

Example: myObject.ToString()

CodeFieldReferenceExpression

Evaluates to a field reference.

Example: this.myVariable

CodeMethodInvokeExpression

Represents a method call.

Example: Console.WriteLine("Hi")

CodeMethodReferenceExpression

Represents a reference to a method. This object could then be passed to CodeMethodInvokeExpression.

CodePrimitiveExpression

Represents a single primitive value.

Examples: 1, "Hello", false, 10.5

CodePropertyReferenceExpression

Evaluates to a property reference.

Example: this.MyName

CodeThisReferenceExpression

Represents a reference to the current class.

For example, if you wanted to reference a method on the current, local class, it would look like this: this.PerformValidation().

CodeTypeReference

Represents a reference to a .NET type.

Example: typeof(System.String)

CodeTypeReferenceExpression

Represents a reference to a .NET data type.

Example: System.String

Table 9-5 lists the CodeDom classes that allow operations to be performed between two expressions. These operators range from mathematical operations to Boolean comparison operations. All of these operators are values found in the CodeBinaryOperatorType enumeration.

Table 9-5: CodeDom Comparison Classes
Open table as spreadsheet

Operators

Description

Add

Used to add expressions.

Example: 2+2

BitwiseAnd

Performs a bitwise and operation.

Example: val1 & val2

BitwiseOr

Performs a bitwise or operation.

Example val1 | val2

BooleanAnd

Represents a Boolean and expression, as used with if statements.

Example: val1 == val2&& val3 == val4

BooleanOr

Represents a Boolean or expression.

Example: val1 == val2|| val3 == val4

Divide

Provides the division operator.

Example: 10 / 2

GreaterThan

Represents the greater than operator.

Example: x > 10

GreaterThanOrEqual

Represents the greater than or equal to operator.

Example: val >= 24

IdentityEquality

The identity equality operator.

Example: val == anotherVal

IdentityInequality

The identity inequality operator.

Example: val != anotherVal

LessThan

Represents the less than operator.

Example: 5 < count

LessThanOrEqual

Represents the less than or equal to operator.

Example: val <= 40

Modulus

Allows the use of the modulus operator.

Example: (x%2)==0

Multiply

The multiplication operator.

Example: 2x2

Subtract

The subtraction operator.

Example: 40 - x

ValueEquality

The value equality operator.

Example: val == 2

The Rules API

In addition to the functionality provided by the CodeDom to create rule expressions, the Windows Workflow Foundation API exposes classes that represent the rule entities. This includes items such as rules themselves, rule sets, and rule definitions that represent the container for all rule-related items in a workflow.

The rule-related objects in the API are located in the System.Workflow.Activities.Rules namespace and enable you to programmatically create rules just as they are created with the Visual Studio APIs. Building from the bottom up, the Rule class exposes properties such as Condition, ThenActions, and ElseActions. The ThenActions and ElseActions properties are collections that can have any number of actions associated with them.

Although the conditions and actions associated with a rule all come from the CodeDom classes, they are wrapped in rulecentric classes when associated with the Rule class. The Condition property is of type RuleExpressionCondition, which takes a CodeExpression as a parameter in one of its constructor’s overloads. The ThenActions and ElseActions collections also wrap CodeExpression classes in a class called RuleStatementAction.

Next in the chain comes the RuleSet class. This class has properties such as ChainingBehavior, Name, Description, and Rules. The ChainingBehavior property takes a value from the RuleChaining Behavior enumeration. The Rules property is a collection of Rule objects that make up the rule set. You programmatically add Rule instances just as you would do visually in the rules UI.

The RuleDefinitions class essentially represents what you would find in the .rules file of a workflow. It is a container for all declarative rule conditions as well as all rule sets associated with a particular workflow. It has two properties: Conditions and RuleSets. As described in the following section, which provides an example of programmatic rule creation, the RuleDefinitions class can be serialized to an XML file, which gives you the equivalent of a .rules file created in Visual Studio.

A Programmatic Rules Example

This section studies some code that programmatically builds a rule set using the rules API and the CodeDom classes. To maintain consistency and so that you can see how the same rules can be modeled in different ways, the code builds the same three rules introduced earlier in the chaining section:

  • One rule to check whether the renter requires insurance based on the car type and customer age

  • One rule to provide a premium car upgrade if the customer is classified as Premium

  • One rule to set the required gas level based on the option chosen by the renter

In addition to building the rules and rule set, the following code serializes the RuleDefinitions object to an XML file. The output XML is just as good as the XML created by the rules UI and can be used in the same way.

  public static class RulesHelper {     // the following group of static members is simply obtaining references to     // fields, properties, and types which will be used throughout the code     private static CodeThisReferenceExpression thisReference =         new CodeThisReferenceExpression();     private static CodeFieldReferenceExpression rentalFieldReference =         new CodeFieldReferenceExpression(thisReference, "rental");     private static CodePropertyReferenceExpression customerFieldReference =         new CodePropertyReferenceExpression(rentalFieldReference, "Customer");     private static CodePropertyReferenceExpression carFieldReference =         new CodePropertyReferenceExpression(rentalFieldReference, "Car");     private static CodeTypeReferenceExpression carTypeReference =         new CodeTypeReferenceExpression(typeof(CarType));     private static CodeTypeReferenceExpression customerTypeReference =         new CodeTypeReferenceExpression(typeof(CustomerType));     private static CodeTypeReferenceExpression gasOptionReference =         new CodeTypeReferenceExpression(typeof(GasOption));     private static Rule BuildRequireInsuranceRule()     {         Rule rule = new Rule("RequireInsurance");         rule.Priority = 15;         // car type == luxury         CodeBinaryOperatorExpression carTypeExp =             new CodeBinaryOperatorExpression();         carTypeExp.Left = new CodePropertyReferenceExpression(rentalFieldReference,             "CarType");         carTypeExp.Operator = CodeBinaryOperatorType.ValueEquality;         carTypeExp.Right = new CodeFieldReferenceExpression(carTypeReference,             "Luxury");         // age <= 27         CodeBinaryOperatorExpression ageExp = new CodeBinaryOperatorExpression();         ageExp.Left = new CodePropertyReferenceExpression(customerFieldReference,             "Age");         ageExp.Operator = CodeBinaryOperatorType.LessThanOrEqual;         ageExp.Right = new CodePrimitiveExpression(27);         CodeBinaryOperatorExpression condition =             new CodeBinaryOperatorExpression();         condition.Left = carTypeExp;         condition.Operator = CodeBinaryOperatorType.BooleanAnd;         condition.Right = ageExp;         rule.Condition = new RuleExpressionCondition(condition);         // create the THEN action         // require insurance = true         CodeAssignStatement thenAction = new CodeAssignStatement(             new CodePropertyReferenceExpression(rentalFieldReference,                 "RequireInsurance"),             new CodePrimitiveExpression(true));         rule.ThenActions.Add(new RuleStatementAction(thenAction));         return rule;     }     private static Rule BuildIsPremiumCustomerRule()     {         Rule rule = new Rule("IsPremiumCustomer");         rule.Priority = 10;         // customer type ==premium         CodeBinaryOperatorExpression customerTypeExp =             new CodeBinaryOperatorExpression();         customerTypeExp.Left =             new CodePropertyReferenceExpression(customerFieldReference, "Type");         customerTypeExp.Operator = CodeBinaryOperatorType.ValueEquality;         customerTypeExp.Right =             new CodeFieldReferenceExpression(customerTypeReference, "Premium");         rule.Condition = new RuleExpressionCondition(customerTypeExp);         // create the THEN action         // car type = luxury         CodeAssignStatement thenAction = new CodeAssignStatement(             new CodePropertyReferenceExpression(rentalFieldReference, "CarType"),             new CodeFieldReferenceExpression(carTypeReference, "Luxury"));         rule.ThenActions.Add(new RuleStatementAction(thenAction));         return rule;     }     private static Rule BuildGasOptionRule()     {         Rule rule = new Rule("GasOption");         rule.Priority = 5;         // gas option == refill before return         CodeBinaryOperatorExpression customerTypeExp =             new CodeBinaryOperatorExpression();         customerTypeExp.Left =             new CodePropertyReferenceExpression(rentalFieldReference, "GasOption");         customerTypeExp.Operator = CodeBinaryOperatorType.ValueEquality;         customerTypeExp.Right =             new CodeFieldReferenceExpression(gasOptionReference,                 "RefillBeforeReturn");         rule.Condition = new RuleExpressionCondition(customerTypeExp);         // create the THEN action         // required return tank level = current tank level         CodeAssignStatement thenAction = new CodeAssignStatement(             new CodePropertyReferenceExpression(rentalFieldReference,                 "MinimumTankLevelUponReturn"),             new CodePropertyReferenceExpression(carFieldReference,                 "CurrentTankLevel"));         // create the ELSE action         // required return tank level = 0         CodeAssignStatement elseAction = new CodeAssignStatement(             new CodePropertyReferenceExpression(rentalFieldReference,                 "MinimumTankLevelUponReturn"),             new CodePrimitiveExpression(0));         rule.ThenActions.Add(new RuleStatementAction(thenAction));         rule.ElseActions.Add(new RuleStatementAction(elseAction));         return rule;     }     public static RuleDefinitions BuildRuleDefinitions()     {         Rule rule1 = BuildRequireInsuranceRule();         Rule rule2 = BuildIsPremiumCustomerRule();         Rule rule3 = BuildGasOptionRule();         RuleSet ruleSet = new RuleSet("CarRentalRuleSet");         ruleSet.ChainingBehavior = RuleChainingBehavior.Full;         ruleSet.Rules.Add(rule1);         ruleSet.Rules.Add(rule2);         ruleSet.Rules.Add(rule3);         RuleDefinitions ruleDefinitions = new RuleDefinitions();         ruleDefinitions.RuleSets.Add(ruleSet);         WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();         serializer.Serialize(new XmlTextWriter(@"C:\rules.xml", Encoding.Default),             ruleDefinitions);         return ruleDefinitions;     } } 

There are a couple of things going on here. First, there is logic that builds the rules using the CodeDom classes - this code isn’t specific to Windows Workflow Foundation. Here, the classes that were introduced in Table 9-4 and Table 9-5 are used to build the same rules that were built earlier using the UI. Second, there is code that takes the CodeDom expressions and creates Rule instances, and then adds these Rule instances to a RuleSet object in the BuildRuleDefinitions method. Finally, a RuleDefinitions instance is created and serialized to an XML file using WorkflowMarkupSerializer.

Creating Your Own Rules Editor

Given the knowledge conveyed in the last few sections, you could conceivably develop a completely customized rules editor. If end users need to be able to develop and maintain rules, the UI provided with the workflow API may or may not meet all requirements.

In reality, any interface given to users who are not developers would need to have tight controls and guidance during the rule development process. Although you may take for granted how easy it is to type this.rental.CarType, the average user’s head might explode if you expect him or her to know how to do just that, and rightfully so. Users aren’t developers, and it is not their job to be technical. It is up to you to provide an interface based on your users’ level of expertise.

A user-friendly rules editor would probably be able to determine which properties users need to make comparisons on and provide these properties in something like a drop-down box so that there is no guessing involved. Comparison operators would probably also be displayed in a list for ease of use. Whatever your rules editor ends up looking like, the flexibility of the .NET Framework and Windows Workflow Foundation allow virtually endless possibilities to meet your needs.

Rules in XML

By default in Windows Workflow Foundation, rule definitions are associated with workflows in XML files. If you define rule conditions or rule sets in Visual Studio, the Solution Explorer window displays a <Workflow Name>.rules file associated with your workflow file (see Figure 9-7).

image from book
Figure 9-7

The following XML represents the three rental rules introduced earlier in this chapter. The nodes in the file correspond to types in the System.CodeDom namespace and are laid out in a similar fashion as the code implementation in the previous section. Some of the text has been abbreviated for spacing considerations.

  <RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">   <RuleDefinitions.RuleSets>     <RuleSet Name="RentalRules" ChainingBehavior="Full" Description="{p3:Null}"         xmlns:p3="http://schemas.microsoft.com/winfx/2006/xaml">       <RuleSet.Rules>         <Rule Name="Require Insurance" ReevaluationBehavior="Always" Priority="15"             Description="{p3:Null}" Active="True">             ...         </Rule>         <Rule Name="Is Premium Customer" ReevaluationBehavior="Always"             Priority="10" Description="{p3:Null}" Active="True">           <Rule.ThenActions>             <RuleStatementAction>               <RuleStatementAction.CodeDomStatement>                 <ns0:CodeAssignStatement LinePragma="{p3:Null}"                     xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System,                     Version=2.0.0.0, Culture=neutral,                     PublicKeyToken=b77a5c561934e089">                   <ns0:CodeAssignStatement.Left>                     <ns0:CodePropertyReferenceExpression PropertyName="CarType">                       <ns0:CodePropertyReferenceExpression.TargetObject>                         <ns0:CodeFieldReferenceExpression FieldName="rental">                           <ns0:CodeFieldReferenceExpression.TargetObject>                             <ns0:CodeThisReferenceExpression />                           </ns0:CodeFieldReferenceExpression.TargetObject>                         </ns0:CodeFieldReferenceExpression>                       </ns0:CodePropertyReferenceExpression.TargetObject>                     </ns0:CodePropertyReferenceExpression>                   </ns0:CodeAssignStatement.Left>                   <ns0:CodeAssignStatement.Right>                     <ns0:CodeFieldReferenceExpression FieldName="Luxury">                       <ns0:CodeFieldReferenceExpression.TargetObject>                         <ns0:CodeTypeReferenceExpression>                           <ns0:CodeTypeReferenceExpression.Type>                             <ns0:CodeTypeReference ArrayElementType="{p3:Null}"                                 BaseType="RulesTesting.CarType" Options="0"                                 ArrayRank="0" />                           </ns0:CodeTypeReferenceExpression.Type>                         </ns0:CodeTypeReferenceExpression>                       </ns0:CodeFieldReferenceExpression.TargetObject>                     </ns0:CodeFieldReferenceExpression>                   </ns0:CodeAssignStatement.Right>                 </ns0:CodeAssignStatement>               </RuleStatementAction.CodeDomStatement>             </RuleStatementAction>           </Rule.ThenActions>           <Rule.Condition>             <RuleExpressionCondition Name="{p3:Null}">               <RuleExpressionCondition.Expression>                 <ns0:CodeBinaryOperatorExpression Operator="ValueEquality"                     xmlns:ns0="...">                   <ns0:CodeBinaryOperatorExpression.Left>                     <ns0:CodePropertyReferenceExpression PropertyName="Type">                       <ns0:CodePropertyReferenceExpression.TargetObject>                         <ns0:CodePropertyReferenceExpression                             PropertyName="Customer">                           <ns0:CodePropertyReferenceExpression.TargetObject>                             <ns0:CodeFieldReferenceExpression FieldName="rental">                               <ns0:CodeFieldReferenceExpression.TargetObject>                                 <ns0:CodeThisReferenceExpression />                               </ns0:CodeFieldReferenceExpression.TargetObject>                             </ns0:CodeFieldReferenceExpression>                           </ns0:CodePropertyReferenceExpression.TargetObject>                         </ns0:CodePropertyReferenceExpression>                       </ns0:CodePropertyReferenceExpression.TargetObject>                     </ns0:CodePropertyReferenceExpression>                   </ns0:CodeBinaryOperatorExpression.Left>                   <ns0:CodeBinaryOperatorExpression.Right>                     <ns0:CodeFieldReferenceExpression FieldName="Premium">                       <ns0:CodeFieldReferenceExpression.TargetObject>                         <ns0:CodeTypeReferenceExpression>                           <ns0:CodeTypeReferenceExpression.Type>                             <ns0:CodeTypeReference ArrayElementType="{p3:Null}"                                 BaseType="RulesTesting.CustomerType" Options="0"                                 ArrayRank="0" />                           </ns0:CodeTypeReferenceExpression.Type>                         </ns0:CodeTypeReferenceExpression>                       </ns0:CodeFieldReferenceExpression.TargetObject>                     </ns0:CodeFieldReferenceExpression>                   </ns0:CodeBinaryOperatorExpression.Right>                 </ns0:CodeBinaryOperatorExpression>               </RuleExpressionCondition.Expression>             </RuleExpressionCondition>           </Rule.Condition>         </Rule>         <Rule Name="Gas Option" ReevaluationBehavior="Always" Priority="5"             Description="{p3:Null}" Active="True">             ...         </Rule>       </RuleSet.Rules>     </RuleSet>   </RuleDefinitions.RuleSets> </RuleDefinitions> 



Professional Windows Workflow Foundation
Professional Windows Workflow Foundation
ISBN: 0470053860
EAN: 2147483647
Year: 2004
Pages: 118
Authors: Todd Kitta

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