Dynamic Editing of Running WF Program Instances


WF programs can describe real-world processes. Typically, however, a WF program can really only capture a set of most probable paths along which a process may unfold. A great deal of flexibility can be built into WF programs using control flow activities that are crafted to meet the requirements of a specific problem domain. But because people are involved in processes, processes inevitably change. Steps are added, skipped, restarted, or canceled, and the logic that governs transitions from one step to another can certainly change too. We are not referring here to gradual refinement of a process definition. Most change comes from the realities of having to constantly adjuston the fly, in the context of an instance of the processto special circumstances: a difficult but important customer, an employee who changes jobs, a shifting deadline, a supplier that goes out of business, a rush order, a lost order, a duplicate order, a power outage, new requirements from a partner, or an unexpected spike in demand.

This is one reason why software is not as pervasive as it might be. No one will use a program to help coordinate her work if the program shackles her with rigid definitions of processes. The software cannot propose to help 25% or even 50% of the time. It should be useful all of the time. To do this, it must have a degree of malleabilityat the level of process instancesthat is rare in software today.

The approach taken by WF is simple. The activity tree that constitutes a WF program prototype is available for inspection at runtime. Furthermore, a request can be made to perform changes to a prototype, in the context of a single running WF program instance (which was manufactured from that prototype). The WF runtime will enforce certain inherent constraints on what changes are allowed, and the activities in the tree also vote on whether a proposed set of changes is acceptable. The crucial point here is that the WF runtime, and the activities, make their determination based upon the full current state of the running WF program instance that is being changed. Thus it is possible for a WF program instance to evolve, as it is running, subject to fine-grained state-based constraints, and in this way can enjoy some of the same nimbleness that the people who are involved in a process typically exhibit as they steer the process, and tweak it, with the goal of reaching a successful outcome.

Here is a simple but functional code snippet that shows how to dynamically add one child activity, of type WriteLine, to the root activity of a WF program:

 // Obtain the instance that we wish to change WorkflowInstance instance = ... // Get the prototype for this instance Activity definition = instance.GetWorkflowDefinition(); // Create a wrapper for proposing changes WorkflowChanges changes = new WorkflowChanges(definition); // Obtain the writeable copy of the prototype Interleave root = changes.TransientWorkflow as Interleave; // Create and add our new activity WriteLine write = new WriteLine(); write.Name = "write1"; write.Text = "added dynamically!"; root.Activities.Add(write); // Apply the changes, for this instance instance.ApplyWorkflowChanges(changes); 


In order to propose changes to a WF program instance, we first obtain from the WF runtime a System.Workflow.Runtime.WorkflowInstance object representing that instance. The WorkflowInstance type was discussed in Chapter 5; the WorkflowInstance methods used in the preceding code snippet are shown in Listing 8.14.

Listing 8.14. WorkflowInstance Revisited

 namespace System.Workflow.Runtime {   public sealed class WorkflowInstance   {     public Activity GetWorkflowDefinition();     public void ApplyWorkflowChanges(WorkflowChanges changes);     /* *** other members *** */   } } 


The GetWorkflowDefinition method returns a copy of the WF program prototype from which the instance was manufactured. Because this activity tree is a prototype, the activity objects in the tree will not carry any instance-specific property valuesit is effectively all metadata.

We use this copy of a WF program prototype, obtained from WorkflowInstance, to create a System.Workflow.ComponentModel.WorkflowChanges object. A WorkflowChanges object is conceptually just a container for a set of proposed changes to a specific WF program instance. The WorkflowChanges type is shown in Listing 8.15.

Listing 8.15. WorkflowChanges

 namespace System.Workflow.ComponentModel {   public sealed class WorkflowChanges   {     public WorkflowChanges(Activity root);     public CompositeActivity TransientWorkflow { get; }     public ValidationErrorCollection Validate();     public static readonly DependencyProperty ConditionProperty;     /* *** other members *** */   } } 


