Windows Workflow Foundation (WF)

A question of one of my blog readers: what about multiple workflows calling into the same Local Communication Service concerning possible threading and synchronization issues.

Consider the following scenario. Assume you have one WorkflowRuntime in your host application, together with one registered Local Communication Service defined by the following interface contract:

[ExternalDataExchange] interface IBar { void Bar(int i); void Foo(int i); }

Assume Workflow1 relies on the Bar method while Workflow2 relies on the Foo method, with both workflow definitions like the following:

The whileActivity1 has a condition that evaluatues true at all times (read: endless loop). The sequenceActivity1 acts as the container for a set of activities in the body of the loop, and wraps the callExternalMethodActivity1 (that calls the Bar and Foo method for Workflow1 and Workflow2 respectively) as well as the delayActivity1 (that has a 1 second and a 3 second delay for Workflow1 and Workflow2 respectively).

Next, assume the following host application code (note: there's a little issue in the code below since I'm running more than one workflow and the waitHandle would be set upon completion of one of both, causing the app to terminate - since both workflows won't ever terminate due to the endless loop, this doesn't cause further problems in this particular demo):

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) 6 { 7 AutoResetEvent waitHandle = new AutoResetEvent(false); 8 workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();}; 9 workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e) 10 { 11 Console.WriteLine(e.Exception.Message); 12 waitHandle.Set(); 13 }; 14 15 ExternalDataExchangeService edx = new ExternalDataExchangeService(); 16 workflowRuntime.AddService(edx); 17 edx.AddService(new MyService()); 18 19 WorkflowInstance instance1 = workflowRuntime.CreateWorkflow(typeof(TestConcurrency.Workflow1)); 20 instance1.Start(); 21 22 WorkflowInstance instance2 = workflowRuntime.CreateWorkflow(typeof(TestConcurrency.Workflow2)); 23 instance2.Start(); 24 25 waitHandle.WaitOne(); 26 } 27 } 28 }

where MyService implements the IBar interface:

class MyService : IBar { public void Bar(int n) { for (int i = 0; i < 100; i++) { Thread.Sleep(10); Console.Write('#'); } } public void Foo(int n) { for (int i = 0; i < 100; i++) { Thread.Sleep(10); Console.Write('@'); } } }

Question: Predict the console output of the workflow execution above. Tip: what about adding [MethodImpl(MethodImplOptions.Synchronized)] to the method declarations? What about inspecting the ManagedThreadId property of the Thread.CurrentThread in both methods?

Happy threading!

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

Introduction

A reader of mine noticed I'm using Visual Studio 2005 as the tool to create all of my Windows Workflow Foundation related samples, instead of taking my favorite minimal command-line approach to create basic samples. So here it is today: a simple Hello World demo of WF using the command-line.

 

Please welcome WFC

Just like csc.exe is the C#'s programmer best friend, wfc.exe plays that role for WF programmers. Basically it turns a .xoml file (which stands for eXtensible Object Markup Language) into a .dll assembly file representing the workflow you've designed. Below you can see a simple demo.xoml file:

<SequentialWorkflowActivity x:Name="Demo" x:Class="FooBar.Demo"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
   <CodeActivity x:Name="sayHello" ExecuteCode="sayHello_ExecuteCode">
      <x:Code>
         <![CDATA[
            void sayHello_ExecuteCode(object sender, EventArgs e)
            {
               Console.WriteLine("Hello World");
            }

         ]]>
      </x:Code>
   </CodeActivity>
</SequentialWorkflowActivity>

This simple sample can be compiled into a .dll file by invoking the following:

>wfc demo.xoml

In this case, we've been using a CDATA section to put the code inline between the XOML definition. Another approach would be to put the following in the demo.xoml file:

<SequentialWorkflowActivity x:Name="Demo" x:Class="FooBar.Demo"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
   <CodeActivity x:Name="sayHello" ExecuteCode="sayHello_ExecuteCode" />
</SequentialWorkflowActivity>

and to create another file called demo.cs:

using System;

namespace FooBar
{
   partial class Demo
   {
      void sayHello_ExecuteCode(object sender, EventArgs e)
      {
         Console.WriteLine("Hello World");
      }
   }
}

which can be compiled using:

>wfc demo.xoml demo.cs

