Adding Flexibility to SharePoint 2010 Workflows Using the Windows Workflow Foundation Rules Engine

Adding Flexibility to SharePoint 2010 Workflows Using the Windows Workflow Foundation Rules Engine

SharePoint 2010

Summary:  Learn about Windows Workflow Foundation rules engine capabilities and benefits that can help you automate business logic and processes for workflows in Microsoft SharePoint 2010 applications. Discover how and when to use the rules engine for simple or more complex SharePoint workflow scenarios.

Workflows are applications that enforce business processes, which include a series of activities and the control structures that connect them. The application needs a way to transition from activity to activity until the process is completed. But complexity arises because parts of those processes are often subject to change, while the core logic of the workflow remains fixed. For example, thresholds that require different levels of approval and time frames for escalation of tasks are examples of conditions that can change as business needs change. But the logic that supports them often remains unchanged—approvals still need to occur, and tasks still need to be escalated and completed. This is where a business rules engine can provide a framework that supports both the "moving parts" and the core business logic. Before you try to understand the rules engine and how it works, it is important that you understand the problem that this tool addresses.

As the title of this section states, the goal is to segment a workflow project so that business logic is separated from the logic that controls the workflow processing. In other words, the code that creates and updates tasks, manipulates list items and documents, and performs other tasks, should not be intermingled with the code or markup that expresses your business information. Identification of users whose approval is required for specific items and how much time the users have to review documents is information that should be separated from the underlying business logic. Example 1 includes two scenarios that show why, from a maintenance point of view, it is important to separate and control both the business logic and the processing logic.

Example 1: A Simple Threshold Value Rule for Routing

Imagine that you are writing a workflow that routes a document for approval. In your business requirements, it says that if any specific reviewer has not acted upon the assigned review task within five days, the document should be considered unapproved and should move through the remainder of the process appropriately, as if it had been actively rejected. Business requirements being what they typically are, it is quite likely that shortly after you deploy this custom workflow, someone will realize that five days is not nearly long enough for a review period and that lack of action by a reviewer has caused an "indirect veto" to occur—thus realizing that approvers should have at least two weeks to review and sign off on their task. Chances are, this is realized late on a Friday afternoon and absolutely must be put in place before Monday morning. Furthermore, it must also be put in place for all workflow instances that are currently in process.

Which scenario would you prefer?

  • Scenario 1:  Check the source code out of your source control system, wade through all the code to find where the five-day review period was hard-coded (which is probably in multiple locations), then make the change, put the process through a series of tests, repackage, redeploy, and hope that nothing goes wrong—with either new workflows that are starting, or any of the instances that are already in process.

  • Scenario 2:  Make a change to a single XML file through a custom client application, and save it back to the server.

The answer is obvious.

You could handle a situation like this in several ways—for example, by using web services, database entries, or simple flat files. However, you must realize that this is just a simple example, and using a rules engine for a simple threshold value adds a level of complexity that is completely unnecessary. But imagine a situation that is more complex, such as the one described in Example 2.

Example 2: Complex Routing Rules

Let's say that you have the same document approval process that includes the same possibility of an "indirect veto" (due to lack of timely response by a reviewer), but now the workflow process contains a few additional requirements, as follows:

  • If the person who misses a review timeframe is a director or higher level employee, the document is not automatically rejected. Instead, the task is assigned to their administrative assistant, who now has a new threshold time period in which to have the document approved.

  • Approval timeframes are adjusted from a base value, and are increased or decreased based on the priority of the document.

  • If the document originator is a vice president or the administrative assistant of a vice president, the indirect veto option is removed, and instead, a series of escalation timeframes are in effect.

  • If the document is identified as having a certain sensitivity level, regardless of any other factors, administrative assistants are never shown any information in their tasks that reveal the contents of the document. They are simply told that they must ask their assigned executive to review a document.

In this more complex scenario, several additional factors come into play—multiple variable-length review periods that have unique threshold values, rules that trump other rules, rules that cause other rules to be reevaluated, rules that cause other rules to change, and other factors.

Implementing the second example with any of the options that worked for the first example just became much more difficult. A custom database and web services might still work, but would be much more difficult to implement. To complicate things even more, the number of options that you must manage just grew considerably. In addition, if you need a new workflow that is almost the same, but differs in just one or two ways from this second example, you must reproduce much of the same logic and maintain two copies, or else you must build a generic application that can handle both workflows—and a third, fourth, fifth, and so on.

