Friday, November 6, 2009

Dynamic Updates in Windows Workflow Foundation (WF)

As we know that workflows created using Windows WF are especially useful for long running processes. If during the execution of these long running processes, any update is required in workflow then we seem to have no option other than aborting the workflows and running them again after making the necessary updates. But you would be glad to know that WF provides us the facility to update a running instance of workflow without aborting it. This called Dynamic Updates in WF.


It must be remembered that the target audience of Windows WF is developers. It has never been intended for end-users. So by dynamic updates, we mean a provision in the framework to provide developer the flexibility of updating any instance of a workflow.


Though it is still a primitive update API but still we can use it for many of our requirements.


What can be updated?

In this discussion, we would be discussing following updates to the workflow instance:

  1. Addition / removal of activities in the workflow
  2. Updating declarative rules


Sources of updates:

There might be following sources from where we can update the workflow:

  1. Hosting application
  2. Within the workflow


WorkflowChanges object

To update the workflow, a WorkflowChanges object is created. This provides a working copy of workflow instance. This can be instantiated by passing in the IRootActivity of your already running workflow in the constructor.


All the requested changes made to the transient workflow, available through WorkflowChanges object, would still be part of this until you apply the changes. In that case the running workflow instance would be updated with all the changes.


WorkflowChanges object allows you to make offline changes to the skeleton copy of the workflow instance. We can make all the changes in this copy and can apply all the changes to the workflow instance at once. This is like a batch update. This would make sure that workflow instance is not busy updating itself for all the transient changes which we might want to roll over.


Restricting Dynamic Updates:

Dynamic updates are good but we don’t want our workflows to be modified very frequently by any users. We want to verify if those changes are legitimate before applying them.


In Windows WF, we can check for special conditions before allowing any changes to our workflow instance. If the changes are not able to fulfill some condition then it could never be applied to our workflow.


For this purpose, there is a property in workflow. This is DynamicUpdateCondition property. We can specify either code or declarative condition for this. Before making update, the workflow runtime would check this condition. If successful then it would allow modification of the workflow instance.


There might be specific conditions you want to check before allowing any application to be modifying your workflow. As in all the conditions, there are two options possible:

  1. Code Condition
  2. Declarative rule condition


Since we know that by dynamic updates, we can modify rule definitions. It would be interesting to check if we can modify DynamicUpdateCondition using dynamic updates. If that is the case then we can turn off this condition whenever we desire.


Validating Structural Changes:

All these conditions are checked when ApplyWorkflowChanges method is called from WorkflowChanges object. In case of failure to verify the conditions, the updates are not applied and an exception of type InvalidOperationException is thrown.


It must be remembered that before applying the changes, they can be validated using validate() method available in WorkflowChanges object. This validates all the structural changes made to the workflow. In case of failed validations, this would return a collection of ValidationError objects. Remember that it would not be verifying the updates as defined in DynamicUpdateCondition within the workflow. So there might be cases when you could validate the changes but would not be able to apply them because of failing the required conditions for such updates.


Namespaces used:

Mainly the following classes in the following namespaces are used for making dynamic updates to the rules:

  1. System.Workflow.Activities.Rules
  2. System.CodeDom

Limitations:

As discussed in the beginning, there are limitations to the updates allowed using Dynamic updates option of Windows WF. I am discussing two which I don’t like.


Adding event handlers:

It is not possible to add code to the event handlers of activities at runtime. E.g. we can not add CodeActivity and assign its Execute event handler to some delegate or lambda expression.


CodeActivity ca = new CodeActivity("MyCodeActivity");

ca.Description = "My Code activity";

ca.ExecuteCode += (sender2, e2) => Console.WriteLine(ca.Description);


The above code would result in an exception when this activity is added to the transient workflow and changes are applied to the workflow instance. This results in serialization issue.


Updating Condition Property of Activity at Runtime

It is not possible to update the condition property of activity at runtime. It releases in an InvalidOperationException exception. This is generally not a big issue because we can change the definition of condition in rules file. So generally it is not needed to reference it to a new definition.


Listing 1: (Creating a new activity in a running workflow instance)