Notice the use of the partial keyword in the class definition. In the end, the .xoml file is a partial definition of the workflow class, so the .cs file is another part of it and has to be marked as "partial". Both compilations will yield the same assembly:

Using the assembly in a host application is another concern of course. Basically you just have to compile the following piece of code with a reference to the demo.dll file generated above:

using System;
using System.Threading;
using System.Workflow.Runtime;

namespace FooBar
{
   class Hello
   {
      public static void Main()
      {
         using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
         {
            AutoResetEvent waitHandle = new AutoResetEvent(false);

            workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
            {
               waitHandle.Set();
            };

            WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(FooBar.Demo));
            instance.Start();

            waitHandle.WaitOne();
         }
      }
   }
}

which might be a little difficult to add the reference to the System.Workflow assemblies which reside in the GAC. A more convenient demo approach is to merge the host application and the code-behind for the workflow into one single .cs file, like this:

using System;
using System.Threading;
using System.Workflow.Runtime;

namespace FooBar
{
   class Hello
   {
      public static void Main()
      {
         using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
         {
            AutoResetEvent waitHandle = new AutoResetEvent(false);

            workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
            {
               waitHandle.Set();
            };

            WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(FooBar.Demo));
            instance.Start();

            waitHandle.WaitOne();
         }
      }
   }

   partial class Demo
   {
      void sayHello_ExecuteCode(object sender, EventArgs e)
      {
         Console.WriteLine("Hello World");
      }
   }

}

Now compile this file (demo.cs) together with the .xoml file, as follows:

>wfc /target:exe /r:System.Xml.dll demo.xoml demo.cs

 

MsBuild style

There's of course another approach that leans more towards Visual Studio 2005 compilation, i.e. using MsBuild. Assuming you have a file called demo.xoml:

<SequentialWorkflowActivity x:Name="Demo" x:Class="FooBar.Demo"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
   <CodeActivity x:Name="sayHello" ExecuteCode="sayHello_ExecuteCode">
      <x:Code>
         <![CDATA[
            void sayHello_ExecuteCode(object sender, EventArgs e)
            {
               Console.WriteLine("Hello World");
            }

         ]]>
      </x:Code>
   </CodeActivity>
</SequentialWorkflowActivity>

and a file called hello.cs:

using System;
using System.Threading;
using System.Workflow.Runtime;

namespace FooBar
{
   class Hello
   {
      public static void Main()
      {
         using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
         {
            AutoResetEvent waitHandle = new AutoResetEvent(false);

            workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
            {
               waitHandle.Set();
            };

            WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(FooBar.Demo));
            instance.Start();

            waitHandle.WaitOne();
         }
      }
   }
}

you can create a .csproj build file that contains this:

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <RootNamespace>FooBar</RootNamespace>
    <AssemblyName>Demo</AssemblyName>
    <OutputPath>.</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Xml" />
    <Reference Include="System.Workflow.Activities" />
    <Reference Include="System.Workflow.ComponentModel" />
    <Reference Include="System.Workflow.Runtime" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Hello.cs">
      <DependentUpon>demo.xoml</DependentUpon>
      <SubType>Component</SubType>
    </Compile>
  </ItemGroup>
  <ItemGroup>
    <Content Include="demo.xoml"/>
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
  <Import Project="$(MSBuildExtensionsPath)\Microsoft\Windows Workflow Foundation\v3.0\Workflow.Targets" />
</Project>

which can be built using:

>msbuild

Enjoy command-line compiling WF-apps (for demo purposes only I hope)!

kick it on DotNetKicks.com

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

Introduction

This post builds upon the foundation created in yesterday's post about the HandleExternalEventActivity. Please follow the instructions in that post before continuing with this one. As you've learned from this previous post, waiting for an external event to occur is a very powerful mechanism to drive execution of a workflow. There are a lot of scenarios however where you'll want to wait for more than one event at the same time, or you want to timeout such a wait for an event to get raised. To make this kind of dreams reality, WF has the ListenActivity in its magic toolbox.

The demo

Take the solution you built in the previous post on working with events and go to Workflow1.cs's designer view. It should look like this:

Your next job is to transform this into the following:

An easy way to accomplish this is outlined below:

  • Add a ListenActivity below the WhileActivity.
  • Rename the left branch of the ListenActivity to clientArrived and the right branch to timeoutOccurred.
  • Drag and drop the clientArrival and doWork activities from the while loop to the left branch.
  • Delete the SequenceActivity from the WhileActivity's body.
  • Drag and drop the ListenActivity to the body of our WhileActivity.
  • Add a DelayActivity (set to 10 seconds) called timeout to the right branch, as well as a CodeActivity called oops with the following ExecuteCode handler code:

    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.WriteLine("Oops! Timeout occurred"
    );
    Console.ResetColor();

Basically, the ListenActivity blocks till one branch completes. Notice you can add additional branches, e.g. to listen to three, four, ... events at the same time:

When you try to execute this workflow, you'll see timeouts occurring when the client waits to long to enter her name:

I'm sorry for the defaced console output, but hey that's not the goal of the demo is it?

Conclusion

Waiting for one event to occur is a common requirement; waiting for multiple events can be even a more common requirement. WF makes it really easy to do this, without having to mess around with threading and waithandles (even more: in a stateful long-running world relying on additional runtime services like persistence) using the ListenActivity. I bet you'll find yourself leveraging the power of this activity on a regular basis, especially in combination with a DelayActivity to model timeouts. Now you know everything about waiting for events in WF, it's time to wake up yourself and don't wait any longer to explore WF! Cheers!

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

Introduction

In yesterday's post, you learned that the vast majority of workflows need to exchange data with other parties to get their jobs done. Just one of the benefits of workflows is the possibility to visualize this kind of interaction by means of different activities, like the CallExternalMethodActivity that was explained in the previous post. Based on a contract definition (read: interface) a workflow can be defined while the choice of the service implementation is left as a decision for the workflow host application. In today's post, we continue our journey on the Local Communication Services and External Data Exchange path with the HandleExternalEventActivity.

Scenario

The CallExternalMethodActivity explained in the previous post is used to perform methods calls from inside the workflow to some service. Based on an interface and method contract, the workflow can be defined. At runtime, an appropriate service implementation is attached to the workflow and chosen by the engine to process service requests. An example is a workflow calling to an order processing system in some way defined by the contract (e.g. PlaceOrder).

Beside making calls from the workflow to some party outside, one can also rely on the host application to notify the workflow when it needs to do something. This is done using the HandleExternalEventActivity which makes a workflow block till the corresponding event (again in a contract-based manner) is raised by some service. As an example, think of a workflow waiting for approval based on some event (e.g. OrderApproved).

A simple demo

Workflow definition

As usual on this blog, we'll try to keep things simple and approachable, to focus on the basics of the topic discussed. Create a simple Sequential Workflow Console application called EventsDemo:

Next, create the following workflow definition in Workflow1.cs:

This needs some elaboration. On top, we have some CodeActivity called start with the simple ExecuteCode event handler displayed below. At the bottom, there's a similar activity with the event handler shown below as well:

private void start_ExecuteCode(object sender, EventArgs e)
{
   Console.ForegroundColor = ConsoleColor
.Green;
   Console.WriteLine("Waiting for clients..."
);
   Console
.ResetColor();
}

private void stop_ExecuteCode(object sender, EventArgs
e)
{
   Console.ForegroundColor = ConsoleColor
.Red;
   Console.WriteLine("Served 5 clients; time for early retirement!"
);
   Console
.ResetColor();
}

Next, there's an activity called clientListener of the type WhileActivity. This one just loops till five clients have been served, based on the following Declarative Rule Condition:

this.clientCount < 5

relying on the following private variable in the code-behind:

private int clientCount = 0;

A WhileActivity can only contain one single child activity. Because of this, a SequenceActivity is added to the body of the WhileActivity. Inside this SequenceActivity, two child activities are added:

  • clientArrival is of type HandleExternalEventActivity and will be discussed below shortly
  • doWork is of type CodeActivity and has the following ExecuteCode event handler:

    private void doWork_ExecuteCode(object sender, EventArgs e)
    {
       Console.ForegroundColor = ConsoleColor
    .Yellow;
       Console.WriteLine("Event captured - Hello {0}. You're client number {1}."
    , args.Name, clientCount);
       Console
    .ResetColor();
    }

The core of the workflow is the HandleExternalEventActivity called clientArrival. This activity works in a similar way as the CallExternalMethodActivity and relies on an interface and in this case an event to wait for. The logical next step is to define this interface (IBar.cs):