The situation described in Example 2 shows the need for a "rules engine."

At their simplest, rules are pieces of logic that result in a Boolean value that you can use to branch your workflow process down any of several potential paths. At that level, there is really nothing special about them. Rules like this are typically referred to as conditions, and they are like the simple threshold discussed in Example 1.

Rules can be used as conditions in multiple activities, such as:

  • IfElseBranch

  • While

  • Conditioned ActivityGroup

  • Replicator

In each of these cases, the condition is simply a Boolean evaluation. The specific activity that contains the condition behaves differently depending on the result of the condition evaluation. In the simplest case of an IfElseBranch, if the condition is true, the branch executes its child activities; if the condition is false, the branch does nothing. The other activities function in a similar way.

You can implement conditions in one of two ways: as code conditions or as declarative conditions. Using code conditions obviously involves writing code in a custom assembly that executes to arrive at the Boolean value for the condition. Declarative conditions use the Condition Editor (shown in Figure 1) that is built into the workflow extensions for Microsoft Visual Studio. Sample declarative conditions are shown in Example 3. Although it is true that you still write code inside of the Condition Editor, this code typically just implements simple comparisons, so it does not present the complexity involved with writing custom code.

Figure 1. Condition Editor

Condition Editor

Example 3: Declarative Conditions

Any of the following are examples of simple conditions:

this.po_amount > 1000 and reviewer.role < userrole.director

privacy_level == "restricted"

document_owner.division = Divisions.Finance

date_now > required_approval_date

Implementing a condition in code gives you full flexibility to do whatever you have to do. Anything you can do in the Microsoft .NET Framework can be done in a code condition, to arrive at the necessary Boolean result. You can interact with databases, web services, custom objects, complex evaluations, and so on. For declarative conditions, the available options are somewhat limited simply according to feasibility. You still write code, and that code can perform most of the actions that a full code condition can do. However, to do more than simple comparisons, it is probably a better choice to use a code condition from the start.

Based on what I have described so far, you may have a question about the usefulness of rules. They appear to add complexity without providing much value. However, the story does get better. To see why, you must understand two things: the Policy activity and RuleSets.

Policy Activity in Windows Workflow Foundation

The Policy activity is a built-in tool that is included with Windows Workflow Foundation and is therefore available for SharePoint workflows. A policy is a collection of related rules that work together to define business requirements. This is exactly what the Policy activity does: It encapsulates the definition and execution as a group of rules, known as a RuleSet. (For more details, see the next section, RuleSets in Windows Workflow Foundation.)

The conditions I have discussed previously act as a Boolean flag to determine how or whether a workflow process continues. They are the if portions of an if-then-else statement. For the previous examples, that was sufficient; the activities took care of the rest. For other scenarios, you need more, and the Policy activity provides it. The Policy activity lets you append the then-else portion to the end to round out the functionality. It is applicable when you need to do more. The job of the Policy activity is to manage the creation and execution of rules in a RuleSet.

The Policy activity itself is simple. It contains only one important property that you should be concerned about: RuleSetReference. (The other properties are Name, Description, and Enabled—all relatively self-explanatory.) The RuleSetReference property, as the name implies, contains a reference to the RuleSet that is executed by this Policy activity at run time. It also provides access to a user interface to manage that RuleSet, which provides the real power of the rules engine.

RuleSets in Windows Workflow Foundation

A RuleSet is a collection of rules with a set of execution semantics. It consists of the following:

  • One or more conditions

  • Zero or more actions to take if the conditions evaluate to true

  • Zero or more actions to take if the conditions evaluate to false

  • Metadata that is used to control execution of the rules, such as the following:

    • Name

    • Priority

    • Active flag

    • Reevaluation conditions

    • Chaining behavior

The following sections examine each of the rules and their semantics in more detail, and then finish with a review of the RuleSet Editor application that is provided by Microsoft.

Rule Actions

The actions that a rule takes can be executed if the conditions evaluate either to true or false. These are the then and else portions of the rule, and can contain whatever logic is necessary—calling web services, using custom objects, setting field values, and so on. An action can be anything that you can write code for in the .NET Framework, although you should consider a few important things, as follows:

  • The object that this action refers to in your workflow.

  • Fields or methods of custom objects must be static, because you cannot instantiate instances.

  • You cannot instantiate objects directly. However, you can write a custom object with a static method that instantiates and works with other objects.