From the WorkflowChanges.TransientWorkflow property getter, we obtain a writeable instance-specific activity tree. We can cast the object returned to the actual type of the root activity of our WF program. In our preceding example, the root activity of the WF program instance being changed is assumed to be of type EssentialWF.Activities.Interleave. Our preparatory work is done.

Because we now hold an updateable activity tree for the WF program instance, we are free to navigate up and down the hierarchy of activities and make changes. Here we simply add a WriteLine activity to the Interleave activity at the root of the WF program, but (subject to restrictions discussed shortly) we can actually add and remove activities anywhere in the tree, just as we could at design-time.

Once we are done proposing changes, we apply these changes to the instance by calling the ApplyWorkflowChanges method of the WorkflowInstance object from which we obtained the WF program prototype.

Restrictions on Dynamic Editing of a Program Instance

There are three forms of restrictions on the kinds of changes to a WF program instance that can be made. Some limitations are inherent to the WF programming model. Other restrictions can be imposed by the WF program author; this can include turning off update capability. Still other restrictions can be dictated by composite activity writers; this logic too can go so far as to reject any proposed changes. Of course the application hosting the WF runtime also can play a part in determining what changes are permissible, and who can propose them, but such layers of application code are outside the scope of the WF programming model.

The first set of restrictions on what changes can be made to a running WF program instance are inherent to the WF programming model. These are

  • Granularity-based. Activities can be added to or removed from a WF program but properties on an existing activity (with the exception of declarative conditions and rule sets, which can be updated) cannot be changed.

  • Composition-based. A composite activity whose type constructor defines its set of child activities cannot have activities dynamically added or removed (at any depth). Thus, you can dynamically add and remove child activities to and from a Sequence activity within a WF program instance but you cannot do this to an Echo activity (a derivative of Sequence that always contains a ReadLine and a WriteLine) within a WF program. This protects the integrity of custom composite activity types.

  • Automaton-based. An activity with one or more instances in the Executing state (or Faulting state, or Canceling state, or Compensating state) cannot be removed from a WF program instance.

The author of a WF program can also participate in the determination of whether a set of proposed changes is allowed. An ActivityCondition can be attached to the root activity of a WF program, which at runtime is used by the WF runtime as a conditional check on whether a dynamic change can take place for a specific WF program instance. The condition is an attached dependency property named Condition that is defined by the WorkflowChanges class (see Listing 8.15). The following XAML snippet shows how the property is attached to the root activity in a WF program:

 <Interleave x: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow" xmlns="http://EssentialWF/Activities">   <wf:WorkflowChanges.Condition>     ...   </wf:WorkflowChanges.Condition>     ... </Interleave> 


Like any condition, this attached condition can be realized as a CodeCondition or as a RuleConditionReference, or even as a custom derivative of Activity-Condition. As an example of a custom condition type, Listing 8.16 shows a simple derivative of ActivityCondition that holds a constant Boolean value.

Listing 8.16. ConstantCondition

 using System; using System.Workflow.ComponentModel; namespace EssentialWF.Activities {   public class ConstantCondition : ActivityCondition   {     public static readonly DependencyProperty       BooleanValueProperty = DependencyProperty.Register(         "BooleanValue",         typeof(bool),         typeof(ConstantCondition),         new PropertyMetadata(DependencyPropertyOptions.Metadata)       );     public bool BooleanValue     {       get { return (bool) GetValue(BooleanValueProperty);}       set { SetValue(BooleanValueProperty, value); }     }     public override bool Evaluate(Activity activity,       IServiceProvider provider)     {       return this.BooleanValue;     }   } } 


If WorkflowChanges.Condition is configured as a ConstantCondition that always evaluates to false, any proposed change (to an instance of this WF program type) will be rejected. For example, here is the implementation of a Constant-Condition that always rejects proposed changes to the WF program:

 <Interleave x:Name="i1" xmlns="http://EssentialWF/Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow">   <wf:WorkflowChanges.Condition>     <ConstantCondition BooleanValue="false" />   </wf:WorkflowChanges.Condition>   ... </Interleave> 


