Monday, August 28, 2006 11:11 PM bart

WF - How to make a workflow dynamic? - Part 1

Introduction

One of the goals of workflows in general is to make "logic", "business processes", etc more visible by having a graphical representation of these. At design time it's pretty easy to compose a workflow using the Visual Studio 2005 designer but to this extent a workflow stays pretty static. So what about adapting or modifying a workflow at runtime? In this series of posts I'll outline various methodologies to modify a running workflow.

Modification from the inside

One of the options to change a workflow is doing it from the inside. Basically this means that the "basic" logic of the workflow foresees that something might have to be changed and that the workflow modifies itself (from the inside) when certain criteria are met. Assume the following workflow but ignore the disabled activities (marked with a green background) - we'll examine these in another blog post because these illustrate modifying a workflow from the outside:

We have - for illustration purposes and to keep things simple - four CodeActivity activities and one DynamicSequenceActivity. Under normal (non-modification) circumstances the system would execute these one after another in a sequential order; Our goal is to add and remove activities in this workflow.

Modification 1 - Adding another activity

Assume we want to add another activity dynamically in between the SelfModificationActivity and the AnotherModificationActivity (you could choose another position too however). An ideal place to do that is inside the SelfModificationActivity, in a real situation based on some decision logic. In our case, we're just going to make the change under any circumstance. Take a look at the ExecuteCode handler logic for the SelfModificationActivity:

private void SelfModificationActivity_ExecuteCode(object sender, EventArgs e)
{
   Console.WriteLine("This is the SelfModificationActivity speaking"
);

   WorkflowChanges wc = new WorkflowChanges(this
);
   MyActivity ma = new MyActivity("With greetings from SelfModificationActivity"
);
   wc.TransientWorkflow.Activities.Insert(1, ma);

   try
   {
      this
.ApplyWorkflowChanges(wc);
   }
   catch (InvalidOperationException
ex)
   {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("No update allowed - "
+ ex.Message);
      Console
.ResetColor();
   }
}

The most important class to notice in here is the WorkflowChanges class. As the API describes this class is the wrapper around a set of proposed changes to a workflow. Therefore applying the changes using the ApplyWorkflowChanges method can throw an exception - something I'll illustrate later on. In this piece of code I'm just adding another activity on position 1 of the workflow (that is, immediately after the SelfModificationActivity). This index-based modification might not be the most ideal way of working, but it's a nice mind-setting example to start with.

The code displayed above inserts an activity of type MyActivity. What's going on in there isn't very important for the sake of the demo, but here is a possible definition for our demo:

public partial class MyActivity: SequenceActivity
{
   private string
message;

   public
MyActivity()
   {
      InitializeComponent();
   }

   public MyActivity(string message) : this
()
   {
      this
.message = message;
   }

   private void MainActivity_ExecuteCode(object sender, EventArgs
e)
   {
     
Console.WriteLine("This is the MyActivity speaking - "
+ message);
   }
}

Modification 2 - Deleting an activity

Deleting an activity can be an interesting option to skip certain activities under some circumstances where logic isn't expressed in the workflow itself (you might consider an if-else structure in the workflow to get a similar and designer-visible effect). Nevertheless, let's assume you want to remove an activity from the running workflow instance at runtime based on some conditions (of which the logic might be loaded at runtime, e.g. using reflection). More specifically I want to kill the DeadManWalkingAcitivity:

private void DeadManWalkingActivity_ExecuteCode(object sender, EventArgs e)
{
   Console.WriteLine("If you can see this, you are God :o"
);
}

The code to remove this activity will be added to the AnotherModificationActivity's ExecuteCode handler:

private void AnotherModificationActivity_ExecuteCode(object sender, EventArgs e)
{
   Console.WriteLine("Let's kill the dead man"
);

   WorkflowChanges wc = new WorkflowChanges(this
);
   Activity deadman = wc.TransientWorkflow.GetActivityByName("DeadManWalkingActivity"
);
   wc.TransientWorkflow.Activities.Remove(deadman);

   try
   {
      this
.ApplyWorkflowChanges(wc);
   }
   catch (InvalidOperationException
ex)
   {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("No update allowed - "
+ ex.Message);
      Console
.ResetColor();
   }
}