Other than these few considerations, you can do whatever you need inside actions. You will see what the interface for this looks like when you examine the RuleSet Editor.

Example 4: Actions

This example shows some sample Actions, in the then and else portion of your RuleSet.

this.SomeVariable = SomeValue

myObject.myStaticMethod()

myObject.SomeStaticField=this.workflowProperties.List.Title

Name

A name is the identity given to each rule.

Priority

A priority enables you to control the order in which rules are executed when the Policy runs. Rules are executed in descending priority order—so a priority 5 rule is evaluated before a priority 4 rule, a priority 2 rule is evaluated before a priority 1 rule, and so on. By default, all rules start with a priority of 0. To force a rule to execute last without changing every other rule's priority, you can specify negative priorities.

Active Flag

An active flag is a Boolean value that indicates whether a particular rule is active. This enables you to keep rules in the RuleSet but prevent them from executing.

Reevaluation Conditions

A reevaluation condition is a value that is fairly basic on the surface, but that has far-reaching implications. There are only two options: Always and Never. The default is Always. To fully understand the ramifications of this setting, you must understand chaining, which is described later in this article. For now, be aware of the following:

  • Always means that when the rules engine determines that a specific rule in a RuleSet must be reevaluated, it will be.

  • Never means that even if the rules engine determines that a specific rule in a RuleSet should be reevaluated, it will not be. It is important to notice that a rule counts as having been evaluated only if it executes its non-empty then or else action. Also, this setting acts only to control reevaluation—it has no effect upon initial evaluation. To prevent initial evaluation, you must use the Active flag that was discussed previously.

You will see a reevaluation condition in action in the discussion of "chaining" in the Chaining Workflows section later in this article. But first, you must learn how dependencies between rules are identified, because it is these dependencies that trigger reevaluation.

Dependencies between rules occur when one rule makes a change that affects the evaluation of another rule. The simplest example of this is shown in Example 5.

Example 5: Simple Dependency

This example shows a simple dependency between rules.

Rule 1: if x=5 then y=7

Rule 2: if y=3 then z=4

Rule 2 has a dependency on Rule 1 because Rule 1, if it executes its then action, updates the variable y, which is used in the condition of Rule 2.

Dependencies can become more complicated, as you will see shortly. There are three ways to identify a dependency: Implicit, Attribute-Based, and Explicit.

Implicit Dependency

Although Example 5 is not labeled as such, it uses an implicit model to identify dependencies. The rules engine goes through the RuleSet and looks for scenarios such as this one, in which one rule directly affects the condition evaluation of another rule. This is the default behavior.

Attribute-Based Dependency

Attribute-Based dependency is useful only when a rule calls a custom method. Using custom objects often creates a confusion of interdependencies, and it is potentially impossible for the rules engine to identify dependencies. To solve this problem, there are three attributes with which developers can decorate their methods to provide the information that the rules engine needs, as follows:

  • RuleRead("PropertyName")   The method that is decorated with this attribute reads the specified property value.

  • RuleWrite("PropertyName")   The method that is decorated with this attribute updates the specified property value.

  • RuleInvoke("MethodName")   The method that is decorated with this attribute makes a call to another method, which must be decorated with either the RuleRead attribute or the RuleWrite attribute.

Example 6 demonstrates each of these attributes.

Example 6: Attribute-Based Dependencies

A method that reads a property value is decorated with the RuleRead attribute, as shown in the following code example.

[RuleRead("ApproverRole")]
public UserRole GetApproverRole(string UserName)
{

    // Do some processing here to determine the role of the user, based   
    // upon the ApproverRole property.
    return Role;
}

A method that updates a property is decorated with the RuleWrite attribute, as shown in the following code example.

[RuleWrite("ApprovalThreshold")]
public void SetApprovalThreshold(int BaseDays, UserRole Role)
{
    // Do some processing here to calculate the number of days users 
    // have to complete tasks based on their role in the organization
    // and update the ApprovalThreshold property.
}

A method that does not directly interact with a property can still be decorated with the RuleInvoke attribute, to indicate that it calls another method that does interact with a property.