If we attempt to make dynamic changes to an instance of this WF program, we need to be prepared to handle a System.InvalidOperationException when we try to apply the changes (this is a slight modification to the code snippet shown at the beginning of this topic):

  // Apply the changes, for this instance  try  {    instance.ApplyWorkflowChanges(changes);  }  catch (InvalidOperationException e)  {    Console.WriteLine(e.Message);  } 


When the WorkflowInstance.ApplyWorkflowChanges method is called, our ConstantCondition is invoked as part of the validation of the proposed changes. Because our condition implementation always evaluates to false, the proposed changes are always rejected.

The value of the Message property of the InvalidOperationException that we catch will look like this:

 Workflow changes can not be applied to instance'c762f961-548b-464e-a898- 2fbe627f6845' at this time. The WorkflowChanges Condition property on the root activity has evaluated to false. 


Activity developers can also participate in the validation of proposed changes. As we learned in Chapter 7, activities are associated with validator components. When a set of changes is proposed for a WF program instance, the entire activity tree is validatedjust as at design-timeto ensure that the WF program remains valid after the changes. Additionally, the validator component of each composite activity to which a child activity is being added (or from which a child activity is being removed) will have its ValidateActivityChange method called.

Here is a simple validator component for Interleave that allows dynamic removal of child activities (subject to aforementioned constraints) but disallows dynamic addition of child activities:

 using System; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Compiler; namespace EssentialWF.Activities {   public class InterleaveValidator : CompositeActivityValidator   {     public InterleaveValidator()       : base()     {     }     public override ValidationError ValidateActivityChange(       Activity activity, ActivityChangeAction action)     {       if (action is AddedActivityAction)         return new ValidationError("Interleave won't allow dynamic addition of   child activities!", 1000);       return base.ValidateActivityChange(activity, action);     }   }   [ActivityValidator(typeof(InterleaveValidator))]   public class Interleave : CompositeActivity   {     ...   } } 


The action parameter to the ValidateActivityChange method is either of type AddedActivityAction or of type RemovedActivityAction (both of which are defined in the System.Workflow.ComponentModel namespace). These types provide information about the activity that is being dynamically added or removed from the composite activity. In our simple example, we reject all proposed additions of child activities to Interleave.

Now, when we call WorkflowInstance.ApplyWorkflowChanges in our host application, we must also be prepared to handle an exception of type WorkflowValidationFailedException (this type is defined in the System.Workflow.ComponentModel.Compiler namespace):

 // Apply the changes, for this instance try {   instance.ApplyWorkflowChanges(changes); } catch (InvalidOperationException e) {   Console.WriteLine(e.Message); } catch (WorkflowValidationFailedException e) {   foreach (ValidationError error in e.Errors)     Console.WriteLine(error); } 


Let's assume that we change the logic of the ConstantCondition associated with the WF program to allow dynamic changes (evaluate to true). When we propose to dynamically add a child activity to an Interleave, the WF program logic allows it, but the InterleaveValidator logic rejects it. We will see the following output at the console:

 error 1000: Interleave won't allow dynamic addition of child activities! 


There is one more detail to take care of. When the dynamic addition of a WriteLine activity to our Interleave composite activity succeeds, the WriteLine needs to be scheduled for execution if the Interleave itself happens to be executing. We can accomplish this by overriding, in our implementation of the Interleave activity (see Chapter 5 for the rest of the implementation), the OnActivityChangeAdd method defined by CompositeActivity:

 namespace EssentialWF.Activities {   public class Interleave : CompositeActivity   {     ...     protected override void OnActivityChangeAdd(       ActivityExecutionContext context,       Activity addedActivity)     {       if (this.ExecutionStatus == ActivityExecutionStatus.Executing)       {         addedActivity.Closed += this.ContinueAt;         context.ExecuteActivity(addedActivity);       }     }   } } 


In general, composite activity developers must (if their activities allow dynamic changes) determine the appropriate implementation of the OnActivityChangeAdd (and related OnActivityChangeRemove) method when developing their execution logic.




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