using System;
using
System.Workflow.Activities;

namespace
EventsDemo
{
   [
ExternalDataExchange
]
   public interface
IBar
   {
      event EventHandler<FooEventArgs
> Foo;
   }

   [
Serializable
]
   public class FooEventArgs :
ExternalDataEventArgs
   {
      public FooEventArgs(Guid instanceId, string name) : base
(instanceId)
      {
         this
.name = name;
      }

      private string
name;

      public string
Name
      {
         get { return
name; }
         set { name = value
; }
      }
   }
}

Notice the interface definition being annotated with the ExternalDataExchangeAttribute, which is required for workflow to communicate with it using Local Communication Services. Next, the defined event has an event arguments object derived from ExternalDataEventArgs. The constructor of this event arguments object is worth to mention because it requires a base call to one of the base class's constructors that require a workflow instance identifier to be passed on:

public FooEventArgs(Guid instanceId, string name) : base(instanceId)
{

This is required for the workflow runtime to be able to correlate the event with the right workflow instance.

When you've defined the interface with the event, you can continue to set up the HandleExternalEvent activity. Start by setting the InterfaceType to the IBar interface. Next, set the EventName to Foo. In order to capture the event arguments in the workflow instance to d something useful with it in a later stage, you can bind the e parameter (à la EventArgs e) to some local variable:

private FooEventArgs args;

public FooEventArgs
Args
{
   get { return
args; }
   set { args = value
; }
}

Finally hook up an event handler for the Invoked event of the HandleExternalEvent activity. This will be used to increment the counter that keeps track of the number of served clients (you could do this inside the doWork CodeActivity too):

private void clientArrival_Invoked(object sender, ExternalDataEventArgs e)
{
   clientCount++;
}

Notice you could use this event handler too in order to extract information from the event args that were raised with the exception.

Finally, the property grid of the HandleExternalEventActivity clientArrival should look like this:

The host

On to the host side now. What we want to do, is ask the end-user for a name and then raise the event to the workflow instance to indicate a "client arrival". The workflow should then proceed in the WhileActivity loop and perform work for the newly arrived user.

To do this, we'll first implement IBar as follows:

class Bar : IBar
{
   public event EventHandler<FooEventArgs
> Foo;

   public void RaiseEvent(Guid instanceId, string
name)
   {
      if (Foo != null
)
      {
         EventHandler<FooEventArgs
> evt = Foo;
         FooEventArgs args = new FooEventArgs
(instanceId, name);
         evt(
null
, args);
      }

   }
}

Next, to establish the communication between the workflow and the "Bar" service, we need to register an ExternalDataExchangeService (from System.Workflow.Activities):

bar = new Bar();
ExternalDataExchangeService svc = new ExternalDataExchangeService
();
workflowRuntime.AddService(svc);
svc.AddService(bar);

I've created bar as a local variable in the host class:

private static Bar bar;

Once the workflow instance is started, we'll start a new thread (rather quick-n-dirty) to accept user input till the workflow terminates:

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(EventsDemo.Workflow1));
instance.Start();

Thread userInput = new Thread
(UserInput);
userInput.Start(instance.InstanceId);

waitHandle.WaitOne();

userInput.Abort();

That is, the background thread userInput is started with a parameter to indicate the workflow instance to raise events in. When the workflow completes (when waitHandle is set), the background thread is aborted (there exist cleaner ways to implement this idea, but for demo purposes this should be okay). This background thread is defined as follows:

static void UserInput(object instanceId)
{
   for
(; ; )
   {
      Thread.Sleep(500);
//dirty demo trick
      Console.Write("User name: "
);
      string name = Console
.ReadLine();
      bar.RaiseEvent((
Guid
) instanceId, name);
   }
}

The core line of code is indicated in bold and does the real work of notifying the workflow instance of the simulated "client arrival". Don't worry about the Thread.Sleep call which is just there to keep the console output nice and smooth in a very dirty way to keep things simple and clean.

Now run the application, it should produce the following output (enter a few names and see what happens):

Conclusion

Local communication between a workflow and external data services is an absolute must for a lot of workflow scenarios. In this post and the previous post, you learned how to establish this kind of communication with a workflow in both directions, by making method calls and by waiting for events to occur. Enjoy!

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