[RuleInvoke("SetApprovalThreshold")]
[RuleInvoke("GetApproverRole")]
Public void CalculateAndAssignThresholdBasedOnRole(string UserName)
{
    UserRole Role = GetApproverRole(UserName);
    // Calculate BaseDays here.
    SetApprovalThreshold(BaseDays, Role);
}

Notice that in the code example above, multiple attributes are used. Also notice that the target methods of the RuleInvoke attribute are decorated with either the RuleRead attribute or the RuleWrite attribute.

Consider a few final points about identifying dependencies, as follows:

  • You can specify that all fields on an object are read from or written to by using an asterisk (*). For example, [RuleRead("ApproverRole/*")] indicates that all fields on the object that owns the ApproverRole field are read. The same approach can be used for RuleWrite attributes.

  • If no attributes are specified, it is assumed that a call to a method reads all properties and fields of the target object but writes to none. Any parameters that are passed to the method are assumed to be read from, but only out or ref parameters are assumed to be written to.

Explicit Dependency

The Explicit attribute is useful when you are making a call to a method for which you do not have the source code, or for which you cannot alter the source code to decorate the target method with the appropriate attributes. This dependency identification is added directly to your rule in the RuleSet Editor by using an Update statement, as shown in Example 7. In some situations, it can also be used for any method call in which you must explicitly identify dependencies.

Example 7: Explicitly Identifying Dependencies

This example shows the use of the Update() statement to explicitly identify a dependency.

If x > 7 then SomeExternalMethodThatUpdatesY()

Update(y)

Chaining Workflows

"Chaining" enables you to chain workflows together, that is, to call one entirely new workflow from within an activity of another workflow. Chaining is based on the identified dependencies among rules; more specifically, chaining is based on the dependencies among the actions of a rule and the conditions of other rules. Chaining is where things start to become complicated. The concept itself is simple enough: The potential exists to have relationships between rules whereby the then or else action of one rule takes some action (for example, setting a variable) that causes reevalution of another dependent rule condition. Unfortunately, it often does not stay this simple. These dependencies are identified by the methods that are described earlier, and they include Implicit, Explicit, or Attribute-Based dependencies.

Chaining enables you to control the way that rules are reevaluated, based upon the following:

  • Changes that are made by other rules

  • Dependencies that are identified by the rules engine (taking specific directives into account)

  • A reevaluation condition that is set for the RuleSet as a whole

The following are the three choices for the chaining setting:

  • Full  All dependencies are followed bidirectionally, and rules are reevaluated as necessary.

  • Explicit Update Only  Only dependencies that are identified with a specific Update statement are honored.

  • Sequential  Chaining is turned off. Rules are executed only one time, in descending priority order, and no rules are reevaluated.

Full is the most complicated of these options. Full causes a potential mesh of interrelated rules that can be very complex. The likelihood of producing a circular reference in a complex, full-chaining environment is very real. Example 8 provides a simple and generic way to help you understand the concept. Example 8 is followed by a real-world example that demonstrates the power and potential complexity of chaining.

Example 8: Simple Chaining

The following are examples of simple chaining.

Rule 1 (Priority: 3, Reevaluation=Always): If x=2 then x=5

Rule 2 (Priority: 2, Reevaluation=Always): If y=3 then z=7 and x=2 else z=4

Rule 3 (Priority: 1, Reevaluation=Always): If z=4 then y=3

There are two dependencies in this RuleSet. Each is fairly obvious, as follows:

  • Rule 3 is dependent on Rule 2 because the condition in Rule 3 is based on the value of z, which is set in the else action of Rule 2.

  • Rule 2 is dependent upon Rule 3 because the condition of Rule 2 is based on the value of y, which is set in the then action of Rule 3.

Assume that chaining for the RuleSet is set to Full, and you start with the following initial values:

x=0

y=7

z=2

Now, walking through the rules one by one, the following occurs:

  1. Rule 1 executes first because it has the highest priority. Because its condition evaluates to false (x != 2), its then action never executes, so nothing changes.

  2. Rule 2 (Priority 2) executes next. Again, its condition evaluates to false, so its then action does not execute. However, its else action executes, so the value of z is changed to 4.

  3. Rule 3 executes last. Its condition evaluates to true (z = 4), so its then action executes and changes y to 3.

  4. If chaining is set to any value other than Full, execution would stop here. However, because chaining is set to Full and reevaluation is set to Always, Rule 2 is reevaluated.

  5. This time around, the condition for Rule 2 evaluates to true, so z is set to 7 and x to 2.

  6. This triggers more reevaluation—for both Rule 1 and Rule 3. Rule 1 is reevaluated first because it has a higher priority.

  7. The condition for Rule 1 now evaluates to true, so x is changed to a value of 5.

  8. Rule 3 now reevaluates. Its condition evaluates to false, so nothing happens, and the RuleSet ends.

