RuleSet
What You’ll Learn in This Hour:
WF rule overview
RuleSet
creation through the Policy
activity
RuleSet
dependency calculations
Calling methods from rules and dependency attributes
External RuleSet application
Configuring a workflow to access an external RuleSet
So far we have looked at executing single rules that are bound to an activity. The structure of a common rule is this: if (ItemQuantity < 0) CancelOrder();
. This matches the way rules are used in traditional code. We will now look at The WF RuleSet
, another form of rules much more closely aligned with rules engines. Rule sets in general differ from traditional rules in two ways:
In addition to providing evaluational criteria, they provide the corresponding action. In the preceding example, a Rule
in a RuleSet
will contain both the if (ItemQuantity < 0)
and the CancelOrder();
action.
They frequently contain a number of interrelated rules (a set) that process in conjunction to perform a function such as calculating a price or a promotional amount. If you looked into the source code of a promotional or pricing calculation, for example, you are likely to find many if
, else
, while
, switch
, and other statements that churn in tandem to return an answer.
In WF, a RuleSet
is always stored in a .rules
file. Declarative Rule Conditions are also stored in .rules
files. The only WF rules not stored in .rules
files are Code Conditions. The two main advantages to storing rules in .rules
files are that they are more accessible to tools and they can be loaded and/or changed at runtime.
In this hour you learn to create a WF RuleSet
. Then you will learn to call methods from a Rule
within a RuleSet
. You will use the External RuleSet Toolkit that permits .rules
files to be stored, versioned, and accessed at runtime from a SQL database. Finally, you will learn to trace RuleSet
execution using standard .NET tracing.
This section begins with an overview of RuleSet
terminology and then takes you through creating a RuleSet
.
As previously stated, a RuleSet
contains one or more rules. Each RuleSet
rule contains the following attributes:
A Name
property that provides the rule with a name.
A Condition
property that holds the evaluation criteria (for example, if (itemqty > 0))
.
A Then Actions
property that holds the actions performed if the Condition
evaluates to true
.
An optional Else Actions
property that holds the actions performed if the rule Condition
evaluates to false
.
An optional Priority
property that can override the order that individual rule executes in.
A Reevaluate
property that determines whether the rule should be automatically reevaluated if a value it depends on is changed by another rule (this is referred to as forward or full chaining in rules-engine speak and is described shortly).
RuleWrite
and RuleRead
attributes that can be added to Rule
methods to explicitly state dependencies on a WF rule when WF is incapable of inferring them itself.
You will create a RuleSet
in this hour that has three rules: DiscountPercent
, TotalOrderAmount
, and YearlySales
. These rules are all dependent on each other. Table 12.1 shows each rule, its evaluational criteria, and its execution semantics.
A WF RuleSet
is processed through a forward (or full) chaining rules engine, which means it can reprocess dependent rules. The neat part is that WF can do much of this automatically via clever CODEDOM programming (a .NET serialization capability), in contrast to forcing the developer to attribute the dependencies. For example, the yearly sales must be updated with the current order amount, but the discount must be factored in or else yearly sales will be erroneously inflated. The next steps demonstrate how WF executes the sample RuleSet
.
1. Evaluate the Condition
property on the YearlySales
rule because it contains the highest Priority
. It evaluates to false
.
2. Evaluate the Condition
on the DiscountPercent
rule because it contains the second highest Priority
. It evaluates to true
, so the ThenActions
updates the discount
attribute.
3. Evaluate the Condition
on the TotalOrderAmount
rule because it is next and it is dependent on the discount
changed in the prior rule. It evaluates to true
, so execute its ThenActions
.
4. Reevaluate the YearlySales
rule because it depends on the total
attribute contained in the preceding rule.
1. Create a new blank solution (Other Project Types, Visual Studio Solutions) named RulesetSolution
in the C:SamsWf24hrsHoursHour12Rules
directory.
2. Add a Sequential Workflow Console Application
project named RulesetProject
to the solution.
3. Open Program.cs
and add the following code below the WaitHandle.WaitOne();
line to pause the host:
Console.WriteLine("Press enter to continue");
Console.Read();
4. Rename Workflow1.cs
to RulesetWorkflow.cs
in the Solution Explorer.
5. Add the following member variables to the RulesetWorkflow
code-behind file below the constructor:
double discountThreshold = 2000.00;
double subtotal = 3000.00;
double total = 0.00;
double discount = 0.00;
double totalYearlySales = 0.00;
double originalTotalYearlySales = 5000.00;
The workflow will be sparse. It consists of a Policy
activity that contains a RuleSetReference
property that a RuleSet
is bound to. Therefore a RuleSet
, through the Policy
activity, can be modeled into workflows.
1. Drag and drop a Policy
activity onto the workflow.
2. Click its RuleSetReference
property and click the ellipsis.
You should see the Select Rule Set dialog shown in Figure 12.1.
3. Click the New button to add a new RuleSet
.
1. Click the Add Rule button to add the first rule (a rule within the RuleSet
).
You should see the Rule Set Editor dialog as shown in Figure 12.2.
2. Name the rule DiscountPercent
.
3. Set its priority to 2
.
4. Leave the default Reevaluation (set at Always) and make sure the Active check box is checked.
5. Enter the following in the Condition
expression box:
this.subtotal > 2000
6. Enter the following in the ThenActions
expression box (this action occurs if the rule is true
):
this.discount = 0.05
7. Leave the ElseActions
expression box empty. (Don’t click OK yet.)
In the next steps, you add the TotalOrderAmount
rule.
1. Click the Add Rule button to add the second rule. (The first one will be displayed in the middle of the form.)
2. Name the rule TotalOrderAmount
.
3. Set its Priority to 1
.
4. Leave the default Reevaluation as Always and make sure Active is checked.
5. Enter the following in the Condition
expression box:
this.discount > 0
6. Enter the following in the ThenActions
expression box:
this.total = (1 - this.discount) * this.subtotal
7. Enter the following in the ElseActions
expression box:
this.total = this.subtotal
In the next steps, you add the YearlySales
rule.
1. Click the Add Rule button to add the third rule. (The second rule will be displayed in the middle of the form.)
2. Name the rule YearlySales
.
3. Set its Priority
to 3
.
4. Leave the default Reevaluation as Always and make sure Active is checked.
5. Enter the following in the Condition
expression box:
this.total != 0
6. Enter the following in the ThenActions
expression box:
this.totalYearlySales = this.originalTotalYearlySales + this.total
7. Leave the ElseActions
expression box empty.
8. Your Rule Set Editor dialog with completed rules should now look like Figure 12.3.
9. Click the OK button.
10. You should now be returned to the Select RuleSet form where the rule set you just added should be displayed.
11. Make sure your newly added RuleSet
is highlighted and click the Rename button.
12. Chang the rule set name to MyRuleSetDeclarative
in the Rename RuleSet dialog.
13. Click the OK button twice to leave both dialogs.
Follow the next steps to add a Code
activity to print the results to the console and run the workflow.
1. Drag and drop a Code
activity onto the workflow below the Policy
activity to display the results.
2. Double-click the Code
activity and enter the following in its handler:
Console.WriteLine("Subtotal {0}", subtotal);
Console.WriteLine("Total {0}", total);
Console.WriteLine("Discount {0}", discount);
Console.WriteLine("Total Yearly Sales {0}", totalYearlySales);
Console.WriteLine("Original Total Yearly Sales {0}",
originalTotalYearlySales);
3. Press F5 to run the workflow and you should receive the results shown in Figure 12.4.
The key is that the TotalYearlySales
is 7,850, which reflects the discounted total order amount. WF automatically infers the dependency of the total
in the YearlySales
and TotalOrderAmount
rules. In a simple sample with three rules this is not a great help; however, in many more complex pricing, promotional, and trading rules that contain 10s, 100s, or even 1,000s of rules, this automated dependency processing can be immensely useful. In the upcoming section that shows how to call methods from a rule, you will see what happens when dependencies are not observed.
It is important that you understand exactly how rules in a WF RuleSet
are processed. Because although it is useful, the automated reevaluation could be disastrous if it calculated differently than you expected. Both WF tracking and standard .NET tracing can be used to trace RuleSet
execution.
Tracking provides the Rule Name
and Condition
evaluation result. Tracing, depending on the level of detail requested, first enumerates the evaluation and then the execution. It provides Rule Names
, then
and else
condition effects (described shortly), the rules that executed, and the conditional evaluation results for the executed rules.
This exercise covers tracing. RuleSet tracking is covered in Hour 13, “Learning to Track Workflows.”
An application configuration file must be added and the level of tracing specified to determine what RuleSet
information will be traced.
Follow the next steps to configure the workflow to be traced.
1. Add an App.config
file by right-clicking the project, choosing Add, New Item, selecting the Application Configuration File
template.
2. Click the Add button. (Leave the default App.Config
name.)
3. Insert the following snippet between the Configuration
opening and closing elements to specify the maximum tracing information be collected:
<system.diagnostics>
<switches>
<add name="System.Workflow.Activities.Rules" value="All" />
</switches>
</system.diagnostics>
Follow the next steps to run the workflow and evaluate the results.
1. Press F5 to run the workflow; you should see RuleSet
evaluation results in the Output Window following all the loading statements (Click View, Output if your Output Window is not shown). The content is described immediately following its display.
Verbose: 0 : Rule Set "MyRuleSet": Rule "YearlySales" Condition
sdependency:
"this/total/"
Verbose: 0 : Rule Set "MyRuleSet": Rule "YearlySales" THEN side-
effect:
"this/totalYearlySales/"
Verbose: 0 : Rule Set "MyRuleSet": Rule "DiscountPercent" Condition
dependency:
"this/discountThreshold/"
Verbose: 0 : Rule Set "MyRuleSet": Rule "DiscountPercent" Condition
dependency:
"this/subtotal/"
Verbose: 0 : Rule Set "MyRuleSet": Rule "DiscountPercent" THEN
side-effect:
"this/discount/"
Verbose: 0 : Rule Set "MyRuleSet": Rule "TotalOrderAmount"
Condition dependency:
"this/discount/"
Verbose: 0 : Rule Set "MyRuleSet": Rule "TotalOrderAmount" THEN
side-effect:
"this/total/"
Verbose: 0 : Rule Set "MyRuleSet": Rule "TotalOrderAmount" ELSE
side-effect:
"this/total/"
Verbose: 0 : Rule Set "MyRuleSet":Rule "DiscountPercent" THEN
actions trigger
rule "TotalOrderAmount"
Verbose: 0 : Rule Set "MyRuleSet":Rule "TotalOrderAmount" THEN
actions trigger
rule "YearlySales"
Verbose: 0 : Rule Set "MyRuleSet": Rule "TotalOrderAmount" ELSE
actions trigger
rule "YearlySales"
The pre-execution evaluation data contains the following (this is WF’s plan for executing the RuleSet
before it actually does so):
The trace level, which is Verbose
in our case. (Try setting it to different levels to see how it affects the trace output, if you want.)
Each rule’s conditional dependency, the data attribute evaluated in the condition.
Each rule’s THEN side effect, the data updated by the Then
action.
Each rule’s ELSE side effect, the data updated by the Then
action.
The rules triggered by each rule are shown. For example, the DiscountPercent
rule triggers the TotalOrderAmount
rule because a change in the discount necessitates adjusting the order amount.
The post-execution results are immediately below their pre-execution counterparts next (their content is described immediately following):
Rule Set "MyRuleSet": Executing
Rule Set "MyRuleSet": Evaluating condition on rule "YearlySales".
Rule Set "MyRuleSet": Rule "YearlySales" condition evaluated to False.
Rule Set "MyRuleSet": Evaluating condition on rule "DiscountPercent".
Rule Set "MyRuleSet": Rule "DiscountPercent" condition evaluated to True.
Rule Set "MyRuleSet": Evaluating THEN actions for rule "DiscountPercent".
Rule Set "MyRuleSet": Evaluating condition on rule "TotalOrderAmount".
Rule Set "MyRuleSet": Rule "TotalOrderAmount" condition evaluated to True.
Rule Set "MyRuleSet": Evaluating THEN actions for rule "TotalOrderAmount".
Rule Set "MyRuleSet": Rule "TotalOrderAmount" side effects enable rule
"YearlySales" reevaluation.
Rule Set "MyRuleSet": Evaluating condition on rule "YearlySales".
Rule Set "MyRuleSet": Rule "YearlySales" condition evaluated to True.
Rule Set "MyRuleSet": InstanceId Evaluating THEN actions for rule "YearlySales".
The execution results data contains the following:
A row that states that execution has begun.
The condition of the first rule is evaluated.
The results of the first rule are output (false
in our case).
Any rules that evaluate to true
trigger their respective Then
actions, as would be expected. Likewise, Else
actions are triggered when rules evaluate to false
.
The most interesting result (which is boldface in the preceding code) takes place when the TotalOrderAmount
rule’s Then
/Else
action executes: it triggers reevaluation of the Yearly Sales
rule. We already know this, but it is interesting to see WF’s implementation.
You can call methods from the rules in a RuleSet
. We will now replace Then
and Else
actions in the TotalOrderAmount
rule to use a method to update the values instead of doing so declaratively. This requires that we attribute the method, because WF will no longer be able to automatically infer the dependency of the total attribute that is used to calculate yearly sales.
1. Add the following method to your workflow code-beside file below the handler that displays the results:
private void UpdateTotal()
{
this.total = (1 - this.discount) * this.subtotal;
}
2. Click the Policy
activity, click the + next to the RuleSetReference
property, and click the ellipsis in the RuleSet Definition
property.
3. Click the TotalOrderAmount
rule and replace the contents in the Then Actions and the Else Actions sections with a call to UpdateTotal()
.
4. Your TotalOrderAmount
rule should now look like Figure 12.5.
5. Run the workflow and you should see the results shown in Figure 12.6 wherein the Total Yearly Sales
is 0.
This does not match earlier processing. The reason is that the YearlySales
rule is not invoked because the Tota
l field is now updated inside a method and WF cannot automatically detect this. WF only detects members manipulated in the method signature. Those updated in the body must be explicitly noted, as shown next.
In the next subsection you attribute a method called from an action and in the following you do so for a method called from a condition. In both cases, the reason for doing so is to explicitly state dependency.
Follow the next steps to attribute the UpdateTotal
method to explicitly state dependencies from a method from a Rule action.
1. Add the following attributes above the UpdateTotal
method that inform the RuleSet
that the total
field is written to and the discount
field is read from:
[RuleWrite("total")]
[RuleRead("discount")]
2. The method should now look like this:
[RuleWrite("total")]
[RuleRead("discount")]
private void UpdateTotal()
{
this.total = (1 - this.discount) * this.subtotal;
}
3. Run the workflow and you should see results that match the original declarative results.
So far you have called a method from a Rule
action, which performs a write or an update. You can also call methods from a Rule
condition. These methods can be attributed to reflect which values are read inside of them so they will reevaluate.
Follow the next steps to attribute the UpdateTotal
method to explicitly state dependencies from a method from a Rule
condition.
1. Add the following attributed method to your workflow code-beside file, below the previous method.
[RuleRead("subtotal")]
[RuleRead("discountThreshold")]
private bool CheckDiscount()
{
return this.subtotal > this.discountThreshold;
}
2. Now you must edit the DiscountPercent
rule to use this method.
3. Edit the RuleSe
t and select the DiscountPercent
rule.
4. Replace the contents in the Condition
property with the call to CheckDiscount()
. (It will automatically insert this
. before the method name when you save.)
Your RuleSet
(and specifically the DiscountPercent
rule) should now look like Figure 12.7.
5. Run the workflow again and you should get the correct results with all values updated as expected. The dependent values in the methods are detected and the rules reevaluate accordingly.
By default, a RuleSet
is stored in a .rules
file and is bound to one workflow (as a resource). In this exercise you will download an application that permits a RuleSet
to be stored and versioned in a database. The RuleSet
is then loaded and executed at runtime. The RuleSets are maintained from a Windows application that is part of the External RuleSet Toolkit. The dialogs used to maintain a RuleSet
are available via an API, making a RuleSet
available to any .NET 2.0 or later application. The External RuleSet Toolkit is not part of WF, but rather a community application developed by a Microsoft Program Manager. In addition to being useful, the External RuleSet application exemplifies the type of tooling being built around the WF’s XML Serialized formats:—whether XAML or .rules
. It also shows how to create custom runtime services and to call the rules dialogs from a .NET app.
The next lists contains the items you will perform to download and use the External RuleSet application.
1. Download the External RuleSet application.
2. Use the demo workflow included with it.
3. Store the RuleSet
you created earlier in the External RuleSet application.
4. Configure a new workflow to access the RuleSet
stored in the External RuleSet application.
Perform the next steps to download and install the External RuleSet application.
1. Create a directory to hold the application, such as ExternalRuleSet
.
2. Download the External RuleSet application from http://wf.netfx3.com/files/folders/rules_samples/entry309.aspx.
3. Run the self-extracting .exe
file and browse to the directory you just created.
4. View the Setup.cmd
file and change the database settings if necessary. The default setting is to localhostSQLExpress
. To use it with SQL Server and localhost, delete the SQLExpress
reference in the file.
5. The command calls a SQL script that creates a database named rules, which holds the RuleSet
information.
To ensure that the External RuleSet application is functioning and to see how it works, run the sample workflow solution included with the External RuleSet application. This workflow executes a RuleSet
even though it does not contain a policy activity. It achieves this through a custom activity that mediates communication between the database and the .rules
file. The custom activity binds to a .rules
file in a database rather than in the current workflow.
Follow the next steps to run the sample workflow that comes with the External RuleSet application.
1. Open the RuleSetToolkitUsageSample
solution by navigating to the C:ExternalRuleSetRuleSetToolkitUsageSample
directory and clicking the RuleSetToolkitUsageSample.sln
file.
2. Pause the host by adding the requisite code to Program.cs
, as you did earlier in the hour.
3. Press F5 to run the sample; you should see the results shown in Figure 12.8 in the command window. (If not, verify that the External RuleSet application was installed properly.)
4. The custom (Policy
) activity on the workflow is configured as shown in Figure 12.9.
5. The DiscountRuleSet
is the name of a RuleSet
stored in the rules database by the External RuleSet application.
6. If the different versions of the RuleSet
are stored in the External RuleSet application, the specific version can be specified in the MajorVersion
and MinorVersion
properties. If they are both set to 0, the latest version will always be retrieved.
Follow the next steps to upload the RuleSet
created earlier in this hour to the External RuleSet application. This way these rules can be edited outside of the workflow they were created with, versioned, and changed at runtime.
1. Open the ExternalRuleSetToolkit
solution by navigating to the C:ExternalRuleSet
directory and clicking the ExternalRuleSetToolkit.sln
file.
2. Make sure the RuleSetTool
project is set as the startup project and then run the project.
3. You should see the RuleSet Browser dialog with DiscountRuleSet RuleSet
included with the sample application already loaded, as shown in Figure 12.10.
4. Click Data, Import, browse to the RulesetWorkflow.rules
file in the C:SamsWf24hrsHourHour12RulesRulesetSolutionRulesetProject
directory and select the file. This is the project with the Policy
activity and RuleSet
you created in an earlier exercise.
5. The MyRuleSet RuleSet
will be displayed in the RuleSet Selector dialog. The dialog allows for selection when there are multiple RuleSets
stored in a workflow, which is not the case with our sample. Your screen should now look like Figure 12.11.
6. Click OK and the MyRuleSetDeclarative RuleSet
is now added to the list of RuleSets
in the RuleSet Browser form, as shown in Figure 12.12.
7. Version 1.0 of the MyRuleSetDeclarative RuleSet
should be selected.
8. Select Rule Store, Save to upload the MyRuleSetDeclarative RuleSet
to the rules database.
To make the custom Policy
activity available to all workflows, follow the next steps to add it to the toolbox.
1. Go back to the RulesetProject
project you created earlier and open the workflow in design mode.
2. Right-click the Toolbox, select Choose Items. (This may take a while.)
3. Select the Activities tab.
4. Browse to the PolicyActivities.dll
file in the C:ExternalRuleSetPolicyActivitiesinDebug directory
.
5. Ensure that the PolicyFromService
activity is selected in the Choose Toolbox Items dialog and click OK.
6. You should now see the PolicyFromService
activity on the Toolbox.
Follow the next steps to add another workflow project to your RulesetSolution
solution. You will configure this workflow to use the PolicyFromService
activity.
1. Right-click the RuleSetSolution
in the Solution Explorer, choose Add, New Item, select Project, select the Sequential Workflow Console
application template, and name it RuleSetExternalWorkflowsTester
.
2. Reference the C:ExternalRuleSetPolicyActivitiesinDebugPolicyActivities.dll
and C:ExternalRuleSetPolicyActivitiesinDebugRuleSetService.dll
projects, which are needed to execute the PolicyFromService
activity.
3. Add the following statement to the using
statement declaration section at the top of Program.cs
.
using RuleSetServices;
4. Add the following statement above the line that starts the workflow instance to Program.cs
.
workflowRuntime.AddService(new RuleSetService());
5. Add an App.config
file by right-clicking the project, choosing Add New Item, and selecting the Application Configuration File
template.
6. Click the OK button. (Leave the default App.Config
name.)
7. Insert the following snippet between the Configuration
opening and closing elements to specify your connection string (substitute for your settings as appropriate):
<connectionStrings>
<add name="RuleSetStoreConnectionString"
connectionString="Initial
Catalog=Rules;Data Source=localhost;Integrated Security=SSPI;"
providerName="System.Data.SqlClient"/>
</connectionStrings>
Follow the next steps to add a PolicyFromService
activity to the workflow. As with the standard Policy
activity, the PolicyFromService
binds a workflow to a RuleSet
, but does so to one stored in the Ruleset
database in contrast to a .rules
file stored with the workflow.
1. Drag and drop a PolicyFromService
activity onto the workflow. This is the custom activity you added to the Toolbox a couple of steps ago.
2. Click the PolicyFromService
activity and set its RuleSetName
(not the activity name) property to MyRuleSetDeclarative
to configure it to use the RuleSet
you added to the External Rule application.
3. Add the member variables used by the RuleSet
to the workflow code-beside file below the constructor, as shown:
double discountThreshold = 2000.00;
double subtotal = 3000.00;
double total = 0.00;
double discount = 0.00;
double totalYearlySales = 0.00;
double originalTotalYearlySales = 5000.00;
4. Add the custom methods used by the RuleSet
below the member variables as shown (your rules are stored in the external application, but not your custom methods):
[RuleWrite("total")]
[RuleRead("discount")]
private void UpdateTotal()
{
this.total = (1 - this.discount) * this.subtotal;
}
[RuleRead("subtotal")]
[RuleRead("discountThreshold")]
private bool CheckDiscount()
{
return this.subtotal > this.discountThreshold;
}
Add a Code
activity to display rule processing results and run the workflow.
1. Drag and drop a Code
activity below the PolicyFromService
activity onto the workflow and add the following to its handler:
Console.WriteLine("Workflow name {0}", this.Name);
Console.WriteLine("Subtotal {0}", subtotal);
Console.WriteLine("Total {0}", total);
Console.WriteLine("Discount {0}", discount);
Console.WriteLine("Total Yearly Sales {0}", totalYearlySales);
Console.WriteLine("Original Total Yearly Sales {0}",
originalTotalYearlySales);
2. Add code to Program.cs
to pause the host.
3. Press F5 to run the workflow, and you should receive the results shown in Figure 12.13. (If you choose F5, remember to pause the host with a Console.Read()
statement.)
This hour demonstrated WF’s RuleSet
capabilities, which are a collection of interrelated rules. WF’s automatic dependency processing was covered. You learned to monitor the execution of rules. Then the ability to call methods and custom attribute them when the automated dependency checking no longer suffices was covered. Finally, the External RuleSet application was demonstrated, which shows how to centrally manage and operate RuleSets
. In addition it previews WF’s XML capability to work in conjunction with XML tools and rehosting the rules designers.
13.59.75.169