Welcome to Windows Workflow Foundation (WF)
Top Tasks :

WF Community Bloggers

Browse by Tags

All Tags » VB » DevCenter   (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

  • 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

  • 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

  • CodeCamp 2008

    Afgelopen jaar hebben we het eerste CodeCamp in Nederland georganiseerd en dat was een groot succes. De meeste deelnemers vroegen om meer, sommige zelfs om een CodeCamp per kwartaal of een heel weekend lang. Nou hebben we dat laatste nog niet gedaan maar we zijn wel aan de slag gegaan om een nieuw CodeCamp te organiseren. Als datum hebben we zaterdag 6 september gekozen. Gelukkig waren Microsoft en Class-a behulpzaam en kunnen we, net als vorig jaar, weer in het Microsoft Innovation Center in Barneveld terecht. Een mooie datum en locatie om uitgerust van de vakantie een hoop kennis uit te wisselen.

    Het programma staat nog niet helemaal vast, hou daar de website voor in de gaten, maar je kan er ondermeer de bekende Nederlandse sprekers verwachten. Uiteraard kan je zelf ook nog een sessie voorstellen dus als je een idee hebt voor een leuke sessie laat het dan zo snel mogelijk weten.

     

    Dus zet de datum vast in je agenda en meld je zo snel mogelijk aan op de website www.code-camp.nl.

     

    Het CodeCamp is een gezamenlijk initiatief van de SDN, de stichting DotNed en VB Central.

  • SQL Server Compact Workflow Persistence Service updated

    I have just updated the SQL Server Compact Workflow Persistence Service to better handle deserialization issues when loading a workflow. The service now makes sure the WorkflowRuntime ServicesExceptionNotHandled is raised, something it should have done in the first place.

    You can download the SQL Server Compact Workflow Persistence Service from here.

     

    Enjoy!

    www.TheProblemSolver.nl
    http://wiki.WindowsWorkflowFoundation.eu

  • Unit testing asynchronous workflow activities

    In a previous blog post I demonstrated how TypeMock allowed us to mock out the workflow runtime infrastructure and create true unit tests for a workflow activity. Now this may not seems like a big deal but because most of the classes in the workflow runtime are sealed mocking them with another mocking framework is pretty much impossible. Now you can test custom activities by wrapping them in a test workflow, creating a workflow runtime, running the workflow and inspecting the result I can hardly call this a unit test as we have to create lots of, complex, depended objects. So TypeMock really opens up some new possibilities here.

    But the previous examples where still pretty simple, although mocking the ActivityExecutionContext has always been considered impossible, so lets try something more complicated in the shape of an asynchronous activity. Basically an asynchronous activity is an activity that returns ActivityExecutionStatus.Executing from the Execute method indicating to the workflow runtime that it isn't done yet. The activity uses ActivityExecutionContext at a later point in time to indicate that it is done by calling the CloseActivity function.

    Lets take a quick look at our new activity.

    public partial class WriteLineActivity3 : Activity
    {
        public WriteLineActivity3()
        {
            InitializeComponent();
        }
    
        public string Message { get; set; }
    
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            ActivityExecutionStatus result = ActivityExecutionStatus.Closed;
    
            if (!string.IsNullOrEmpty(Message))
            {
                IComparable queueName = Guid.NewGuid();
                WorkflowQueuingService wqs = executionContext.GetService<WorkflowQueuingService>();
                WorkflowQueue queue = wqs.CreateWorkflowQueue(queueName, true);
                queue.QueueItemAvailable += new EventHandler<QueueEventArgs>(queue_QueueItemAvailable);
    
    
                WriteLineService3 service = executionContext.GetService<WriteLineService3>();
    
                service.WriteLine(Message, queueName);
                result = ActivityExecutionStatus.Executing;
            }
    
            return result;
        }
    
        void queue_QueueItemAvailable(object sender, QueueEventArgs e)
        {
            ActivityExecutionContext executionContext = sender as ActivityExecutionContext;
            executionContext.CloseActivity();
        }
    }
    

    In this case the return value from the Execute depends on the Message property being filled or not so we need to test this. First a simple test for an activity with an empty message:

     

    /// <summary>
    ///A test for Execute
    ///</summary>
    [TestMethod()]
    [DeploymentItem("WFUnitTest1.exe")]
    [VerifyMocks()]
    public void ExecuteTestEmpty()
    {
        WriteLineActivity3_Accessor target = new WriteLineActivity3_Accessor();
        ActivityExecutionStatus expected = ActivityExecutionStatus.Closed;
        ActivityExecutionStatus actual;
    
        ActivityExecutionContext executionContext = 
    RecorderManager.CreateMockedObject<ActivityExecutionContext>(); WorkflowQueuingService wqs = RecorderManager.CreateMockedObject<WorkflowQueuingService>(); WorkflowQueue queue = RecorderManager.CreateMockedObject<WorkflowQueue>(); WriteLineService3 service = RecorderManager.CreateMockedObject<WriteLineService3>(); using (RecordExpectations recorder = RecorderManager.StartRecording()) { executionContext.GetService<WriteLineService3>(); recorder.FailWhenCalled(); } target.Message = ""; actual = target.Execute(executionContext); Assert.AreEqual(expected, actual); }

    In this test I actually check that the runtime service, called WriteLineService3 in this case, is never retrieved. This test is still pretty simple and no more complicated than the test we did previously.

     

    Lets take a look at something more complicated. The following test has the Message property set so it is expected to call the runtime service. But being an asynchronous activity it is also required to create a WorkflowQueue and bind to its QueueItemAvailable event so we will check that has happened. Finally this time the Execute function should return a status of ActivityExecutionStatus.Executing as it isn't done yet. This test runs like this:

    /// <summary>
    ///A test for Execute
    ///</summary>
    [TestMethod()]
    [DeploymentItem("WFUnitTest1.exe")]
    [VerifyMocks()]
    public void ExecuteTestStart()
    {
        WriteLineActivity3_Accessor target = new WriteLineActivity3_Accessor();
        ActivityExecutionStatus expected = ActivityExecutionStatus.Executing;
        ActivityExecutionStatus actual;
    
    
        ActivityExecutionContext executionContext = 
            RecorderManager.CreateMockedObject<ActivityExecutionContext>();
        WorkflowQueuingService wqs = RecorderManager.CreateMockedObject<WorkflowQueuingService>();
        WorkflowQueue queue = RecorderManager.CreateMockedObject<WorkflowQueue>();
        WriteLineService3 service = RecorderManager.CreateMockedObject<WriteLineService3>();
    
        using (RecordExpectations recorder = RecorderManager.StartRecording())
        {
            // Record methods here
            executionContext.GetService<WorkflowQueuingService>();
            recorder.Return(wqs);
            wqs.CreateWorkflowQueue(null, true);
            recorder.Return(queue);
            queue.QueueItemAvailable += delegate(object sender, QueueEventArgs e) { };
    
            executionContext.GetService<WriteLineService3>();
            recorder.Return(service);
            service.WriteLine(null, null);
        }
    
        target.Message = "A message";
        actual = target.Execute(executionContext);
    
        Assert.AreEqual(expected, actual);
    
    A little more complicated than the previous check but not bad at all Smile

    In this case we are binding to the QueueItemAvailable event so we need to add another test for the event handler and make sure it informs the workflow runtime we are done.

    /// <summary>
    ///A test for queue_QueueItemAvailable
    ///</summary>
    [TestMethod()]
    [DeploymentItem("WFUnitTest1.exe")]
    [VerifyMocks()]
    public void queue_QueueItemAvailableTest()
    {
        WriteLineActivity3_Accessor target = new WriteLineActivity3_Accessor();
        QueueEventArgs e = null; // TODO: Initialize to an appropriate value
    
        ActivityExecutionContext executionContext = 
            RecorderManager.CreateMockedObject<ActivityExecutionContext>();
    
        using (RecordExpectations recorder = RecorderManager.StartRecording())
        {
            executionContext.CloseActivity();
        }
    
        target.queue_QueueItemAvailable(executionContext, e);
    }
    

    Not bad at all Smile

    That leaves us with writing a unit test for the runtime service itself. This runtime service actually looks like this:

    public class WriteLineService3: WorkflowRuntimeService
    {
        public void WriteLine(string message, IComparable queueName)
        {
            Guid instanceId = WorkflowEnvironment.WorkflowInstanceId;
    
            ThreadPool.QueueUserWorkItem(state => WriteLineAsync(message, instanceId, queueName));
        }
    
        private void WriteLineAsync(string message, Guid instanceId, IComparable queueName)
        {
            Thread.Sleep(TimeSpan.FromSeconds(5));
    
            Console.WriteLine(message);
    
            WorkflowInstance instance = GetWorkflow(instanceId);
            instance.EnqueueItem(queueName, null, null, null);
        }
    
        private WorkflowInstance GetWorkflow(Guid instanceId)
        {
            WorkflowInstance result = Runtime.GetWorkflow(instanceId);
            return result;
        }
    }
    

    Not very complicated or realistic for that matter but still lets write a quick unit test for it like this:

    /// <summary>
    ///A test for WriteLine
    ///</summary>
    [TestMethod()]
    [VerifyMocks(10000)]
    public void WriteLineTest()
    {
        WriteLineService3_Accessor target = new WriteLineService3_Accessor(); 
        
        string message = string.Empty; // TODO: Initialize to an appropriate value
        IComparable queueName = null; // TODO: Initialize to an appropriate value
    
        WorkflowInstance instance = RecorderManager.CreateMockedObject<WorkflowInstance>();
    
        using (RecordExpectations recorder = RecorderManager.StartRecording())
        {
            Guid instanceId = WorkflowEnvironment.WorkflowInstanceId;
            recorder.Return(Guid.NewGuid());
    
            target.GetWorkflow(instanceId);
            recorder.Return(instance);
            instance.EnqueueItem(null, null, null, null);
        }
    
        target.WriteLine(message, queueName);
    
    }
    

    The most important thing here is that we can mock the WorkflowInstance object just as easy as the ActivityExecutionContext in the previous tests. I am actually telling TypeMock to wait for 10 seconds with the [VerifyMocks(10000)] attribute. This allows the delay in the service to remain there and still check if the expected response was queued. One thing to note is that I had to create an extra GetWorkflow() function to wrap the real function on the WorkflowRuntime object. I was unable to mock the latter however I am unsure why this is the case and if mocking it is really impossible.

     

    Enjoy!

    www.TheProblemSolver.nl
    http://wiki.WindowsWorkflowFoundation.eu

  • Changing the ActivityDesignerTheme on an activity

    Using an ActivityDesignerTheme is a quick way to customize the appearance of an activity in the workflow designer. But there is a gotcha Sad Because once you applied the theme and have a workflow with the custom activity open in the designer no matter what you do changes will not be applied.

    I am not sure why this is but I suppose the designer caches the color scheme and doesn't see any changes made to it.

    Take the following activity:

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Workflow.ComponentModel;
    using System.Workflow.ComponentModel.Design;
    using System.Drawing.Drawing2D;
    
    namespace WorkflowConsoleApplication4
    {
        [Designer(typeof(MyActivity1ActivityDesigner))]
        public partial class MyActivity: Activity
        {
        }
    
    
        [ActivityDesignerTheme(typeof(MyActivity1ActivityDesignerTheme))]
        public class MyActivity1ActivityDesigner: ActivityDesigner
        {
        }
    
        public class MyActivity1ActivityDesignerTheme : ActivityDesignerTheme
        {
            /// <summary>
            /// Initializes a new instance of the MyActivity1ActivityDesignerTheme class.
            /// </summary>
            public MyActivity1ActivityDesignerTheme(WorkflowTheme theme)
                : base(theme)
            {
                BackColorStart = Color.LightBlue;
                BackColorEnd = Color.Blue;
                BackgroundStyle = LinearGradientMode.ForwardDiagonal;
            }
        }
    }
    

    Which look like this in the designer

    image

    Clearly the background has a blue gradient. Now if I change the theme code to the following:

    public class MyActivity1ActivityDesignerTheme : ActivityDesignerTheme
    {
        /// <summary>
        /// Initializes a new instance of the MyActivity1ActivityDesignerTheme class.
        /// </summary>
        public MyActivity1ActivityDesignerTheme(WorkflowTheme theme)
            : base(theme)
        {
            BackColorStart = Color.LightCyan;
            BackColorEnd = Color.Cyan;
            BackgroundStyle = LinearGradientMode.ForwardDiagonal;
        }
    }
    

    You might expect the activity to change its look to:

    image

    Unfortunately this doesn't happen Sad

    Now you can quit Visual Studio and restart it after which the new style will take affect but there is a quicker way. Right click the activity and select "Select Custom Theme..."

    image

    When the dialog shows just click OK and the designer reads the theme again Smile

     

    image

    Enjoy!

    www.TheProblemSolver.nl
    http://wiki.WindowsWorkflowFoundation.eu

  • Using multiple SetState activities in a StateMachineWorkflow

    When working with state machine workflow's the SetStateActivity is one of the more important activities to use. The main reason is that is the one used to transition from one state to another. You can actually transition state in a different way as well using the StateMachineWorkflowInstance.SetState() function but the SetStateActivity is by far the most often used.

    One of the cool features of the SetStateActivity that may not be immediately obvious is the fact that it doesn't do the state switch immediately. Instead it saved the new target start in an internal property, named NextStateName on the StateMachineExecutionState type to be exact, and only does the state switch when the current EventDrivenActivity or StateInitializationActivity is finished. And just to be clear those are the only two activities where you can use a SetStateActivity.

    image

    So the cool part is that you don't need to have the SetStateActivity right at the bottom of the EventDrivenActivity but it can be anywhere in the execution flow. Something that might make the execution logic quite a bit easier.

    image

    And the second cool behavior is that you can actually execute multiple SetStateActivity objects with different TargetStateName definitions and only the last one will be executed. Again something that might make the logic quite a bit simple as you can set a default TargetStateName