You finish with the following values:

x=5

y=3

z=7

Although the previous example is fairly simple, it does get the point across. Unfortunately, you never deal with simple integer values in real-world business rules. You deal with people, timeframes, and responsibilities. Office politics can come into play, which always makes things more complex. And because people come and go, you typically have to deal with roles instead of individuals.

Example 9 shows some of this complexity. This example makes some assumptions about variable use and such to keep it short, so do not worry about some of these supporting details—just focus on the business logic. This is also written in pseudo-code to keep things simple and focused. In this example, a RuleSet is used to determine how many days one reviewer has to either approve or reject a document at one stage of a workflow.

Example 9: Real-World Chaining

The following is an example of real-world chaining.

Rule 1 (Priority: 4, Reevaluation=Never): If 1=1 then BaseReviewCycleDays=5

Rule 2 (Priority: 3, Reevaluation=Always): If DocumentPriority=High then ReviewCycleDays=BaseReviewCycleDays - 2 else ReviewCycleDays=BaseReviewCycleDays

Rule 3 (Priority: 2, Reevaluation=Never): If Reviewer > RegularReviewer then ReviewCycleDays += 3

Rule 4 (Priority: 1, Reevaluation=Always): If DocumentAuthor > Director then DocumentPriority=High

Rule 5 (Priority: -1, Reevaluation=Never): If Approver.IsExecutive and ReviewCycleDays < 5 then ReviewCycleDays = 5 and Halt

This section does not walk through this example step-by-step, but you can complete it if you are interested. Suffice it to say that if the document is marked as regular priority, and was authored by a vice president, and the reviewer is not an executive, the result is that the reviewer has three days to review the document. Changing the reviewer to an executive and the author to a nonexecutive extends the review cycle to eight days.

One note about this example: Notice the use of the word Halt at the end of Rule 5. This is a directive that tells the rules engine to stop processing the RuleSet and immediately return to the workflow with all values as they exist at that moment. It is not an error condition, but instead is a way to short-circuit rule processing if you have to do that. In this case, it ensures that nothing is reevaluated after Rule 5 is evaluated.

Although this example is more real-world than the first chaining example, and in some ways more complex, it is still fairly easy to follow. Writing the code for it manually is relatively straightforward. However, remember that this is one piece of one step in a single workflow. This piece determines only how long one reviewer has to act on a document. When you multiply this activity by five or six reviewers, and add a whole different set of assumptions and conditions, you begin to see how complex workflow activities and requirements can become.

Now imagine that you need to make a change to some of the logic. It would be ideal if the business logic were separated from the rest of the workflow logic and could be maintained in a separate application, one that simplified the task for you by showing you only what you need to see to maintain business rules. This is the benefit of the rules engine.

Before leaving this discussion of chaining, there is one final warning: Be careful to avoid circular references. It is a very real possibility. In fact, it is probably safe to say that at some point you will cause a circular reference between your rules and lock your process as a result. Before you deploy to production, test your solution with a wide range of values or combinations of values for the various elements of your RuleSets.

Now that you have seen all of the "plumbing" that goes into a rule, tie it all together by reviewing the application that Microsoft provides to edit your RuleSet—the RuleSet Editor, shown in Figure 2.

Figure 2. RuleSet Editor application for building and maintaining Rules and metadata

RuleSet Editor application

The RuleSet Editor application presents an interface that enables you to build rules and maintain RuleSet metadata. The important thing to know about this application is that it is available for use in custom Windows applications by using the RuleSetDialog class. Because the RuleSet Editor is implemented as a standard Windows dialog box, just as you can with the Save and Open dialog boxes, you can easily rehost it in your custom application.

Unfortunately, the RuleSet Editor is available only for Windows applications; it is not available for web applications.

