Welcome to Windows Workflow Foundation (WF)
Top Tasks :

WF Community Bloggers

Browse by Tags

All Tags » NetFx3 » Workflow   (RSS)

  • Versioning long running workflows part 4

    Part 1
    Part 2
    Part 3
    Part 4

    In the previous blog posts we made sure we could have multiple versions of the same workflow running side by side. This ability is one of the more powerful concepts of WF and a real must have for long running business applications.

    A quick recap.

    Always version your assemblies by giving them a strong name. Make sure the runtime can find each version of the assembly by pointing the CLR to the right version using the configuration\runtimeassemblyBinding\dependentAssembly\assemblyIdentity\codeBase in your App.Config (or Web.Config in the case of a web application). And make sure you use all types and interfaces from the same version as the workflow or, somewhat easier, stick to using basic CLR types when sending messages.

     

    Great, but what about fixing bugs?

    All the versioning is very nice but the simple fact is that sooner or later you are going to find a bug in your code and need to fix a specific assembly. In that case it would not be very nice if the workflow would keep on running with the buggy code. No in that case you would very much like to be able to dehydrate the worfklows and have them use the patched version of the assembly instead of the original one.

    Fortunately this is easy to do, and again due to the standard binary serialization format Windows Workflow Foundation uses, completely standard .NET.

    Again the trick is versioning the assembly and using the App.Config to redirect the runtime to the correct version. So just as I demonstrated in the previous posts I need to strongly sign the assembly. Next when we want to fix a bug in the assembly we need to update the version number and redirect the CLR to the new version. The last part is done using the following config file:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="WorkflowLibrary1" publicKeyToken="8afb6d596a769080" />
            <bindingRedirect oldVersion="1.0.0.0" newVersion="1.0.1.0"/>
          </dependentAssembly>
        </assemblyBinding>
      </runtime>
    </configuration>
    

    To demonstrate the effect the workflow below is started with a parameter that indicates the assembly version it was when started and prints this, long with the current assembly version, on the screen. as you can see the first workflow was started with version 1.0.0.0 but the assembly actually executing is updated to 1.0.1.0. In contrast the second workflow was created using the assembly 1.0.1.0.

    image

    So why bother to update the version number in the first place?

    After all you could just leave the version number as is and replace the assembly. The main disadvantage is that it makes it harder to see which version was executing when a new bug report comes in. Was this the patched assembly and is the bug still there under specific circumstances? On other words: was the bug fix buggySad. Or is the system still using the original assembly, and was the update not done correctly? Best just to avoid these kind of problems and make sure you can see which version either by looking at the assembly file properties or by having an error handler print all loaded assemblies, including version number.

     

    Enjoy!

    www.TheProblemSolver.nl
    Wiki.WindowsWorkflowFoundation.eu

  • Versioning long running workfows part 3

    Part 1
    Part 2

    In the first article of this series I demonstrated how to get multiple versions of a workflow running side by side in the same  workflow runtime. The most important thing was that you need to keep every version of the assembly around and use the assemblyBinding element in the app.config to let the runtime know where each version was on disk. Once done life was good Smile

    In the second part I demonstrated how a HandleExternalEventActivity was version dependent and you needed to use the version specific service to send a message to the workflow. It worked but the as the code was not exactly pretty life was just ok Sad.

    What is wrong with the HandleExternalEventActivity?

    Well there is nothing really wrong with the HandleExternalEventActivity but it is a very thin layer over the actual workflow structures it tries to hide. And these structures are the workflow queuing mechanism! Internally everything is turned into a message and send through a queue. So if this is only a thin abstraction layer why not use the original API in the first place.

    That is exactly what I advise, leave the external data exchange mechanism for what it is and just create a custom workflow activity.

    What does it take to implement the same behavior using a custom activity? Not a whole lot actually so lets take a look.

    image

    Above is the new workflow with the custom activity.

    Because the custom activity is used as the first child in one of the branches of a ListenActivity it must implement the IEventActivity interface and we must override the Execute method to do something once a message is found in the queue.

    using System;
    using System.Workflow.Activities;
    using System.Workflow.ComponentModel;
    using System.Workflow.Runtime;
    
    namespace WorkflowLibrary1
    {
        public partial class MyActivity : Activity, IEventActivity
        {
            public static string TheQueueName = "MyActivityQueueName";
    
            protected override ActivityExecutionStatus Execute(
                ActivityExecutionContext executionContext)
            {
                WorkflowQueuingService wqs = executionContext.GetService<WorkflowQueuingService>();
                WorkflowQueue queue = wqs.GetWorkflowQueue(QueueName);
                object data = queue.Dequeue();
                Console.WriteLine("Received {0} in {1}", data, GetType().Assembly.FullName);
                return base.Execute(executionContext);
            }
    
            public IComparable QueueName
            {
                get { return TheQueueName; }
            }
    
            public void Subscribe(
                ActivityExecutionContext parentContext, 
                IActivityEventListener<QueueEventArgs> parentEventHandler)
            {
                WorkflowQueuingService wqs = parentContext.GetService<WorkflowQueuingService>();
                WorkflowQueue queue = null;
                if (wqs.Exists(TheQueueName))
                    queue = wqs.GetWorkflowQueue(TheQueueName);
                else
                    queue = wqs.CreateWorkflowQueue(QueueName, true);
                queue.RegisterForQueueItemAvailable(parentEventHandler);
            }
    
            public void Unsubscribe(
                ActivityExecutionContext parentContext, 
                IActivityEventListener<QueueEventArgs> parentEventHandler)
            {
                WorkflowQueuingService wqs = parentContext.GetService<WorkflowQueuingService>();
                WorkflowQueue queue = wqs.GetWorkflowQueue(TheQueueName);
                queue.UnregisterForQueueItemAvailable(parentEventHandler);
            }
        }
    }
    

     

    I am not going to explain the details except that the message is read in the Execute and is printed as is along with the assembly version. And guess what, If I create multiple versions of the workflow and run them side by side life is good Smile.

    image

    Sending the data was easy too with only the following code:

    static void SendEvent1(WorkflowRuntime workflowRuntime, Guid instanceId)
    {
        WorkflowInstance instance = workflowRuntime.GetWorkflow(instanceId);
        instance.EnqueueItem(MyActivity.TheQueueName, 1, null, null);
    }
    

    However the data send in this simple example is only an integer. Lets see what happens when we use a custom object instead of the single integer.

    The case of the custom message type

    In the previous example everything worked just fine because we only send in a real simple data type, an integer. However when we switch to a custom type things are less perfect Sad.

    For this example I am using the following data type:

    namespace WorkflowLibrary1
    {
        public class MyData
        {
            public MyData(int data)
            {
                TheData = data;
            }
    
            public int TheData { get; set; }
    
            public override string ToString()
            {
                return string.Format("Data = {0}", TheData);
            }
        }
    }
    

    Still real simple but non the less a custom type we can version along with the workflow and its activities. The code to send the message becomes as follows:

    static void SendEvent1(WorkflowRuntime workflowRuntime, Guid instanceId)
    {
        WorkflowInstance instance = workflowRuntime.GetWorkflow(instanceId);
        MyData data = new MyData(1);
        instance.EnqueueItem(MyActivity.TheQueueName, data, null, null);
    }
    

    Again not a spectacular change as we only substitute the integer for an object of type MyData. The activity execute changes to the following:

    protected override ActivityExecutionStatus Execute(
        ActivityExecutionContext executionContext)
    {
        WorkflowQueuingService wqs = executionContext.GetService<WorkflowQueuingService>();
        WorkflowQueue queue = wqs.GetWorkflowQueue(QueueName);
        MyData data = (MyData)queue.Dequeue();
        Console.WriteLine("Received {0} in {1}", data, GetType().Assembly.FullName);
        return base.Execute(executionContext);
    }
    

    Again no big change, all we are doing is casting the data from the queue to be of type MyData. When we run this with a workflow started using the latest version everything is just fine but when I send a message to a workflow version 1.0.0.0 we receive the following InvalidCastException message:

    Unable to cast object of type 'WorkflowLibrary1.MyData' to type 'WorkflowLibrary1.MyData'

    image

    That message seems kind of weird as it is claiming that we cannot cast MyData to MyData!. Weird as this may seem it is completely true!

    The problem, and things would have been clearer of the message include this information is that we cannot cast between two different versions of the same type as they are really different types.

    The solution

    Just like the previous time the solution is to create an object of the same type as was used in the custom workflow activity. The concept is pretty much the same as last time with the ExternalDataExchangeService and requires a bit of reflection.

    static void SendEvent2(WorkflowRuntime workflowRuntime, Guid instanceId)
    {
        WorkflowInstance instance = workflowRuntime.GetWorkflow(instanceId);
        Assembly assembly = instance.GetWorkflowDefinition().GetType().Assembly;
        Type type = assembly.GetType(typeof(MyData).FullName);
        object data =Activator.CreateInstance(type, new object[] {1});
        instance.EnqueueItem(MyActivity.TheQueueName, data, null, null);
    }
    
    using this code both the workflow and the runtime are perfectly happy. That said, personally I don't really like having to resort to reflection every time Sad

    image

    So instead of using typed objects you might just want to resort to using basic framework objects which will remain the same version until a major .NET framework upgrade. One easy way to send data is just embed it in an XML document or, just as the workflow parameters, a Dictionary<string, object> and pass that along.

    Enjoy!

    www.TheProblemSolver.nl
    Wiki.WindowsWorkflowFoundation.eu

  • A bit more about using TransactionScopeActivity within a ReceiveActivity

    Part 1
    part 2
    Part 3
    Part 4

    You may recall my previous posts about using the TransactionScopeActivity within a ReceiveActivity and all the nasty issues we ran into. Just in case you want to read them again: one, two and three.

    As it turn out this is an intentionally not supported scenario at the moment. If we read the docs for the ReceiveActivity we can find the following note:

    To ensure that persistence performs properly and does not persist transient messages, make sure that child activities of the ReceiveActivity do not persist by themselves. This can occur if the child activities go idle when a persistence provider was created with UnloadOnIdle set to true, for instance.

    The note uses the UnloadOnIdle as an example but the persistence part is just as much true for the TransactionScopeActivity as this persists the state. Of course there is no similar warning in the TransactionScopeActivity docs Sad and the activity validation fails to warn us either so there is definitely some room for improvement here.

    But rumor has it that this improvement is coming Smile The rumor says that not only will we be able use a TransactionScopeActivity inside of a ReceiveActivity but we can even go a step further in being able to flow a transaction from the client through the WCF request into the workflow and have the TransactionScopeActivity  participate in the same transaction. Nice but I guess we will have to wait for the PDC before we get all the details.

    Enjoy!

    www.TheProblemSolver.nl
    Wiki.WindowsWorkflowFoundation.eu

  • Versioning long running workflows part 2

    Part 1
    Part 2

    In my previous post I demonstrated how to keep multiple versions of an assembly around and how to use the assemblyBinding element in the app.config to let the runtime load multiple versions of a worklfow. In the end we had both workflows, the first in assembly 1.0.0.0 and the second in assembly 2.0.0.0, running and life seemed to be good Smile

    So is there more to write on the subject? Yes unfortunately there are still some potential problems that need to be addressed Sad.

     

    The pitfalls of External Data Exchange

    Lets take a look at what happens if we add a HandleExternalEventActivity to the mix. This HandleExternalEventActivity can be used to have a workflow react to input from an external data exchange service, sometimes called local communication.

    Lets change the workflow to reflect the following:

    image

    In this workflow I am waiting for either a DelayActivity to fire or an event to be raised from an external service. Like before the activities are emended in a permanent loop so the workflow is never finished.

    I have kept the external data exchange service real simple. The interface looks like this:

    using System;
    using System.Workflow.Activities;
    
    namespace WorkflowLibrary1
    {
        [ExternalDataExchange]
        public interface IMyService
        {
            event EventHandler<MyEventArgs> TheEvent;
        }
    }
    

    The implementation like this:

    using System;
    
    namespace WorkflowLibrary1
    {
        public class MyService : IMyService
        {
            public event EventHandler<MyEventArgs> TheEvent;
    
            public void OnTheEvent(Guid instanceId)
            {
                if (TheEvent != null && instanceId != Guid.Empty)
                {
                    MyEventArgs args = new MyEventArgs(instanceId, DateTime.Now);
                    TheEvent(null, args);
                }
            }
        }
    }
    

    And the event parameter looks like this:

    using System;
    using System.Workflow.Activities;
    
    namespace WorkflowLibrary1
    {
        [Serializable]
        public class MyEventArgs : ExternalDataEventArgs
        {
            public DateTime FiredAt { get; set; }
            public MyEventArgs(Guid instanceId, DateTime firedAt)
                : base(instanceId)
            {
                FiredAt = firedAt;
            }
        }
    }
    

     

    Not much complexity there Smile.

    In the main function the workflow runtime is created and configured again. This time we also need to add the MyService as follows:

    ExternalDataExchangeService edes = new ExternalDataExchangeService();
    workflowRuntime.AddService(edes);
    MyService myService = new MyService();
    edes.AddService(myService);
    

    The complete main function is at the bottom of the post but the most important things are the two Guids. The variable instanceId1 holds the WorkflowInstanceId from a workflow version 1.0.0.0 while the variable instanceId2 holds a WorkflowInstanceId from a workflow version 2.0.0.0. Both of these have been created during two previous runs and are saved by the SqlWorkflowPersistenceService added to the runtime. So lets see what happens when I run the application.

    image

    As we can see from the screenshot both workflows, the first in assembly 1.0.0.0 and the second in assembly 2.0.0.0, are running together  just fine. So lets see what happens when we raise the event TheEvent as declared in the external data exchange interface.

    image

    As we can see from the screenshot above the second version of the workflow receives the event just fine but version 1.0.0.0 doesn't and instead we receive the following exception:

    Event "TheEvent" on interface type "WorkflowLibrary1.IMyService" for instance id "c9592a1b-e703-4726-b9bb-16410a7aaaad" cannot be delivered.

    Now the workflow did manage to receive the event when it was just created, in fact it could up until the moment we recompiled the application and deployed version 2.0.0.0. Neither the workflow nor the service has changed so what gives?

     

    Underneath the covers of the HandleExternalEventActivity

    To understand the problem we must first understand a bit more about the internals of the HandleExternalEventActivity. When we created the ExternalDataExchange interface we declared an event taking a parameter derived from ExternalDataEventArgs. And when configuring the HandleExternalEventActivity we specified an event name so everything works using .NET events right? Well no, wrong!

    In fact pretty much everything in Windows Workflow Foundation works based on queues. In fact due to the long running nature and the fact you don't really know when thing will execute it must do so. In fact that is the reason why the event parameter must be marked with the Serializable attribute. In fact the ExternalDataExchangeService watches every for every possible event and converts every event into a queued message.

    Okay nice to know but how does that help with this problem?

    Well the thing is it needs to be able to find the correct queue to send the message and that is where things get interesting. When we look at WF queues we see that queue names are of type IComparable. Now most of the time when creating a queue the easiest thing to do is use a string or a guid as the queue name. And as both implement IComparable this is perfectly legal. But in the case of the ExternalDataExchangeService  and the HandleExternalEventActivity both need to be able to construct the same queue name based upon the interface and event name. And this is where the EventQueueName enters.

    The EventQueueName also implements IComparable and is used internally to uniquely identify a queue name. And when the EventQueueName checks if two queues are the same it doesn't just use the interface name and the event name but it also compares the assemblies both are defined in. So in this case the workflow version 1.0.0.0 is creating a queue that contains the fact that it is from version 1.0.0.0 as part of the contract while the runtime only uses the last version this creating a queue that contains version 2.0.0.0 as part of the queue name.

    We can use the following code to pint the queue information from each workflow:

    static void PrintQueues(WorkflowRuntime workflowRuntime, Guid instanceId)
    {
        WorkflowInstance instance = workflowRuntime.GetWorkflow(instanceId);
        ReadOnlyCollection<WorkflowQueueInfo> queues = instance.GetWorkflowQueueData();
        foreach (var queue in queues)
        {
            EventQueueName queueName = queue.QueueName as EventQueueName;
            if (queueName != null)
            {
                Assembly assembly = queueName.InterfaceType.Assembly;
                Console.WriteLine(queueName);
                Console.WriteLine(queueName.InterfaceType.Assembly.FullName);
                Console.WriteLine();
            }
        }
    }
    

    With this as the result:

    image

     

    The solution

    The original function used raise the event looked like this:

    static void SendEvent1(WorkflowRuntime workflowRuntime, Guid instanceId)
    {
        MyService myService = workflowRuntime.GetService<MyService>();
        myService.OnTheEvent(instanceId);
    }
    

    The reason this is failing should now be apparent. After all the main program binds to the version of WorkflowLibrary1 it was build against or version 2.0.0.0. So the GetService() call returns a service object that created a queue name containing version 2.0.0.0 as part of its name and cannot call a HandleExternalEventActivity that creates a queue version 1.0.0.0.

    So the solution is to create the correct service object. Doing so isn't complicated but does unfortunately requires some reflection because we need to work with the same type from an older assembly.

    static void SendEvent2(WorkflowRuntime workflowRuntime, Guid instanceId)
    {
        WorkflowInstance instance = workflowRuntime.GetWorkflow(instanceId);
        Assembly assembly = instance.GetWorkflowDefinition().GetType().Assembly;
        ExternalDataExchangeService edes = workflowRuntime.GetService<ExternalDataExchangeService>();
        Type type = assembly.GetType(typeof(MyService).FullName);
        object myService = edes.GetService(type);
    
        if (myService == null)
        {
            myService = Activator.CreateInstance(type);
            edes.AddService(myService);
        }
    
        MethodInfo mi = type.GetMethod("OnTheEvent");
        mi.Invoke(myService, new object[] { instanceId });
    }
    

    This code first gets an assembly reference to the assembly the actual workflow was defines in. Depending on the workflow this is either going to return assembly version 1.0.0.0 or version 2.0.0.0. Once we have this reference we retrieve the correct service type from the assembly. This type is different for each assembly so when we ask the ExternalDataExchangeService for the service it will try to return one with the correct type information. If this isn't found yet it will return null and we can use the Activator.CreateInstance() to create the correct type adding it to the ExternalDataExchangeService for next time.

    Next we use reflection to execute the OnTheEvent we defined to fire the event for the workflow which now uses the correct version information when creating the queue name and everything works just fine.

    image

    The screenshot above shows that both workflow versions are able to receive events again.

    Conclusion

    The code above works and solves the problem but is not very nice. It assumes that the ExternalDataExchange is defined in the same assembly as the workflow, something that doesn't need to be the case. So is there a better solution? Yes but that is the subject of another blog post.

    Enjoy!

    www.TheProblemSolver.nl
    Wiki.WindowsWorkflowFoundation.eu

     

    The complete main program:

    using System;
    using System.Workflow.Activities;
    using System.Workflow.Runtime;
    using System.Workflow.Runtime.Hosting;
    using WorkflowLibrary1;
    using System.Reflection;
    using System.Collections.ObjectModel;
    
    namespace WorkflowConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
                {
                    ExternalDataExchangeService edes = new ExternalDataExchangeService();
                    workflowRuntime.AddService(edes);
                    MyService myService = new MyService();
                    edes.AddService(myService);
    
                    string connStr = @"Data Source=.\sqlexpress;Initial Catalog=WorkflowPersistence;Integrated Security=True";
                    SqlWorkflowPersistenceService persistence = new SqlWorkflowPersistenceService(connStr,
                        true, TimeSpan.FromSeconds(15), TimeSpan.FromMinutes(1));
                    workflowRuntime.AddService(persistence);
    
                    workflowRuntime.ServicesExceptionNotHandled += (sender, e) =>
                        {
                            Console.WriteLine(e.Exception.Message);
                        };
    
                    workflowRuntime.StartRuntime();
    
                    Guid instanceId1 = new Guid("c9592a1b-e703-4726-b9bb-16410a7aaaad");
                    Guid instanceId2 = new Guid("482e2742-e7c7-45e2-bcd3-8f894d200733");
    
    
                    //WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));
                    //instanceId2 = instance.InstanceId;
                    //instance.Start();
    
    
                    bool done = false;
                    while (!done)
                    {
                        try
                        {
                            ConsoleKeyInfo key = Console.ReadKey(true);
                            switch (key.KeyChar)
                            {
                                case '1':
                                    SendEvent2(workflowRuntime, instanceId1);
                                    break;
    
                                case '2':
                                    SendEvent2(workflowRuntime, instanceId2);
                                    break;
    
                                case '5':
                                    PrintQueues(workflowRuntime, instanceId1);
                                    break;
    
                                case '6':
                                    PrintQueues(workflowRuntime, instanceId2);
                                    break;
    
                                case '0':
                                    done = true;
                                    break;
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
    
                    workflowRuntime.StopRuntime();
                }
            }
    
            static void SendEvent1(WorkflowRuntime workflowRuntime, Guid instanceId)
            {
                MyService myService = workflowRuntime.GetService<MyService>();
                myService.OnTheEvent(instanceId);
            }
    
            static void SendEvent2(WorkflowRuntime workflowRuntime, Guid instanceId)
            {
                WorkflowInstance instance = workflowRuntime.GetWorkflow(instanceId);
                Assembly assembly = instance.GetWorkflowDefinition().GetType().Assembly;
                ExternalDataExchangeService edes = workflowRuntime.GetService<ExternalDataExchangeService>();
                Type type = assembly.GetType(typeof(MyService).FullName);
                object myService = edes.GetService(type);
    
                if (myService == null)
                {
                    myService = Activator.CreateInstance(type);
                    edes.AddService(myService);
                }
    
                MethodInfo mi = type.GetMethod("OnTheEvent");
                mi.Invoke(myService, new object[] { instanceId });
            }
    
            static void PrintQueues(WorkflowRuntime workflowRuntime, Guid instanceId)
            {
                WorkflowInstance instance = workflowRuntime.GetWorkflow(instanceId);
                ReadOnlyCollection<WorkflowQueueInfo> queues = instance.GetWorkflowQueueData();
                foreach (var queue in queues)
                {
                    EventQueueName queueName = queue.QueueName as EventQueueName;
                    if (queueName != null)
                    {
                        Assembly assembly = queueName.InterfaceType.Assembly;
                        Console.WriteLine(queueName);
                        Console.WriteLine(queueName.InterfaceType.Assembly.FullName);
                        Console.WriteLine();
                    }
                }
            }
        }
    }
    
  • Versioning long running workfows

    One of the cool features of Windows Workflow Foundation is that it allows long  running processes. And not just long running as in a few minutes but really long running as in a few years Smile. This is possible true the use of the SqlWorkflowPersistenceService, or in fact any derived class from WorkflowPersistenceService, which is going to save the state of a workflow to disk when it is not actually busy.

    So that is pretty cool but it is kind of unlikely that your programs are not going to change over a year so in all likelihood  you are going to be deploying newer versions of your assemblies while there are multiple workflow's active. In order to allow multiple versions of a workflow to run we need to understand what is going on under the covers.

    To demonstrate the behavior I am going to use a small project with the following layout. The WorkflowConsoleApplication1 is the host and is configured to use SqlWorkflowPersistenceService. The WorkflowLibrary1 is the project containing the actual workflow and this is the project we are going to version.

    image

    The main program is pretty simple and looks like this:

    static void Main(string[] args)
    {
        using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
        {
    
            string connStr = @"Data Source=.\sqlexpress;Initial Catalog=WorkflowPersistence;Integrated Security=True";
            SqlWorkflowPersistenceService persistence = new SqlWorkflowPersistenceService(connStr,
                true, TimeSpan.FromSeconds(15), TimeSpan.FromMinutes(1));
            workflowRuntime.AddService(persistence);
    
            workflowRuntime.ServicesExceptionNotHandled += (sender, e) =>
                {
                    Console.WriteLine(e.Exception.Message);
                };
    
            workflowRuntime.StartRuntime();
    
            WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));
            instance.Start();
    
    
    
            Console.WriteLine("Press ennter to stop");
            Console.ReadLine();
    
            workflowRuntime.StopRuntime();
        }
    }

    Basically it sets up the SqlWorkflowPersistenceService, starts the runtime, starts a new workflow and waits for the user to terminate the program. It also prints any exceptions that might occur in a workflow runtime service like the SqlWorkflowPersistenceService.

    image

    The workflow is real simple. It just loops forever and prints a message that includes the assembly version number every 10 seconds.

    What we are going to do is start this program so we have a first instance in the persistence store, update the workflow assembly to version two an have version 1 and 2 run side by side.

    The WorkflowPersistenceService part.

    The WorkflowPersistenceService actually persists, or dehydrates as it is called, a workflow using the binary serializer. This means that when it recreates, or dehydrates as this is called, the workflow instance it is done in a assembly version depended manner. So the first thing to be aware of is the standard .NET versioning behavior.

    For a .NET assembly to be versionable the first prerequisite is that it has a string name. The rules are simple, no strong name = no versioning and the first assemby found will be the one that is used. Now there is a potential problem here because the binary serializer uses the most compact form possible and doesn't store field names, only their value. The object is responsible for reading the data from a stream in the correct order and size. No problem as long as the same type that was serialized is actually used to deserialize an object. But suppose a newer type is used that actually expects extra fields? Well it is going to try to read some data that is not actually there and the process is going to fail Sad.

    Versioning assemblies is important!

    So in order to be able to version the WorkflowLibrary1 assembly we need to add a strong name. This is done in the project settings on the Signing tab as follows:

    image

    When we run the application we can see from the following output that the workflow version 1 is running just fine Smile.

    image

    Once we have stopped the application its time to upgrade to version 2.0.0.0. Now I am not even going to make any changes to the workflow, all I am going to do is change the assembly major version number of the WorkflowLibrary1 project to 2. This is actually stored as part of the assembly and thus causes the project to recompile.

    image

    If we run the application again we are going to see the following output:

    image

    While version 2.0.0.0 of the workflow will run just fine version 1.0.0.0 cannot be loaded and the SqlWorkflowPersistenceService reports the following error via the WorkflowRuntime ServicesExceptionNotHandled event.

    Could not load file or assembly 'WorkflowLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8afb6d596a769080' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

    This error is the result of the binary serializer seeing that the workflow was dehydrated with version 1.0.0.0 of the WorkflowLibrary1 so it wants to load that version. And that is nowhere to be found as we only have version 2.0.0.0 which is not good enough.

    Enabling side by side execution

    The first ting we need to do is make sure we have both versions of the assembly WorkflowLibrary1 available. In order to do so we need to create a folder in the bin\Debug folder with the name Version_1_0_0_0. This is the folder where I am going to keep a copy of the WorkflowLibrary1.dll version 1.0.0.0.

    image

    Next we need to tell the .NET runtime where to look for version 1.0.0.0 of the assembly by adding this information to the app.config of our application. This is standard .NET material and in no way specific to Windows Workflow Foundation. The app.config looks like this:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="WorkflowLibrary1" publicKeyToken="8afb6d596a769080" />
            <codeBase version="1.0.0.0" href="Version_1_0_0_0/WorkflowLibrary1.dll"/>
          </dependentAssembly>
        </assemblyBinding>
      </runtime>
    </configuration>

    With both versions of the assembly and configuring the runtime so it knows where to find the first version of the assembly both versions of the workflow will run happily together Smile.

    image

     

    So how about the GAC?

    In this example I use a private location to store multiple versions of the assembly but I could also have used the Global Assembly Cache, of GAC for short, to do this. Both mechanisms allow an assembly to be versioned so which is better? Normally the GAC should be used by assemblies used with multiple applications. In this case, and this is probably the case with most workflow assemblies, the WorkflowLibrary1 assembly only used by our host and not by other applications making a private location the proper place. That said there are certainly scenarios, like shared custom activity assemblies, where the GAC would be the proper place.

    So are we all set?

    No unfortunately not quite because there are some more issues that might crop up with versioning. So stay tuned for more Smile

     

    Enjoy!

    www.TheProblemSolver.nl
    Wiki.WindowsWorkflowFoundation.eu

  • Changing a Declarative Rule Condition at runtime

    One powerful feature of a workflow is that a workflow can be changed at runtime. Like other powerful features this is something that should be done with care but it can be a real life saver at times.

    One of the things that you can change are Declarative Rule Conditions. However as these rules are really CodeDom objects it can be a bit tricky to find the right property to change. However once you know a little tick life is much easier.

    The trick is in the .rules file created when you add a Declarative Rule Condition to the workflow and is best demonstrated using a small example. Take the following workflow:

    image

     

    The IfElseActivity has a Declarative Rule Condition which looks like this:

    image

    So I consider every amount larger that 100 large. Hmm not much, lets change that to 1000 instead of 100 Smile

    The first step is we need to change the workflow instance so we need a WorkflowChanges object passing in the workflow root as the activity. No big deal but what now. Well lets open the Workflow1.Rules file and take a look at what is inside.

    <RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
        <RuleDefinitions.Conditions>
            <RuleExpressionCondition Name="LargeAmountRule">
                <RuleExpressionCondition.Expression>
                    <ns0:CodeBinaryOperatorExpression Operator="GreaterThan" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
                        <ns0:CodeBinaryOperatorExpression.Left>
                            <ns0:CodePropertyReferenceExpression PropertyName="Amount">
                                <ns0:CodePropertyReferenceExpression.TargetObject>
                                    <ns0:CodeThisReferenceExpression />
                                </ns0:CodePropertyReferenceExpression.TargetObject>
                            </ns0:CodePropertyReferenceExpression>
                        </ns0:CodeBinaryOperatorExpression.Left>
                        <ns0:CodeBinaryOperatorExpression.Right>
                            <ns0:CodePrimitiveExpression>
                                <ns0:CodePrimitiveExpression.Value>
                                    <ns1:Int32 xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">100</ns1:Int32>
                                </ns0:CodePrimitiveExpression.Value>
                            </ns0:CodePrimitiveExpression>
                        </ns0:CodeBinaryOperatorExpression.Right>
                    </ns0:CodeBinaryOperatorExpression>
                </RuleExpressionCondition.Expression>
            </RuleExpressionCondition>
        </RuleDefinitions.Conditions>
    </RuleDefinitions>

     

    It turns out that the XML is a very literal representation of the objects that make up our rule and what we need to change. So we need to start with a RuleDefinitions.

     

    RuleDefinitions rules =
        (RuleDefinitions)changes.TransientWorkflow.GetValue(RuleDefinitions.RuleDefinitionsProperty);
    

    So getting our hand on it is easy, just use the GetValue function passing in the RuleDefinitionsProperty dependency property.

    Once we have this things are even easier as it is just following the XML Smile. It shows the RuleDefinitions object has a Conditions property and our rule is in there with a name LargeAmountRule and is of type RuleExpressionCondition. This rule in turn contains a left and right part with the right part pointing at the value of 100 we want to change. Again this value is of a type CodePrimitiveExpression, another easy cast. The code below is the complete code required to change the value from 100 to1000.

    WorkflowChanges changes = new WorkflowChanges(this);
    
    RuleDefinitions rules =
        (RuleDefinitions)changes.TransientWorkflow.GetValue(RuleDefinitions.RuleDefinitionsProperty);
    
    RuleExpressionCondition rule = (RuleExpressionCondition)rules.Conditions
        .First(rc => rc.Name == "LargeAmountRule");
    CodeBinaryOperatorExpression expression = (CodeBinaryOperatorExpression)rule.Expression;
    CodePrimitiveExpression primitive = (CodePrimitiveExpression)expression.Right;
    primitive.Value = 1000;
    
    ApplyWorkflowChanges(changes);
    

     

    Easy once you know where to look Smile

    Enjoy!

     

    www.TheProblemSolver.nl
    Wiki.WindowsWorkflowFoundation.eu

  • Using an IronPython runtime service from Windows Workflow Foundation

    In this post I demonstrated how to create IronPython objects and call them from strongly typed .NET code. So suppose we want to do so with Windows Workflow Foundation where could we use this?

    Well the most obvious place would be a runtime service. The example below uses a very simple message that needs to be displayed but it is easy to see how to same concept could be applied in other places.

    image

    But first the basics. I have created a very simple custom DisplayMessageActivity like this:

    public partial class DisplayMessageActivity : System.Workflow.ComponentModel.Activity
    {
        public DisplayMessageActivity()
        {
        }
    
        public string Message { get; set; }
    
        protected override ActivityExecutionStatus Execute(
            ActivityExecutionContext executionContext)
        {
            IDisplayMessageService service = executionContext.GetService<IDisplayMessageService>();
            service.Display(Message);
    
            return base.Execute(executionContext);
        }
    }
    

    Next I have created a very simple workflow with only the DisplayMessageActivity and set the Message property to: "This is the message to show.".

     

    You might have noticed in the code above that I am looking for a service of type IDisplayMessageService. This is pretty simple as well and looks like this:

    public interface IDisplayMessageService
    {
        void Display(string message);
    }
    

    In this case I am using an interface but you could just as well use a concrete baseclass with virtual methods.

    For the actual implementation I created two classes in IronPython, the first displays the message on the console while the second uses a MessageBox to display the same message. The IronPython source looks like this:

    import clr
    
    clr.AddReference('PythonWorkflowConsoleApplication1')
    from PythonWorkflowConsoleApplication1 import IDisplayMessageService
    
    clr.AddReference("System.Windows.Forms")
    from System.Windows.Forms import MessageBox
    
    # Display all messages in the console window
    class ConsoleDisplayMessageService(IDisplayMessageService):
        def Display(self, message):
            print message
    
    # Display all messages using a Windows.Forms.MessageBox
    class MessageBoxDisplayMessageService(IDisplayMessageService):
        def Display(self, message):
            MessageBox.Show(message, 'IronPython')
    

     

    Like in the previous example I first import the clr so I can set references to additional assemblies. Next add a reference to the PythonWorkflowConsoleApplication1 assembly. This is my sample application that contains the runtime service interface definition. With this reference set I can import the IDisplayMessageService interface so I can derive from it.

    I included two implementations, lets first look at the ConsoleDisplayMessageService as this is the simpler of the two. All this does is use the Python print function to display the message on the console. The first parameter, self, is the current object, so this in C# or me in VB. We make sure this class derives from IDisplayMessageService by specifying this after the class name. BTW the lines starting with # are comments in Python.

    The second class is MessageBoxDisplayMessageService and this actually uses a MessageBox to display the message from the workflow. First we need to add a reference to the System.Windows.Forms assembly and once that is done we need to import the MessageBox type. Once this is done we can use it like this:
           MessageBox.Show(message, 'IronPython')

    Simple enough right.

    So now for the trick part, adding the IronPython runtime service to the workflow runtime.

    The console main function is as follows:

    static void Main(string[] args)
    {
        using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
        {
            AddMessageService(workflowRuntime, DisplayMode.Windows);
    
            WorkflowInstance instance = workflowRuntime.CreateWorkflow(
                typeof(PythonWorkflowConsoleApplication1.Workflow1));
            instance.Start();
    
            Console.ReadLine();
        }
    }
    

    With the AddMessageService() doing the actual work. Note the second parameter specifies which IronPython class to load.

    So the AddMessageService looks like this:

    static void AddMessageService(WorkflowRuntime workflowRuntime, DisplayMode mode)
    {
        string className;
        if (mode == DisplayMode.Console)
            className = "ConsoleDisplayMessageService";
        else
            className = "MessageBoxDisplayMessageService";
    
        ScriptRuntime runtime = PythonEngine.CurrentEngine.Runtime;
        ScriptScope scope = runtime.ExecuteFile("DisplayMessageService.py");
    
        PythonType pythonType = scope.GetVariable<PythonType>(className);
        object service = runtime.Operations.Call(pythonType);
    
        workflowRuntime.AddService(service);
    }
    

    The basics are the same as in my previous IronPython post. Depending on the display mode I make a choice of class to load and I use the same code as previous time to create the object. Finally I add it to the WorkflowRuntime as a runtime service. This last bit is easy as this accepts every object type as a valid service and only when we call the GetService do we check the actual type.

    I used IronPython 2.0 Beta 4 for this example. More info about IronPython can be found here, the download of version 2 here.

    Enjoy!

    www.TheProblemSolver.nl
    Wiki.WindowsWorkflowFoundation.eu

  • Red Gate to continue development of .NET Reflector

    .NET Reflector, by Lutz Roeder, must be one of the most useful tools I have when developing .NET code. Usually it is the first thing I install right after Visual Studio not even waiting until I need it because I know I will.

    So the big news is that Red Gate, makers of the Ants profiler and lots of other tools, are taking over from Lutz Roeder and will continue developing .NET Reflector. Interesting move and I hope this means a bright future for the .NET Reflector.

    Read more about this here.

     

    Enjoy!

     

    www.TheProblemSolver.nl
    Wiki.WindowsWorkflowFoundation.eu

  • Book review: Pro WF by Bruce Bukovics

    Windows Workflow Foundation, WF for short, is one of the technologies I work a lot with and as a consequence I own several books about WF. Comparing this book with the others I can only say it is one of the best books you can get on the subject. It has a good coverage of almost all the subject you are going to need to know. Not only is there a good coverage but the explanation and examples are very clear. Now there are a few thing missing from the book. As it was written with the .NET framework 3.0 there is no coverage of the WCF integration that comes in the form of the SendActivity and the ReceiveActivity. While this is unfortunate it is also understandable and I can only hope that the author has the time to update the book with one more chapter.

    For a book this size, about 700 pages, I was amazed at how little I could find that I didn't like. In fact one of few errors I could find is the statement that only a single instance of the WorkflowRuntime can be created per AppDomain. This is a myth that stems back to the first beta's where it was briefly the case but the restriction has been lifted long ago and has never been part of the released product. But given the size of the book and how few people ever need to create multiple WorkflowRuntime objects it is hardly a big objection.

    So my advise: If you are getting into Windows Workflow Foundation make sure you buy this book!

     

    Enjoy.

  • Visual Studio 2008 Service Pack 1 available

    It is available from the subscriptions download at http://msdn.microsoft.com/en-us/subscriptions/default.aspx

     

    Get it while it is hot Smile

     

    Enjoy!

  • More on using a TransactionScopeActivity within a ReceiveActivity

    In a previous blog post I write about what happens when you place a TransactionScopeActivity within a ReceiveActivity and an exception occurs that is supposed to roll back the transaction. In short the story was very bad and we could come up with only a partial workaround, not a pretty sight.

     

    But there is more to it than just that little horror story. Suppose you do the obvious and place the a TransactionScopeActivity within a ReceiveActivity and no exception occurs. Say like the workflow below, please note that the codeActivity1 only sets the return value and causes no error.

     

    image

     

    Now the transaction is committed and the WCF call returns perfectly normally so everything is good right. Well not quite Sad

    The obvious first point is that the TransactionScopeActivity serves no real purpose. After all if it isn't allowed to fail under any circumstances why bother with it in the first place. Well ok there is the point of doing several updates as a single transaction so other users cannot see a partially committed order but that is about it.

    But that is actually the least of my worries as there is a far bigger issue to worry about and that is called workflow persistence.

    Yes that is right. After all when we are using a TransactionScopeActivity workflow persistence is mandatory. The TransactionScopeActivity is decorated with the PersistOnClose attribute so the state of the workflow will be persisted as soon as the transaction is complete. And normally that is a good thing but in this case it is the cause of the second problem because this is still inside the ReceiveActivity. So basically we are storing the workflow state as it is just before returning an answer to the client. Now if everything is running fine that won't matter because the workflow will continue until it is finished and everyone is happy. But suppose the workflow host terminates before the workflow is finished? In this test I added the DelayActivity, and set UnloadOnIdle to false so it doesn't persist the state, giving me the opportunity to kill the workflow runtime. Now if I restart the runtime It is going to reload the last state of the workflow and continue from there. And guess what it's going to do first? You guessed it: it is going to send the response to the client for a second time. Of course the client is no longer around and that action fails with an InvalidOperationException with message "Workflow unloaded between request & response."

    I guess the message is not entirely correct as it should say "Workflow reloaded between request & response." but it's close enough.

    The bottom line is the workflow terminates unless you specifically allow for this to happen and catch the error.

     

    So basically we have a choice between putting the TransactionScopeActivity inside a ReceiveActivity, not being able to throw an error and having a restore problem, or putting the TransactionScopeActivity around the ReceiveActivity, something that only works with a sequential workflow and an initiating ReceiveActivity as I described in the previous post.

    I guess these options make me pretty unhappy Sad.

     

    Enjoy your workflow transactions!

  • Using a TransactionScopeActivity with a WCF ReceiveActivity

    I got an email from a friend last week asking about using a ReceiveActivity and, while receiving, using a TransactionScopeActivity to transitionally save some data in a database. Seems like a common enough scenario right? Well he was having some problems. If everything worked and the transaction succeeded everything was fine and the answer came back. But if an exception occurred and the transaction was aborted be was receiving a real weird error:

    System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: Workflow service unexpectedly unloaded from memory while executing a ReceiveActivity. Make sure that the the workflow does not contain any blocking activities within a ReceiveActivity.  (Fault Detail is equal to An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
    System.InvalidOperationException: Workflow service unexpectedly unloaded from memory while executing a ReceiveActivity. Make sure that the the workflow does not contain any blocking activities within a ReceiveActivity.).

    If we where to believe the message there was a blocking call, something there most certainly was not!

    And to make things more confusing, if we removed the TransactionScopeActivity and just let the exception occur it would bubble back to the client just as it was supposed to. So what gives?

    Well a lot of people looked at this and in the end we declared this a pretty bad bug. Mind you our words not those from Microsoft. But we did find a workaround. So lets take a look at a repro and how to fix this. My original test workflow looked like this:

    image

    I receive a WCF request, start a transaction, determine the return value in a code activity, throw an exception and return. Seems reasonable right? Well I thought so but it produces the error claiming there is a blocking statement. While tracking this Marvin actually noticed that the workflow idle event was raised! Excuse me, a workflow is becoming idle to undo a transaction sounds kind of wrong. And of course when a TransactionScopeActivity is used in a regular workflow, ie non WCF call, there is no Idle event.

     

    A partial solution

    So the way to get this to work is create the following workflow:

    image

    So instead of creating a TransactionScopeActivity inside of my ReceiveActivity I am doing it the other way round. Sounds kind of weird right? Well I think so but it does do the right thing as it returns the correct fault information to the client and then undoes any transactional work done.

     

    So why does this workaround work in this case?

    The ReceiveActivity has its CanCreateInstance property set to true so this is the request that actually creates and starts the workflow. This means that the workflow is created and starts executing from the top. Yes that is right, it starts from the top, not from the ReceiveActivity so any activities before that are also executed. I suppose that potentially opens a can of worms but we will leave that be for the moment. In this case the WCF request is received, this starts the workflow, this in turn starts the transaction, the message is received and processed and the transaction either commits or, like in this case, rolls back.

    How about a non creating ReceiveActivity?

    Well I am afraid no luck there as this workaround isn't going to work then. The problem is that we start a transaction before we start waiting for the additional WCF calls and the TransactionScopeActivity has a a TimoutDuration. So in all likelihood the transaction timeout will occur before the message is received and this effectively cancels the ReceiveActivity meaning it cannot receive the message.

     

    I think this is a pretty major problem with the way WF and WCF work together. After all transactions a an essential piece of business applications and not being able to use them inside of a WCF request is a deadly sin.

     

    Enjoy with case Smile