Introduction

Workflows don't stand on their own; they need a host application to run in but in a lot of cases there's more: external services are needed to provide functionality to the workflow. You might think, let's just use a CodeActivity to call into some piece of functionality but that doesn't offer enough flexibility in most cases. Okay, you can work with an interface and parameterize the workflow, but still the workflow doesn't reveal its intentions visibly: a CodeActivity is an opaque container where the visualized world of workflows is traded for procedural code again.

Enter the Communication Services, part one. In this post, you'll learn how to exchange data with the workflow by hooking in a "external data exchange service".

CallExternalMethodActivity

At the core of external data exchange is the CallExternalMethodActivity. In a later post, we'll focus on HandleExternalEvent but for now, let's just rely on plain simple method calls. The scenario is the following:

  1. We need a workflow to get the job done (for reasons that may vary a lot, which have been discussed extensively already).
  2. It can't be 100% self-contained and has to rely on external services to process things, for example to make an order.
  3. This external service communication needs a big deal of genericity, so that it can be replaced by an alternative implementation when required. In other words, it needs to be contract-based.

(And no, it isn't a web service we want to call, because in that case we could rely on InvokeWebService.)

Demo

A workflow definition

Right, on to the demo with a somewhat simplified contract: let's call a simple calculator to get things done (I don't want to get the curse of the bad demo gods :-)). Start by creating a simple console-based workflow application:

Next, add a single CallExternalMethodActivity from the toolbox to the workflow definition as shown below:

This is what you should get to see when you set the properties correctly as outlined below. First, go to the code behind of the workflow and add three properties:

private int a;

public int
A
{
   get { return
a; }
   set { a = value
; }
}

private int
b;

public int
B
{
   get { return
b; }
   set { b = value
; }
}

private int
result;

public int
Result
{
   get { return
result; }
   set { result = value
; }
}

Now, add an interface to the project called ICalculator:

[ExternalDataExchange]
interface
ICalculator
{
   int Add(int a, int
b);
   int Subtract(int a, int
b);
   int Multiply(int a, int
b);
   int Divide(int a, int
b);
}

Notice the use of the ExternalDataExchangeAttribute, which lives in System.Workflow.Activities. This indicates the interface acts as the contract for external data exchange.

Now go back to the designer and set the CallExternalMethodActivity properties. Start by specifying the InterfaceType:

Next, set the MethodName to Add. Then, set the parameters a, b and (ReturnValue) that appear in the property grid to point to the properties A, B and Result respectively:

If you've done things correctly, this should result in the following:

Right, so now your workflow knows how to transform some input parameters into the corresponding output.

Letting the host know about data services

That's all what's needed for the workflow definition itself. On to the host now (Program.cs). First, we'll parameterize the workflow instantiation as follows:

Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add(
"A"
, 1);
parameters.Add(
"B"
, 2);

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(ExternalDataDemo.Workflow1
), parameters);
instance.Start();

Next, create some class that implements ICalculator. In this case this is very straightforward since we live in the same project as the workflow definition. In reality you'd have to reference the assembly with the interface definition of course. For the sake of the demo, a simple implementation as a nested class inside Program will meet our needs:

class CalculatorService : ICalculator
{
   #region
ICalculator Members

   public int Add(int a, int
b)
   {
      return
a + b;
   }

   public int Subtract(int a, int
b)
   {
      return
a - b;
   }

   public int Multiply(int a, int
b)
   {
      return
a * b;
   }

   public int Divide(int a, int
b)
   {
      return
a / b;
   }

   #endregion
}

Now it's time to hook in this service to the workflow runtime, so that instances can rely on it to get their jobs done. This is done in the Program class again, as follows:

ExternalDataExchangeService edx = new ExternalDataExchangeService();
workflowRuntime.AddService(edx);
//keep this order!
edx.AddService(new CalculatorService()); //keep this order!

It's important to keep the order of the last two lines, otherwise you'll end up with an exception. Also, import the System.Workflow.Activities namespace that contains the ExternalDataExchangeService class. This class acts as a container for all external data exchange services, i.e. implementations of interfaces that were annotated with the ExternalDataExchangeAttribute earlier on. You can only have one implementation of each service.

Finally, to get the result back, we'll change the WorkflowCompleted event handler as displayed below:

int res = 0;

AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted +=
delegate(object sender, WorkflowCompletedEventArgs e)
{
   res = (
int) e.OutputParameters["Result"
];
   waitHandle.Set();
};

...

In here, res is a variable declared on top of the the Main method which gets printed out at the end of the method:

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(ExternalDataDemo.Workflow1), parameters);
instance.Start();

waitHandle.WaitOne();

Console.WriteLine(res);

The result when executing should be obvious :-).

Conclusion

Exchanging data with external parties should be apparent when looking at the workflow definition. Therefore, one has created the CallExternalMethodActivity to encapsulate such a call for external data. By doing so, external data exchange has the potential to become just yet another service, like tracking and persistence. In the next post, we'll talk about events. Enjoy!

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

Introduction

In the past, I've been talking about making a workflow dynamic by applying changes to it at runtime:

Yesterday, you learned how to use tracking (and persistence) services to visualize what's going on inside a workflow in flight, using the WorkflowMonitor sample application that comes with the Windows SDK. Today, we'll combine both the dynamic adaptation of a workflow with tracking services.

Modifying the WorkflowMonitor

As discussed yesterday, the WorkflowMonitor tracks workflows by polling a tracking database used by WF. To visualize what's a workflow keeping itself busy with, it relies on the workflow definition type that gets loaded in the workflow designer that's rehosted by the sample application. However, when making dynamic changes, the WorkflowMonitor doesn't visualize the modifications made to the workflow, because the type doesn't get reloaded. To make things work in a quick-n-dirty fashion, you'll need to change the WorkflowMonitor sample a bit (one line to be precise).

Go to MainForm.cs and locate the method called UpdateActivities. Change the code as follows (only a few lines are displayed, just enough for you to find the spot where modification is needed):

...

ListViewItem
currentWorkflow = listViewWorkflows.SelectedItems[0];
if (currentWorkflow != null
)
{
   Guid
workflowInstanceId = workflowStatusList[(currentWorkflow.SubItems[0]).Text].InstanceId;

   SqlTrackingWorkflowInstance sqlTrackingWorkflowInstance = null
;
   if (true == monitorDatabaseServiceValue.TryGetWorkflow(workflowInstanceId, out
sqlTrackingWorkflowInstance))
   {
      //Edited by Bart De Smet - 10/05/06
      GetWorkflowDefinition(workflowInstanceId);
      //End edit

      listViewActivities.Items.Clear();
      activityStatusListValue.Clear();

      ...

Dynamic updates

To keep things easy, I'll rely on the results of yesterday's work, so follow those instructions first and make sure the app runs fine. Then go to Workflow1.cs in the TrackingDemoLibrary project and change the ExecuteCode eventhandler for allowAccess like this:

private void allowAccess_ExecuteCode(object sender, EventArgs e)
{
   Console.ForegroundColor = ConsoleColor
.Green;
   Console.WriteLine("You're granted access"
);
   Console
.ResetColor();

   WorkflowChanges wc = new WorkflowChanges(this
);

   CodeActivity hello = new CodeActivity("hello"
);
   hello.ExecuteCode +=
new EventHandler
(hello_ExecuteCode);

   IfElseActivity ageChecker = (IfElseActivity)wc.TransientWorkflow.Activities["ageChecker"];
   IfElseBranchActivity plusEighteen = (IfElseBranchActivity)ageChecker.GetActivityByName("plusEighteen");
   plusEighteen.Activities.Add(hello);

   this
.ApplyWorkflowChanges(wc);
}

Next, add another delay on top of the workflow, set to 10 seconds. This will give us the opportunity to see the dynamic change happen in the WorkflowMonitor:

Recompile and make sure to re-register the workflow definition assembly in the GAC:

You might need to recreate the tracking database too (that the easy way to clean it when doing demos), as explained in the post on Tracking Services. Just drop the SqlTrackingDatabase, create it again and execute the two .sql scripts.

Now start the adapted (see above) WorkflowMonitor and then start the TrackingDemo solution. Keep an eye on the WorkflowMonitor and see what happens:

The result of the execution looks as follows thanks to dynamic adaptation:

Conclusion

Tracking in a world of dynamic updates is even more interesting and with one simple change to the WorkflowMonitor sample code, one creates a very appealing piece of tracking functionality. Have fun!

Del.icio.us | Digg It |