This presents a significant problem. After all, SharePoint is a web application, and you are trying to use the rules engine from that web application. Unfortunately, this means that you cannot quite reach your goal from this point. However, you can build on what you have by doing a little extra work to make it all the way to your goal. Here is what you have accomplished so far:

  • Separated your business logic from your workflow processing logic

  • Learned how to harness the rules engine as a tool to manage your business logic

Where you might have failed was in achieving the simplicity that was hinted at back in Scenario 2 of Example 1: Maintaining your rules is not as simple as editing an XML file and saving it back to the server.

However, with a little more work, it could be simpler. Although a detailed discussion of how to build the whole solution is out of scope for this article, the final section of this article highlights the steps that are required for success.

.Rules File

It is important to understand how RuleSets are stored so that they are available to the Policy activity. If you pay attention to the Solution Explorer window in Visual Studio while you add rules to a RuleSet, you might notice the addition of a .rules file. This is the file that maintains the information about your rules, and it is the XML file that was mentioned in Scenario 2 of Example 1. This is the file that you must change when your rules change. The .rules file is a serialized representation of your RuleSet. There is no example of a .rules file included here because there is no such thing as a "short" .rules file. Even the simplest rule, if 1==1 then this.someVariable = 5, is about 50 lines of XML.

Fortunately, the RuleSet Editor application that was discussed earlier makes creating and editing a .rules file simple. As mentioned previously, the RuleSet Editor is implemented as a standard dialog box. When you click OK, the RuleSet that you created becomes available in the RuleSet property of the RuleSetDialog class. Converting this to a file is a simple matter of serialization. The only trick is to use the WorkflowMarkupSerializer class instead of the standard XML serialization classes.

By default, when you compile a workflow, the .rules file is stored as a resource, as shown in Figure 3. When the Policy activity executes, it looks for the RuleSet as a resource in its parent assembly. This obviously does not work for what you are trying to accomplish here, because changing the .rules file would require recompiling and redeploying your assembly. To make things simpler, you need a custom Policy activity that looks for .rules files elsewhere.

Figure 3. .rules files are embedded into a workflow assembly as resources

Rules files embedded into workflow assembly

ExternalPolicy Activity

The last piece of the solution is a custom workflow activity that looks somewhere other than to its containing assembly for its RuleSet definition. This location could be a Microsoft SQL Server database, a file on the server file system, or a SharePoint document library—anything that lets it retrieve the serialized rules file and use it to rebuild and execute a RuleSet. Example 10 shows roughly what the Execute method of such an activity would look like. In this case, it retrieves the .rules file from a specific URL, such as the URL for a SharePoint document library.

Example 10: Executing an Externally Stored Policy

The following example shows the beginnings of an externally managed rule application.

WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
XmlReader reader = XmlReader.Create(UrlOfRuleFile);

// Deserialize the .rules file into a RuleSet object.
RuleSet ruleSet = (RuleSet)serializer.Deserialize(reader);
reader.Close(); 

// Next, use a custom method that returns the root of the workflow as  
// an activity.
Activity target = GetRootOfWorkflowAsActivity(); 
RuleValidation validation = new 
     RuleValidation(target.GetType(), null);
RuleExecution execution = new RuleExecution(validation,target, ctx);
ruleSet.Execute(execution);

One benefit of storing the .rules file inside a SharePoint document library is that you can take advantage of the full versioning, alerts, Recycle Bin, approval, and other advanced capabilities that SharePoint includes. You could even have a workflow running to process changes that are made to a .rules file for another workflow.

Sanity Check: Can't We Just Do This in Code?

In any situation in which the rules engine can play a role, you can also use code. However, this is not feasible for even moderately complex scenarios, especially highly volatile ones. For these situations, the rules engine enables you to manage the complex, changing rules without writing and rewriting code. Using the rules engine is not targeted at simple scenarios. For simple Boolean evaluations, code is sufficient or declarative conditions are sufficient. For more complex scenarios, writing the equivalent code would be a massive undertaking.

This article describes the capabilities and benefits of the Windows Workflow Foundation rules engine that is available to workflows in SharePoint 2010. The Windows Workflow Foundation rules engine is not intended for use by every SharePoint workflow. It is most useful for sophisticated and complex scenarios. Managing and evaluating complex, interrelated business logic is where the rules engine shines. When you find yourself in such a scenario, you will have an ally in the Windows Workflow Foundation rules engine.

Show:
© 2016 Microsoft