Let us create a sample workflow console application (C#) and name it ConsoleWFAcitiviesDynamicUpdate. The template project would provide a code workflow class and Program.cs, which would instantiate the workflow.

In main method in Program.cs, insert the following code before starting the workflow instance:


System.Workflow.ComponentModel.WorkflowChanges wc = new System.Workflow.ComponentModel.WorkflowChanges(instance.GetWorkflowDefinition());

DelayActivity da = new DelayActivity("myDelayActivity");

da.Description = "My description";

da.TimeoutDuration = new TimeSpan(300);

wc.TransientWorkflow.Activities.Insert(0, da);

try

{

wc.Validate();

instance.ApplyWorkflowChanges(wc);

}

catch (Exception ex)

{

Console.WriteLine(ex.Message);

}


This would add a delay activity in the workflow instance and set the duration of delay as 300. Don’t forget to import Activities namespace in Program.cs using the following statement:


using System.Workflow.Activities;


To verify that it has created the delay activity in the workflow, we go to the workflow, workflow1.cs and create initialize even handler and insert the following code in its body:


Array.ForEach<Activity>(this.Activities.ToArray(), (ac) =>

{

Console.WriteLine(ac.Name);

}

);


Now run the project. You should be able to see “myDelayActivity” in the console window. This code is displaying the name of each activity in the workflow. This is using lambda expression syntax in C#.


You can download this project here: ConsoleWFAcitiviesDynamicUpdate1.zip




Listing 2: (Removing an existing activity)

We create a Console WF application named ConsoleWFModifyActivity and insert a delay activity in the workflow named “mydelayActivity”.

Now insert the following code in main method in Program.cs after creation of workflow instance but before starting it.

System.Workflow.ComponentModel.WorkflowChanges wc = new System.Workflow.ComponentModel.WorkflowChanges(instance.GetWorkflowDefinition());

DelayActivity da =

(DelayActivity)wc.TransientWorkflow.GetActivityByName("mydelayActivity");

da.TimeoutDuration = new TimeSpan(300);

try

{

wc.TransientWorkflow.Activities.Remove(da);

instance.ApplyWorkflowChanges(wc);

}

catch (Exception ex)

{

Console.WriteLine(ex.Message);

}


The above code is getting the delay activity from the WorkflowChanges object and removing it.


To verify if the code works, generate handlers in workflow workflow1.cs. Insert the following code in the intitialize handler:


Array.ForEach<Activity>(this.Activities.ToArray(), (ac) =>

{

Console.WriteLine(ac.Name);

}

);


This code prints the name of all activities in the console window. Since before starting the workflow instance, we have already removed the delay activity, the console window should be blank after the execution completes.


You can get this project from here: ConsoleWFModifyActivity.zip



Listing 3: (Replacing an existing activity)

Windows WF does not directly make this option available using DynamicUpdate but since we have seen in Listing 1 and 2 that we can add and remove any activity at any location. We can combine both to replace an activity. We just have to follow the following steps:


  1. Create a new activity

System.Workflow.ComponentModel.WorkflowChanges wc = new System.Workflow.ComponentModel.WorkflowChanges(instance.GetWorkflowDefinition());

CompositeActivity parentActivity = wc.TransientWorkflow;

DelayActivity newDelayActivity = new DelayActivity("myNewActivity");

newDelayActivity.Description = "My New description";

newDelayActivity.TimeoutDuration = new TimeSpan(300);


  1. Find the required activity to be replaced with the help of GetActivityByName method in TransientWorkflow.

DelayActivity exsitingDelayActivity =

(DelayActivity)parentActivity.GetActivityByName("existingDelayActivity");


  1. Find the index of the activity using different Find… methods available in Activities collection in TransientWorkflow.


int index = parentActivity.Activities.FindIndex((ac) => (ac == exsitingDelayActivity));


  1. Remove the existing activity


parentActivity.Activities.Remove(exsitingDelayActivity);

  1. Insert the new activity at the same index.

parentActivity.Activities.Insert(index, newDelayActivity);

instance.ApplyWorkflowChanges(wc);


To verify, you can insert the following code in initialized event handler of your workflow.


Array.ForEach<Activity>(this.Activities.ToArray(), (ac) =>

{

Console.WriteLine(ac.Name);

});


You can download the example project here: ConsoleWFReplaceActivity



Listing 4: (Updating a declarative Business rule)


We create a console workflow project and name it as WFDynamicRuleUpdate.


Now we create an integer MyNumber. This is defined to be used in the dynamic rule for the IfElse activity that we would create later.


public Int32 MyNumber { get; set; }


We drag and drop an IfElse activity from the toolbox to the designer window and name it as ifElseActivity1. We update the name of “if” branch activity to IfBranchActivity and “else” branch activity to “ElseBranchActivity”.


Now we drag and drop code activities in both of them.


We select the Condition property of “IfBranchActivity” and select “Declarative Rule Condition”. After updating its name as “Condition1”, we define the following rule:


this.MyNumber > 100


We create the following event handler for Code activity nested in the “IfBranchActivity”:


private void codeActivity1_ExecuteCode_1(object sender, EventArgs e)

{

Console.WriteLine("If code executed");

}


We create the following event handler for Code activity nested in the “ElseBranchActivity”:


private void codeActivity2_ExecuteCode(object sender, EventArgs e)

{

Console.WriteLine("Else code executed");

}


Now run the workflow. It would “Else code executed” print on the console.


We generate event handlers for the workflow and insert the following code in the initialize event handler:


WorkflowChanges wfChanges = new WorkflowChanges(this);

RuleDefinitions definitions = (RuleDefinitions)wfChanges.TransientWorkflow.GetValue(RuleDefinitions.RuleDefinitionsProperty);

RuleExpressionCondition cond = definitions.Conditions["Condition1"] as RuleExpressionCondition;

CodeBinaryOperatorExpression ex = cond.Expression as CodeBinaryOperatorExpression;

ex.Right = new CodePrimitiveExpression(2);

try

{

ValidationErrorCollection vr = wfChanges.Validate();

Array.ForEach(vr.ToArray(), (x) => Console.WriteLine(x));

if (vr.Count == 0)

{

ApplyWorkflowChanges(wfChanges);

}

}

catch (Exception exc)

{

Console.WriteLine(exc.Message);

}


The above code get the condition object as defined in the workflow rules. It updates the rule so that instead of checking it for 100, now it is checking it for 2.


Running the workflow executes prints “If code executed” on the console.


You can download the project from: WFDynamicRuleUpdate

5 comments:

John Rusk said...

Is this the "dynamic update" feature which Microsoft dropped from the final release of WF 4? ("Dynamic update" was in some betas, but then they dropped it)

jhonoo said...

Hi! How I can modify a workflow associated with a running application that is based on WF? Thanks in advance. So long.

Muhammad Shujaat Siddiqi said...

John,
Though it was part of beta 1 but, sometime between beta 1 and 2, it was decided to be dropped from WF.

Jhonoo,
Modifying a workflow is very generic. Can you specify what specific changes do you want?

Do Quoc Hung said...

I can not download your example, can you upload again. thanks very much

Muhammad Shujaat Siddiqi said...

It seems my directory structure messed up on blogger and I seem to have lost the project.