Again, the general approach is the same. Use a WorkflowChanges object, manipulate the TransientWorkflow's Activities collection (in this case by calling Remove) and call ApplyWorkflowChanges passing in the WorkflowChanges object with proposed changes. In this piece of code however, the activity to be removed is retrieved by name, not by some index number using GetActivityByName.

Modification 3 - Using the SequenceActivity

A third possibility is to foresee a placeholder in which activities will be loaded dynamically at runtime. Instead of filling out the "Drop activities here" at design time, we'll add activities in there at runtime. For demo purposes, the code to do this will be added to the DynamicSequenceActivity CodeActivity:

private void DynamicSequenceActivity_ExecuteCode(object sender, EventArgs e)
{
   if (dynamicActivityType == null
)
      return
;

   Type t = Type
.GetType(dynamicActivityType);

   WorkflowChanges wc = new WorkflowChanges(this
);
   Activity a = t.Assembly.CreateInstance(t.FullName) as Activity
;
   ((
SequenceActivity)wc.TransientWorkflow.GetActivityByName("DynamicSequence"
)).Activities.Add(a);

   try
   {
      this
.ApplyWorkflowChanges(wc);
   }
   catch (InvalidOperationException
ex)
   {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("No update allowed - "
+ ex.Message);
      Console
.ResetColor();
   }
}

Instead of just statically adding some activity, we're using reflection in the demo above to load an activity from the type specified as "dynamicActivityType". Again for demo purposes, this variable is just a simple property in the workflow class:

private string dynamicActivityType;

public string
DynamicActivityType
{
   get { return
dynamicActivityType; }
   set { dynamicActivityType = value
; }
}

This property is set when starting the workflow (e.g. upon calling a webservice method) but one can imagine various sources to get this property value from (e.g. a database). To illustrate setting this property, look at the following piece of demo code:

static void Main(string[] args)
{
   using(WorkflowRuntime workflowRuntime = new WorkflowRuntime
())
   {
      AutoResetEvent waitHandle = new AutoResetEvent(false
);
      workflowRuntime.WorkflowCompleted +=
delegate(object sender, WorkflowCompletedEventArgs
e) {waitHandle.Set();};
      workflowRuntime.WorkflowTerminated +=
delegate(object sender, WorkflowTerminatedEventArgs
e)
      {
         Console
.WriteLine(e.Exception.Message);
         waitHandle.Set();
      };

      Dictionary<string, object> arguments = new Dictionary<string, object
>();
     
arguments.Add("DynamicActivityType", "DynamicWf.DynamicallyLoadedActivity, DynamicWf"
);

      WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(DynamicWf.Workflow1
), arguments);
      instance.Start();

      waitHandle.WaitOne();
   }
}

In this case, I'm just referring to "DynamicWf.DynamicallyLoadedActivity, DynamicWf" in the same assembly but you can make it much more flexible of course. You can think of some "DynamicallyLoadedActivity" yourself, just define a custom activity of your choice. An example is (how original again :$) a one-CodeActivity-wrapping custom activity with the CodeActivity's ExecuteCode set so:

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
   Console.ForegroundColor = ConsoleColor.Green;
   Console.WriteLine("Greetings from a dynamic friend!"
);
   Console
.ResetColor();
}

The result

Executing the workflow constructed above (ignore the disabled activities for a while) yields the following result:

This is exactly the result we expected: the MyActivity was inserted on the right position; the "dead man activity" wasn't executed and another activity (DynamicallyLoadedActivity) was - as the name implies - loaded dynamically using reflection.

Changes allowed?

You might be concerned about the fact that a workflow seems to allow dynamic updates at all times, especially when we'll be looking (in a next post) at modifications from the outside. The good news is that there is a way for a workflow to decide on whether it allows updates to be made or not. This is done by setting the DynamicUpdateCondition property of the defined workflow. You can use a "Declarative Rule Condition" or a "Code Condition". Let's choose the latter option for now:

The corresponding code looks as follows:

private void CanBeUpdated(object sender, ConditionalEventArgs e)
{
   e.Result = allowUpdates;
}

Making the decision whether updates are allowed or not can be a complex piece of code of course, but let's stick to simplicity again and just use a boolean property for this purpose:

private bool allowUpdates;

public bool
AllowUpdates
{
   get { return
allowUpdates; }
   set { allowUpdates = value
; }
}

The decision is then communicated back using the ConditionalEventArgs's Result property. To illustrate the result in case of dynamic update denial, consider the following host code that sets the AllowUpdates property at startup of the workflow instance:

static void Main(string[] args)
{
   using(WorkflowRuntime workflowRuntime = new WorkflowRuntime
())
   {
      AutoResetEvent waitHandle = new AutoResetEvent(false
);
      workflowRuntime.WorkflowCompleted +=
delegate(object sender, WorkflowCompletedEventArgs
e) {waitHandle.Set();};
      workflowRuntime.WorkflowTerminated +=
delegate(object sender, WorkflowTerminatedEventArgs
e)
      {
         Console
.WriteLine(e.Exception.Message);
         waitHandle.Set();
      };

      Dictionary<string, object> arguments = new Dictionary<string, object
>();
      arguments.Add("AllowUpdates", false);

     
arguments.Add("DynamicActivityType", "DynamicWf.DynamicallyLoadedActivity, DynamicWf"
);

      WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(DynamicWf.Workflow1
), arguments);
      instance.Start();

      waitHandle.WaitOne();
   }
}

When executing the code now, you'll see the following:

As you can see, every time the ApplyWorkflowChanges method is called, the dynamic update condition is evaluated. In case it evaluates to false, an InvalidOperationException is thrown, which gets caught by our update-code that foresees this possibility:

   try
   {
      this
.ApplyWorkflowChanges(wc);
   }
   catch (InvalidOperationException
ex)
   {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("No update allowed - "
+ ex.Message);
      Console
.ResetColor();
   }

Happy WF-ing!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# Windows Workflow Foundation

Thursday, August 31, 2006 10:26 AM by ScottGu's Blog

Workflow is one of the new core capabilities (along with WPF aka Avalon and WCF aka Indigo) being added

# WF: Windows Workflow Foundation

Thursday, August 31, 2006 3:08 PM by while(availableTime>0) {

Yeah, I know that&#180;s a strange acronym and that it should be WWF, but what would it be of WWF , then?...

# Windows Workflow Foundation

Friday, September 01, 2006 8:44 AM by Tom's corner

# Workflow Foundation Learning - I DID IT!

Saturday, September 16, 2006 11:59 PM by while(availableTime>0) {

Well, today (4th of September 2006, almost the independence day of my country) I decided to learn about...

# WF - How to make a workflow dynamic? - Part 2

Friday, September 29, 2006 1:24 AM by B# .NET Blog

Introduction
One of the goals of workflows in general is to make &quot;logic&quot;, &quot;business processes&quot;, etc&amp;nbsp;more...

# Learning Windows Workflow Foundation &laquo; Angel &#8220;Java&#8221; Lopez on Blog

# Aprendiendo Windows Workflow Foundation

Monday, October 09, 2006 11:58 AM by Angel "Java" Lopez

En estos dias, he ordenado algunos enlaces y recursos sobre el Windows Workflow Foundation, el motor

# 2006 October 09 &laquo; Angel &#8220;Java&#8221; Lopez on Blog

Monday, October 09, 2006 12:04 PM by 2006 October 09 « Angel “Java” Lopez on Blog

# http://bartdesmet.net/blogs/bart/archive/2006/08/28/4322.aspx

# Windows Workflow &laquo; GreenRock Software Code Comment

Wednesday, June 04, 2008 7:09 PM by Windows Workflow « GreenRock Software Code Comment

Pingback from  Windows Workflow &laquo; GreenRock Software Code Comment

# Windows Workflow &laquo; GreenRock Software Code Comment

Wednesday, June 04, 2008 7:10 PM by Windows Workflow « GreenRock Software Code Comment

Pingback from  Windows Workflow &laquo; GreenRock Software Code Comment

# Rehydrating a State Machine Workflow

Friday, March 06, 2009 1:30 PM by Jim @ imason

At first glance, Windows Workflow does a good job of persisting workflows to the database for long-running

# #.think.in infoDose #22 (16th Mar - 20th Mar)

Tuesday, March 24, 2009 3:51 AM by #.think.in

#.think.in infoDose #22 (16th Mar - 20th Mar)

# Iulian Tab??r??: Blog &raquo; Blog Archive &raquo; Windows Workflow Foundation

Pingback from  Iulian Tab??r??: Blog  &raquo; Blog Archive   &raquo; Windows Workflow Foundation

# Rehydrating a State Machine Workflow

Friday, October 09, 2009 11:26 AM by Jim @ imason

At first glance, Windows Workflow does a good job of persisting workflows to the database for long-running