The Problem Solver

Tell me and I will forget
Show me and I will remember
Involve me and I will understand
- Confucius -

Workflow

Note: This blog post is written using the .NET framework 4.0 Beta 2

With Windows Workflow Foundation 3 it was possible to rehost the workflow designer in your own application. But possible is about all there was to say about it as it was pretty hard to do anything beyond the basics.

 

With Windows Workflow Foundation 4 live has become much better on the rehosting front Smile In fact it is possible to create the fully functional and useful workflow editor below in about 200 lines of code. Now that is more like it!

image

 

The WorkflowDesigner

The WorkflowDesigner is the main class to work with. This exposes the actual design surface through the View property and the linked property sheet through the PropertyInspectorView property. Both these properties point to ready to use WPF UIElement’s so, as long as the host used WPF, adding them to a form is easy. And loading or saving a workflow is easy to, all it takes is a Load() and Save() function pointing to a XAML file. The code that does this part is below:

_workflowDesigner = new WorkflowDesigner();
_workflowDesigner.Load(_fileName);
 
var view = _workflowDesigner.View;
Grid.SetColumn(view, 1);
Grid.SetRow(view, 1);
LayoutGrid.Children.Add(view);
 
var propInspector = _workflowDesigner.PropertyInspectorView;
Grid.SetColumn(propInspector, 2);
Grid.SetRow(propInspector, 1);
LayoutGrid.Children.Add(propInspector);

 

DesignerMetadata

One thing that is needed is to register the workflow activity designer metadata. It is just a single call but leaving it out means all activities are just a small collapsed shape and won’t expand.

new DesignerMetadata().Register();

 

Displaying the toolbox with activities

The toolbox on the left if another standard WPF control called the ToolboxControl. Again easy to add to any WPF form. Add to it ToolboxItemWrapper with the activity type to add and you are good to go. Dragging controls onto the design surface just works, no extra work required. In the code below I just scan through all types in a few assemblies and if they are valid activities I add them to the toolbox. Again nice and easy.

var toolbox = new ToolboxControl();
var cat = new ToolboxCategory("Standard Activities");
var assemblies = new List<Assembly>();
assemblies.Add(typeof(Send).Assembly);
assemblies.Add(typeof(Delay).Assembly);
assemblies.Add(typeof(ReceiveAndSendReplyFactory).Assembly);
 
var query = from asm in assemblies
            from type in asm.GetTypes()
            where type.IsPublic &&
            !type.IsNested &&
            !type.IsAbstract &&
            !type.ContainsGenericParameters &&
            (typeof(Activity).IsAssignableFrom(type) ||
            typeof(IActivityTemplateFactory).IsAssignableFrom(type))
            orderby type.Name
            select new ToolboxItemWrapper(type);
 
query.ToList().ForEach(ti => cat.Add(ti));
toolbox.Categories.Add(cat);
Grid.SetColumn(toolbox, 0);
Grid.SetRow(toolbox, 1);
LayoutGrid.Children.Add(toolbox);

 

The current selection

At the top of the form I display the currently selected activity and its parents. The WorkflowDesigner has an Items collection with a bunch of useful objects in there. One of these is a Selection object. We could periodically check this Selection but its even easier to subscribe to chances using the Subscribe() function and passing in a handler that is called whenever the selection changes. Setting up the subscription is another one-liner.

_workflowDesigner.Context.Items.Subscribe<Selection>(SelectionChanged);

The handler itself isn’t very complex either.

private void SelectionChanged(Selection selection)
{
    var modelItem = selection.PrimarySelection;
    var sb = new StringBuilder();
 
    while (modelItem != null)
    {
        var displayName = modelItem.Properties["DisplayName"];
 
        if (displayName != null)
        {
            if (sb.Length > 0)
                sb.Insert(0, " - ");
            sb.Insert(0, displayName.ComputedValue);
        }
 
        modelItem = modelItem.Parent;
    }
 
    CurrentActivityName.Text = sb.ToString();
}

 

Validating the workflow

It would be nice to let the user know if the workflow is valid. Again quite easy to do by adding a IValidationErrorService to the WorkflowDesigner services collection. In this case I added a listbox to the form and let the IValidationErrorService add each error to the ListBox items. No need to call anything, the IValidationErrorService is automatically called as soon as anything in the workflow changes.

var validationErrorService = new ValidationErrorService(WorkflowErrors.Items);
_workflowDesigner.Context.Services.Publish<IValidationErrorService>(validationErrorService);

The IValidationErrorService consists of a single function that gets the list of errors as a parameter.

public class ValidationErrorService : IValidationErrorService
{
    private IList _errorList;
    public ValidationErrorService(IList errorList)
    {
        _errorList = errorList;
    }
 
    public void ShowValidationErrors(IList<ValidationErrorInfo> errors)
    {
        _errorList.Clear();
        foreach (var error in errors)
        {
            _errorList.Add(error.Message);
        }
    }
}

 

Running the workflow

Just to be able to run the workflow I added a bit of code that uses a WorkflowApplication to run the workflow. Loading it is also easy, just call the ActivityXamlServices.Load() passing in the file and it will return a DynamicActivity ready to run.

var writer = new StringWriter();
var workflow = ActivityXamlServices.Load(_fileName);
var wa = new WorkflowApplication(workflow);
wa.Extensions.Add(writer);
wa.Completed = WorkflowCompleted;
wa.OnUnhandledException = WorkflowUnhandledException;
wa.Run();

 

So all together I have a working application that I can use to edit and run workflows. So who still needs Visual Studio 2010?

Sweet Smile

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 4 comment(s)
Filed under: , , , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

One of the things that wasn’t possible in Windows Workflow Foundation 3 was flowing transaction over a WCF service request into a workflow. We could have transactions on the client, we could have transactions on the server but they would not be able to cooperate.

Fortunately that is one the problems that Windows Workflow Foundation 4 solves for us. Smile

 

In WF4 we can use the ReceiveAndSendreply template to configure a workflow as a service and accept WCF calls. That works just fine but doesn’t allow for transactions. If we want to use transactions we need to add the TransactedReceiveScope to the workflow. Move the ReceiveRequest activity into the Request part and the SendResponse activity into the Body part. Don’t forget the last part because leaving the SendResponse below the TransactedReceiveScope means things will not work and the error message will be less that helpful Sad

 

image

This is enough to make all work done during the message receive part of a transaction. To show what is happening with transactions I am using the following code to record and return the transactional status:

using System.Text;
using System.Transactions;
 
public class TransactionReport
{
    public static string GetReport()
    {
        var sb = new StringBuilder();
 
        if (Transaction.Current == null)
        {
            sb.Append("No transaction");
        }
        else
        {
            sb.AppendFormat("TransactionInformation\n");
            sb.AppendFormat("\tLocalIdentifier {0}\n", Transaction.Current.TransactionInformation.LocalIdentifier);
            sb.AppendFormat("\tDistributedIdentifier {0}\n", Transaction.Current.TransactionInformation.DistributedIdentifier);
            sb.AppendFormat("\tStatus {0}\n", Transaction.Current.TransactionInformation.Status);
            sb.AppendFormat("\tIsolationLevel {0}\n", Transaction.Current.IsolationLevel);
        }
 
        return sb.ToString();
    }
}

 

Adding a console application with a service reference with the following code:

using System;
using Client.ServiceReference1;
 
namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            var proxy = new ServiceClient();
            var result = proxy.PostData();
 
            Console.WriteLine(result);
 
 
            Console.ReadLine();
        }
    }
}

Produces the following output:

image

No big surprises there. The only thing I am missing is the ability to control the IsolationLevel which seems to be fixed at Serializable.

 

 

Flowing transactions from the client

The first step we need to take for client transactions to flow to the service is do the service call inside of a transaction using this client code:

using System;
using System.Transactions;
using Client.ServiceReference1;
 
namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
 
            using (var tx = new TransactionScope())
            {
                Console.WriteLine(TransactionReport.GetReport());
 
                var proxy = new ServiceClient();
                var result = proxy.PostData();
 
                Console.WriteLine(result);
 
                Console.WriteLine(TransactionReport.GetReport());
            }
 
            Console.ReadLine();
        }
    }
}

If we run the code now we get the following output:

image

The transaction is there on the client but DistributedIdentifier is still empty so the transaction doesn’t flow to the service yet.

 

Before transactions will flow we need to do two things. First of all we need a WCF binding that can flow transactions. So far we have been using the BasicHttpBinding, the default for an HTTP endpoint in the .NET 4 default configuration, which doesn’t support transactions. So we need to switch to something like WSHttpBinding that does. Next we need to enable transaction flow because it is disabled by default. All of this can be done by adding a little extra configuration to the web.config file.

<bindings>
  <wsHttpBinding>
    <binding transactionFlow="true"/>
  </wsHttpBinding>
</bindings>
<services>
  <service name="Service1">
    <endpoint address=""
              binding="wsHttpBinding"
              contract="IService" />
  </service>
</services>

With this in place all we need to do is update the client service reference and transactions will flow from the client to the service.

image

 

Keep in mind that flowing transactions across a network boundary is a very powerful but also a very dangerous concept. You might be letting someone else, from a different organization, place locks on your database. So make sure you understand the implications of flowing transactions before you open up a whole can of worms!

 

Nice Smile

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments
Filed under: , , , , ,

The recordings from my recent online Windows Workflow Foundation 4 presentation have been posted online. Get them while they are hot Smile

 

You can download the samples and PowerPoint from here.

 

Streaming:

http://developmentor.s3.amazonaws.com/webcasts/Meet-The-New-Workflow-WF4-Maurice-de-Beijer-2009-12-01.wvx

Video Download:

http://developmentor.s3.amazonaws.com/webcasts/Meet-The-New-Workflow-WF4-Maurice-de-Beijer-2009-12-01.zip

Video As WMV (used by streaming link):
http://developmentor.s3.amazonaws.com/webcasts/Meet-The-New-Workflow-WF4-Maurice-de-Beijer-2009-12-01.wmv

MP3 (iTunes Ready):
http://developmentor.s3.amazonaws.com/webcasts/Meet-The-New-Workflow-WF4-Maurice-de-Beijer-2009-12-01.mp3

Download MP3 (iTunes Ready):

http://developmentor.s3.amazonaws.com/webcasts/Meet-The-New-Workflow-WF4-Maurice-de-Beijer-2009-12-01-mp3only.zip

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 1 comment(s)
Filed under: , , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

 

In my previous post about Windows Workflow Foundation 4 I used the CorrelationScope activity to arrange for the activity correlation between a Send and the related ReceiveReply activities. That is quite easy to do and personally I find things nice and easy to arrange with both messaging activities nested in the CorrelationScope activity . The default templates use a slightly different approach though and use a RequestReplyCorrelationInitializer. The end result is the same though so just use what you think best.

 

Just for comparison below is the code to create the same workflow with a RequestReplyCorrelationInitializer.

static Activity CreateWorkflow()
{
    var getDataResult = new Variable<string>();
    var handel = new Variable<CorrelationHandle>();
 
    var send = new Send()
    {
        CorrelationInitializers =
        {
            new RequestReplyCorrelationInitializer()
            { 
                CorrelationHandle = handel
            }
        },
        OperationName = "GetData",
        ServiceContractName = "IService1",
        Endpoint = new Endpoint()
        {
            Binding = new BasicHttpBinding(),
            AddressUri = new Uri("http://localhost:8080/GetDataService/")
        },
        Content = new SendParametersContent()
        {
            Parameters = 
            {
                {"value", new InArgument<int>(42)}
            }
        }
    };
 
    var receive = new ReceiveReply()
    {
        Request = send,
        Content = new ReceiveParametersContent
        {
            Parameters =
            {
                {"GetDataResult", new OutArgument<string>(getDataResult)}
            }
        }
    };
 
    var workflow = new Sequence()
    {
        Variables = { getDataResult, handel },
        Activities = 
        {
            send, 
            receive,
            new WriteLine() { Text = getDataResult } 
        }
    };
 
    return workflow;
}

 

The workflow is even a little simpler because there are fewer activities involved. And this is effectively the same as the SendAndReceiveReply and ReceiveAndSendReply templates generate in WF4.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments
Filed under: , , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

There are two types of correlation to think about in Windows Workflow Foundation:

  • Message correlation
    Basically sending multiple requests to the same workflow.
  • Activity correlation
    Making sure two activities work together.

In this post I am going to show activity correlation

 

Activity correlation is used when multiple activities form a single logical action. Think about the Send and ReceiveReply activities. Two distinct activities but they work together because the first, the Send activity, send a WCF request somewhere and the second, the ReceiveReply activity, receives the response. The same goes when receiving messages.

The good thing is that when we use the ReceiveAndSendReply or SendAndReceiveReply templates everything will be done for us and the required activity correlation will be configured automatically. However sometimes we might need to do so ourselves because we create a workflow in a different way, like using regular code.

 

So when we start using the Send and ReceiveReply or the Receive and SendReply activities from code we need to do a bit more work. How much varies and depends on both the workflow and the hosting environment we use. The big exception to the rule is when we use the WorkflowServiceHost and no parallel or overlapping messaging activities. In that case life is simple and the WorkflowServiceHost takes care of all required correlation for us. Nice Smile

That does leave for quite a few scenarios where we do need to add activity correlation though. The obvious one is using the WorkflowApplication or WorkflowInvoker to run our workflows. Fortunately this produces an System.InvalidOperationException with a message like:

The Send activity is configured with a request/reply Operation 'GetData', however, there is no ReceiveReply activity paired with it. Please pair Send with ReceiveReply and correlate them using a CorrelationHandle.

There are different ways to configure activity correlation but the easiest way is to wrap the activity pair in a CorrelationScope activity. The CorrelationScope will automatically create the required correlation handle and the messaging activities will use this handle.

 

Show me the code

The code below calls a very standard and basic WCF service passing in the value if 42 and prints the result. The important part is that the send are receive actions are done inside of a CorrelationScope activity so this workflow can run in any host.

static Activity CreateWorkflow()
{
    var getDataResult = new Variable<string>();
 
    var send = new Send()
    {
        OperationName = "GetData",
        ServiceContractName = "IService1",
        Endpoint = new Endpoint()
        {
            Binding = new BasicHttpBinding(),
            AddressUri = new Uri("http://localhost:8080/GetDataService/")
        },
        Content = new SendParametersContent()
        {
            Parameters = 
            {
                {"value", new InArgument<int>(42)}
            }
        }
    };
 
    var receive = new ReceiveReply()
    {
        Request = send,
        Content = new ReceiveParametersContent
        {
            Parameters =
            {
                {"GetDataResult", new OutArgument<string>(getDataResult)}
            }
        }
    };
 
    var workflow = new Sequence()
     {
         Variables = { getDataResult },
         Activities = 
        {  
            new CorrelationScope() 
            {
                Body = new Sequence() 
                { 
                    Activities = { send, receive } 
                } 
            }, 
            new WriteLine() { Text = getDataResult } }
     };
 
    return workflow;
}

 

Update: See here for the code when using a RequestReplyCorrelationInitializer.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 1 comment(s)
Filed under: , , , ,

Attached are the PowerPoint sheets and samples from my online Window Workflow Foundation 4 presentation today.

 

Let me know if you have any questions.

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 2 comment(s)
Filed under: , , , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

In Windows Workflow Foundation 4 there are  a number of “activities” in the designer that aren’t really activities but activity templates. The most obvious of there are the ReceiveAndSendReply and the SendAndReceiveReply in the Messaging section of the toolbox.

If you search for either of these activities you are not going to find them. Their real name is ReceiveAndSendReplyFactory and SendAndReceiveReplyFactory respectively and they aren’t activities but activity templates.

 

So how to create an activity template?

Quite simply create a class and implement the IActivityTemplateFactory interface. This interface is simple and consists of a single Create function that returns the activity to be added to the workflow. Need to create multiple activities like the messaging activities do? Just wrap them in a Sequence activity and return that.

So what does that look like? Well in the case of the ReceiveAndSendReplyFactory the Create() looks like this according to Reflector:

public Activity Create(DependencyObject target)
{
    string str = ActivityDesignerHelper.GenerateUniqueVariableNameForContext(target, "__handle");
    Variable<CorrelationHandle> variable = new Variable<CorrelationHandle>
    {
        Name = str
    };
    VisualBasicSettings settings = new VisualBasicSettings
    {
        ImportReferences = 
        { 
            new VisualBasicImportReference 
            { 
                Assembly = requiredAssemblyName, 
                Import = requiredNamespace 
            } 
        }
    };
    Receive receive = new Receive
    {
        OperationName = "Operation1",
        ServiceContractName = XName.Get("IService", "http://tempuri.org/"),
        CorrelationInitializers = 
        { 
            new RequestReplyCorrelationInitializer 
            { 
                CorrelationHandle = new VisualBasicValue<CorrelationHandle> 
                { 
                    ExpressionText = str, 
                    Settings = settings 
                } 
            } 
        }
    };
    return new Sequence
    {
        Variables = { variable },
        Activities = 
        { 
            receive, 
            new SendReply                 
            { 
                DisplayName = "SendReplyToReceive", 
                Request = receive 
            } 
        }
    };
}

 

Nice to know what is happening behind the scenes and IActivityTemplateFactory is a nice interface to have when you start creating your own complex activities.

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments
Filed under: , , , ,

Tomorrow I will be doing an online presentation about Windows Workflow Foundation 4. The meeting lasts 1 hour and starts at 4PM European time, 3PM UTC or 7AM PST.

The goal is to provide an overview of WF4 and how to get started with it in Visual Studio 2010.

 

I will be doing the meeting using LiveMeeting and you can register here.

During the meeting I will also be tracking the Twitter hash tag #dmwf4 for additional questions and feedback.

“See” you there tomorrow Smile

 

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments

Note: This blog post is written using the .NET framework 4.0 Beta 2

I can’t say I am a fan of the way the TryCatch activity is implemented in Windows Workflow Foundation 4.

For starters there is a Finally block where you can add some activities you want to execute. Sounds nice and very much like the try/catch/finally code construct we have in C# or Visual Basic. Except that it behaves in a subtlety different way. With the C# try/catch/finally the finally code will always run, whether you catch the exception or not. In fact you can just use a try/finally and it will work just fine.

With WF4 this is not the case though Sad

The finally activity will only execute if the try block or one of the catch blocks completes. This means that an exception that is unhandled which bubbles terminating the workflow up will not cause the finally block to execute unless the host application forces it to do so by returning UnhandledExceptionAction.Cancel from the OnUnhandledException instead of UnhandledExceptionAction.Terminate (the default). A subtle difference and one that is likely to bite people because the normal code path is usually to have the try activity to complete normally.

 

Another issue I have is with the catch blocks and the cause of the exception.

As with a code catch block you get a reference to the exception. However where you normally would use the stack trace to see where the error occurred this is useless in Windows Workflow Foundation. After all that is only going to show you a few methods in the ActivityExecutor and the activity that was executed, not which instance and where in the activity tree. And by default there is no way to get to the offending activity Sad

 

Turns out that Darren Headrick created a rather nice way to get to the offending activity through workflow tracking. Quite a nice solution but not entirely obvious and I am sad that a solution like this is needed. Still nice work from Darren though Smile

public class WhosFaultWasThat : TrackingParticipant
{
    public string Name { get; set; }
 
    protected override void Track(TrackingRecord trackingRecord, TimeSpan timeout)
    {
        if (trackingRecord is FaultPropagationRecord)
        {
            Name = ((FaultPropagationRecord)trackingRecord).FaultSource.Name;
        }
    }
}

Besides the FaultSource you also get the Fault and a FaultHandler property which will tell you if there is a TryCatch activity that might handle it. I say might because while you can see that there is a TryCatch you can’t quite see its catch blocks so you don’t know if it will be caught or not.

 

This neat trick does show one important thing though.

If you want to know what is going on with your workflow and activities you need to use a TrackingParticipant and add it to the Extensions collections. No matter which execution module you choose, WorkflowInvoker, WorkflowApplication or WorkflowServiceHost, they all support this mechanism.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments
Filed under: , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

The whole persistence model has changed quite a but for WF4.

 

The persistence class out of the box is called SqlWorkflowInstanceStore and as the name suggests it saves workflow data in either SQL Server 2005 or 2008. It is based on the InstanceStore class to if you prefer some other store all you need to do is subclass the InstanceStore and create your own.

 

So what can we do with the SqlWorkflowInstanceStore?

We can attach it to either a WorkflowApplication or a WorkflowServiceHost and persist workflows when we want. Notice I left out the WorkflowInvoker. This can be used to run only short lived workflows and doesn’t support persistence.

The case of the WorkflowApplication is quite simple.

var instanceStore = new SqlWorkflowInstanceStore(connStr);
WorkflowApplication app = new WorkflowApplication(workflow);
app.InstanceStore = instanceStore;
app.Run();

Okay its a little more involved then that.

First of all you need to create a database to store the workflow state in. There are a couple of SQL files in the “C:\Windows\Microsoft.NET\Framework\v4.0.21006\SQL\en” folder. The two you need are SqlWorkflowInstanceStoreSchema.sql and SqlWorkflowInstanceStoreLogic.sql. I created a batch file to quickly recreate my persistence database like this:

osql -E -S .\sqlexpress -Q "Drop Database WorkflowInstanceStore"
osql -E -S .\sqlexpress -Q "Create Database WorkflowInstanceStore"
osql -E -S .\sqlexpress -d WorkflowInstanceStore -i SqlWorkflowInstanceStoreSchema.sql
osql -E -S .\sqlexpress -d WorkflowInstanceStore -i SqlWorkflowInstanceStoreLogic.sql

 

So with all this in place we still need to tell the WorkflowApplication when to persist the workflow. There are several ways to to this but one is to use the PersistableIdle callback. This will only fire when an InstanceStore has been provided. Options are to persist and unload the workflow, just unload the workflow or do nothing at all. The callback is just another function so you can make whatever decisions you like.

app.PersistableIdle = e => PersistableIdleAction.Persist;

Another option is to use the Persist activity. This will allow the workflow to decide on extra persistence points regardless of the workflow being idle.

 

How about the WorkflowServiceHost?

With the WorkflowServiceHost we get a couple of different choices. First of all we can just create a SqlWorkflowInstanceStore and set it like this:

var workflow = new Workflow1();
var baseAddress = new Uri("http://localhost:8080/MyWorkflow");
var host = new WorkflowServiceHost(workflow, baseAddress);
 
var connStr = @"Data Source=.\sqlexpress;Initial Catalog=WorkflowInstanceStore;Integrated Security=True;Pooling=False";
var instanceStore = new SqlWorkflowInstanceStore(connStr);
host.DurableInstancingOptions.InstanceStore = instanceStore;
 
host.Open();
 
Console.WriteLine("Listening...");
Console.ReadLine();
host.Close();

Simple but it doesn’t give us any control over when workflows are persisted. We don’t get quite the same control as with a WorkflowApplication in this case, all we can do is set a few timeout values using the WorkflowIdleBehavior like this:

var workflowIdleBehavior = new WorkflowIdleBehavior();
workflowIdleBehavior.TimeToPersist = TimeSpan.FromSeconds(10);
workflowIdleBehavior.TimeToUnload = TimeSpan.FromMinutes(1);
host.Description.Behaviors.Add(workflowIdleBehavior);

We can’t make any decisions based, just set time the workflow is idle before it is persisted and the same before it is unloaded.

 

Suppose we want some more control over the way the SqlWorkflowInstanceStore behaves. We can using the SqlWorkflowInstanceStoreBehavior. This is actually the same class as is used through the config file.

var connStr = @"Data Source=.\sqlexpress;Initial Catalog=WorkflowInstanceStore;Integrated Security=True;Pooling=False";
var behavior = new SqlWorkflowInstanceStoreBehavior(connStr);
behavior.InstanceCompletionAction = InstanceCompletionAction.DeleteNothing;
behavior.InstanceLockedExceptionAction = InstanceLockedExceptionAction.AggressiveRetry;
behavior.InstanceEncodingOption = InstanceEncodingOption.None;
host.Description.Behaviors.Add(behavior);
 
 

So that is all there is to it?

 
Not quite there is a little complexity when it comes to using the same SqlWorkflowInstanceStore with multiple WorkflowApplication instances. Note that the WorkflowServiceHost automatically takes care of this so no need to worry about that.
 
By default a SqlWorkflowInstanceStore will only work with a single WorkflowApplication. If you try to use it with mutiple workflow you can get a InstancePersistenceCommandException with the following message:
SqlWorkflowInstanceStore does not support creating more than one lock owner concurrently. Consider setting InstanceStore.DefaultInstanceOwner to share the store among many applications.
The trick is to set the DefaultInstanceOwner of the SqlWorkflowInstanceStore. The code isn’t hard but not exactly obvious either.
var instanceStore = new SqlWorkflowInstanceStore(connStr);
 
var instanceHandle = instanceStore.CreateInstanceHandle();
var createOwnerCmd = new CreateWorkflowOwnerCommand();
var view = instanceStore.Execute(instanceHandle, createOwnerCmd, TimeSpan.FromSeconds(30));
instanceStore.DefaultInstanceOwner = view.InstanceOwner;
 
// Do whatever needs to be dome with multiple WorkflowApplications
 
var deleteOwnerCmd = new DeleteWorkflowOwnerCommand();
instanceStore.Execute(instanceHandle, deleteOwnerCmd, TimeSpan.FromSeconds(30));
 
The key is the CreateWorkflowOwnerCommand that needs to be executed at the start. And when you use the CreateWorkflowOwnerCommand just make sure not to forget the DeleteWorkflowOwnerCommand otherwise all workflow will remain locked by the owner and can’t be reloaded by another SqlWorkflowInstanceStore

 

Quite a but more flexible than we had before but, specially with the WorkflowApplication, also quite a bit more work and not always as obvious as I would like it.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 1 comment(s)
Filed under: , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

Flowcharts are a nice addition to Windows Workflow Foundation 4. They allow for a lot of pretty complex behavior that is hard to do in a sequential workflow. In WF 3 we used to model these complex behaviors as state machine workflows. That worked but they weren't really state machines or event driven and things could get a bit tricky.

 

Enter the flowchart in Windows Workflow Foundation 4

One of the good things is that a flowchart is not another workflow type. No it is just another activity to drop in a workflow. So you are free to combine sequential work with a flowchart in one workflow. For example you can have a Sequence activity containing a Flowchart activity and that in turn can contain another Sequence activity which can contain another …. I guess you get the picture.

Below a Flowchart containing a mix of a FlowSwitch and a FlowDecision activity, which are only usable in a Flowchart, and some other general activities like a WriteLine and an Assign activity.

image

 

So far so good.

The one thing I don’t like is the FlowSwitch activity

The problem with the FlowSwitch is that the activity takes an expression determining the branch to execute and each branch has a case property to determine which branch is executed. Sounds like a good plan except when you start trying things. What I did was add a Random variable named rnd to the workflow so different branches would execute. To do this I added the following expression to the FlowSwitch:

rnd.Next(5)

Next in each of the first 3 cases I entered 1, 2 and 3 leaving the 4th as default. Sounds good right?

image

Well not quite!

When I ran the sample I would only get Default printed to the console. Well I guess with Random that is possible to try again. And again.

 

Hmm something must be wrong here.

It turns out all the cases are of type string and the expression is of type Int32. Those are never going to match! But there are no compile or runtime errors either. I am sure this is going to bite me some time in the future Sad

Of course the FlowSwitch should just to a ToString() on the expression result, anything else would be pointless as any comparison fails. But as it doesn’t we have to do so ourselves. So I changed the Expression to:

image

and everything works as expected.

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 2 comment(s)
Filed under: , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

When using Windows Workflow Foundation 4 you often need to enter expression in some activity property. The new thing is these expressions are all in the Visual Basic dialect regardless of what language you project is in. So when developing a project in C# the expressions are sill in Visual Basic.

Weird right?

Well not really when you think about it!

After all power business users are expected to be able to modify workflows. And guess what their favorite tools are. That would be Microsoft Excel and Word. And what do they use to create macros? Right Visual Basic for Applications is their language of choice.

 

So if they use VBA letting them enter VB into expression dialogs is a far more natural fit than having them use C#.

 

A case for the Delay activity

So lets take a look at the delay activity. This activity takes 1 input property Duration of type Time Span. So I guess that means our VBA power users are going to have to enter something like if they want to wait 5 seconds:

image

Now for some reason I suspect that is not the first thing they are going to type. In fact they are more likely to time something like “0:0:05” instead. Not very VB like right?

Well it turns out workflow is smart enough to realize it can convert the string literal to a Time Span and use this. So when our power users does the following it just works Smile

image

Nice, I like this!

 

Enjoy WF4!

 

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 11 comment(s)
Filed under: , , , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

Creating parameters for a workflow is quite easy to do in WF 4. Just open the arguments tab in the workflow and add each input or output parameter you need and the required type.

image

 

Now in WF 3 passing data to a workflow was done using a Dictionary<string, object> to specify the input. This is very flexible but also very brittle as it is all magic of key names matching property names. This approach still works as the following code demonstrates.

var input = new Dictionary<string, object>();
input["FirstName"] = "Maurice";
input["LastName"] = "de Beijer";
var result = WorkflowInvoker.Invoke(new Workflow1(), input);
Console.WriteLine(result["Greeting"]);

 

However WF4 has a new approach that includes full type safety. Every time you create an in or out argument the workflow will also get a corresponding public property. These properties are of type InArgument<T> but they contain an implicit cast from T so the code is quite easy as shown below.

var workflow = new Workflow1();
workflow.FirstName = "Maurice";
workflow.LastName = "de Beijer";
 
var result = WorkflowInvoker.Invoke(workflow);
Console.WriteLine(result["Greeting"]);

 

Much cleaner!

Unfortunately the OutArgument Greeting is there but printing it’s contents to the console only results in the type being printed. There is also a Get() method but this requires an ActivityContext to be passed. So in the case of output we are still restricted to using the Dictionary<string, object>.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments
Filed under: , ,

Op 21 november 2009 organiseren de SDN, Stichting dotNed en VBcentral samen het derde Nederlandse Code Camp. Dit is een dag lang met code, code sharing, freaking en gezellig samenzijn. Een evenement door ontwikkelaars, voor ontwikkelaars. De regie ligt voor een belangrijk deel bij de deelnemers! Het aantal plaatsen voor deelnemers is wel beperkt tot maximaal 150. Wacht dus niet te lang met beslissen want voor je het weet is er geen plaats meer. Vergeet bij je aanmelding niet op te geven welke onderwerpen je interesse hebben. Bovendien nodigen we iedereen graag uit om zelf een sessie in te vullen.


Op de website, www.codecamp.nl, vind je meer informatie en kan je je aanmelden.

Note: This blog post is written using the .NET framework 4.0 Beta 2

In the previous blog posts, here, here and here, I demonstrated how to  use WCF from WF4. This same some more about sending multiple messages to the same workflow, AKA Workflow Correlation.

 

One of the ugly parts of Windows Workflow Foundation 3 was the message correlation part when you used WCF to send multiple messages to the same workflow.

When using WF3 you where forced to use one of the context bindings like BasicHttpContextBinding or WSHttpContextBinding. Not only that but you also had to retrieve and set the correlation context using the IContextManager and set it to some arcane guid. This guid was the workflow instance ID but having to use that on the client means the client had to be very aware of the server technology used, something that goes against the principals of WCF. Using something in the message itself as to route the message to the correct workflow was just not possible with the build-in activities.

 

Fortunately the WF4 story is much better. No longer do we need to use the “context” bindings but the standard WCF bindings will do just fine. And the client doesn’t have to work with the workflow instance id but can pass some more logical data, like an invoice number, that will be mapped to the correct workflow. Much better!

 

To demonstrate this we first need to expand the workflow so it can receive multiple messages for the same workflow. The input for both Receive activities will still be the same person class from the previous example and we will use the persons ID to route the messages.

 

The new workflow definition looks like this:

private static WorkflowElement CreateWorkflow()
{
    var result = new Sequence();
    var input = new Variable<Person>();
    result.Variables.Add(input);
    XNamespace ns = "http://tempuri.org";
 
    var handle1 = new Variable<CorrelationHandle>();
    result.Variables.Add(handle1);
 
    var handle2 = new Variable<CorrelationHandle>();
    result.Variables.Add(handle2);
 
    var receive1 = new Receive()
    {
        OperationName = "Operation1",
        ServiceContractName = ns + "MyService",
        Value = new OutArgument<Person>(input),
        AdditionalCorrelations = {                   
           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle1)}
        },
        CorrelationQuery = CreateCorrelationQuery(),
        CanCreateInstance = true
    };
    result.Activities.Add(receive1);
 
    var write1 = new WriteLine()
    {
        Text = new InArgument<string>(env => string.Format("\tThe workflow was called with '{0}'.", input.Get(env)))
    };
    result.Activities.Add(write1);
 
    var reply1 = new SendReply()
    {
        Request = receive1,
        Value = new InArgument<string>("The result")
    };
    result.Activities.Add(reply1);
 
 
    var receive2 = new Receive()
    {
        OperationName = "Operation2",
        ServiceContractName = ns + "MyService",
        Value = new OutArgument<Person>(input),
        AdditionalCorrelations = {
           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle2)}
        },
        CorrelationQuery = CreateCorrelationQuery()
    };
    result.Activities.Add(receive2);
 
    var write2 = new WriteLine()
    {
        Text = new InArgument<string>(env => string.Format("\tThe workflow was called again with '{0}'.", input.Get(env)))
    };
    result.Activities.Add(write2);
 
    var reply2 = new SendReply()
    {
        Request = receive2,
        Value = new InArgument<string>("The second result")
    };
    result.Activities.Add(reply2);
 
    return result;
}

 

This is very similar to the previous workflow except we have two Receive activities and only the first is configured to allow the message to create a new instance.

 

The trick in getting the second message routed to the same workflow is by setting the CorrelationQuery on the two Receive activities. This is done in the CreateCorrelationQuery() function as follows:

private static CorrelationQuery CreateCorrelationQuery()
{
    var result = new CorrelationQuery();
    var xpath = new XPathMessageQuery()
    {
        Namespaces = new XmlNamespaceManager(new NameTable()),
        Expression = "//sample:Person/sample:Id"
    };
 
    xpath.Namespaces.AddNamespace("sample", "urn:WF4Sample:person");
 
    var messageQuerySet = new MessageQuerySet()
    {
        Name = "RequestCorrelation"
    };
 
    messageQuerySet.Add("PersonId", xpath);
    result.Select = messageQuerySet;
 
    return result;
}

The important part here is that the MessageQuerySet key value and the related Expression XPath must produce the same result for both request. Something that is easy here as I am sending in the same person object twice.

 

So the regular client has hardly any changes. All that is needed is the addition of the second operation to the ServiceContract like this:

[ServiceContract]
interface MyService
{
    [OperationContract]
    Operation1Response Operation1(Person request);
    [OperationContract]
    Operation1Response Operation2(Person request);
}

And calling the second operation like this.

static void Main(string[] args)
{
    var binding = new BasicHttpBinding();
    var endpoint = new EndpointAddress("http://localhost:8090/Sequence1/Operation1");
    var factory = new ChannelFactory<MyService>(binding, endpoint);
    var proxy = factory.CreateChannel();
 
    var request = new Person()
    {
        Id = 23,
        FirstName = "Joe",
        LastName = "Regular",
        BirthDate = new DateTime(1975, 7, 1)
    };
    var result = proxy.Operation1(request);
    Console.WriteLine(result.Value);
 
    //request.Id = 99;
    request.FirstName = "Maurice";
    request.LastName = "Chevalier";
    request.BirthDate = new DateTime(1888, 9, 12);
 
    result = proxy.Operation2(request);
    Console.WriteLine(result.Value);
 
    Console.WriteLine("Regular client is done.");
    Console.ReadLine();
}

Note that I cam completely free to change the person object as long as I use the same Id. Should I change the Id the WorkflowServiceHost would try and find an existing workflow with the changed person Id, fail and throw a FaultException with message “The requested operation could not complete because instance key '4d7e998a-4cff-4743-3dd4-2afd5578383c' could not be found.”. Not quite sure where the Guid comes from but there is no workflow with that instanceId.

The good thing here is that the client doesn’t need to be aware of the workflow implementation on the server and the server can use “regular” message data to determine the correct workflow to send the message to.

 

The Workflow client has about the same change. In this case the Send activity is duplicated but other that that there is very little change and certainly no code that makes the client workflow aware of the service workflow.

private static WorkflowElement CreateWorkflow()
{
    var result = new Sequence();
 
    XNamespace ns = "http://tempuri.org";
 
    var endpoint = new Endpoint()
    {
        Uri = new Uri("http://localhost:8090/Sequence1/Operation1"),
        Binding = new BasicHttpBinding()
    };
 
    var response = new Variable<string>();
    result.Variables.Add(response);
 
    var handle = new Variable<CorrelationHandle>();
    result.Variables.Add(handle);
 
    var person = new Person()
    {
        Id = 7,
        FirstName = "Maurice",
        LastName = "de Beijer",
        BirthDate = new DateTime(1962, 8, 24)
    };
 
    var request = new Send()
    {
        OperationName = "Operation1",
        Endpoint = endpoint,
        AdditionalCorrelations =
        {
           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle)}
        },
        ServiceContractName = ns + "MyService",
        Value = new InArgument<Person>(person)
    };
    result.Activities.Add(request);
 
 
    var reply = new ReceiveReply()
    {
        Request = request,
        Value = new OutArgument<string>(response)
    };
    result.Activities.Add(reply);
 
    var write = new WriteLine()
    {
        Text = new InArgument<string>(response)
    };
    result.Activities.Add(write);
 
    var delay = new Delay()
    {
        Duration = TimeSpan.FromSeconds(5)
    };
    result.Activities.Add(delay);
 
    var request2 = new Send()
    {
        OperationName = "Operation2",
        Endpoint = endpoint,
        AdditionalCorrelations =
        {
           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle)}
        },
        ServiceContractName = ns + "MyService",
        Value = new InArgument<Person>(person)
    };
    result.Activities.Add(request2);
 
 
    var reply2 = new ReceiveReply()
    {
        Request = request2,
        Value = new OutArgument<string>(response)
    };
    result.Activities.Add(reply2);
 
    var write2 = new WriteLine()
    {
        Text = new InArgument<string>(response)
    };
    result.Activities.Add(write2);
 
 
    return result;
}

 

Conclusion.

The WF3/WCF marriage in Wf3.5 left quite a bit to be desired and fortunately live has become a lot simpler with WF4.

 

Enjoy!

 

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 2 comment(s)
Filed under: , , , , ,

In the previous two blog posts (here and here) I showed how to create and expose a Windows Workflow Foundation 4 workflow via WCF and have both a workflow and a regular C# client work with it. But the parameter and return value where real simple with just a string each. So how about passing some more complex data.

 

To show how to do so I will replace the singe string with a person object. Admittedly not the most complex person with only an id, first and last name and their birth date but enough to demonstrate the principal.

 

The main of the service, which uses a WorkflowServiceHost, stays exactly the same so see the first blog post for that part. The CreateWorkflow() function will change now reflecting the fact that the service will no longer expect a string but a person object.

private static WorkflowElement CreateWorkflow()
{
    var result = new Sequence();
    var input = new Variable<Person>();
    result.Variables.Add(input);
    XNamespace ns = "http://tempuri.org";
 
    var handle = new Variable<CorrelationHandle>();
    result.Variables.Add(handle);
 
    var receive = new Receive()
    {
        OperationName = "Operation1",
        ServiceContractName = ns + "MyService",
        Value = new OutArgument<Person>(input),
        AdditionalCorrelations = {                   
           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle)}
        },
        CanCreateInstance = true
    };
    result.Activities.Add(receive);
 
    var write = new WriteLine()
    {
        Text = new InArgument<string>(env => string.Format("\tThe workflow was called with '{0}'.", input.Get(env)))
    };
    result.Activities.Add(write);
 
    var reply = new SendReply()
    {
        Request = receive,
        Value = new InArgument<string>("The result")
    };
    result.Activities.Add(reply);
 
    return result;
}

Not a big change really as we only have to change the input variable from Variable<string> to Variable<Person> and do the same with the Value property of the Receive activity which goes from new OutArgument<string>(input) to new OutArgument<Person>(input).

 

That leaves the Person class and this is just straightforward WCF work by turning the basic class into a data contract.

[DataContract(Namespace = "urn:WF4Sample:person")]
public class Person
{
    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string FirstName { get; set; }
    [DataMember]
    public string LastName { get; set; }
    [DataMember]
    public DateTime BirthDate { get; set; }
 
    public override string ToString()
    {
        return string.Format("{0} {1} was born on {2} (ID: {3})", FirstName, LastName, BirthDate, Id);
    }
}

Again no big deal Smile

 

So with the service done we need to make the same changes to the two client applications. First the workflow client using the Send activity.

The Person class is exactly the same as on the server so just copy the code as is. Again the main in the workflow client doesn’t change, see the first blog post for that part. The part that does change a little is the CreateWorkflow() function so it creates and sends a Person instead of a string. The code goes as follows:

private static WorkflowElement CreateWorkflow()
{
    var result = new Sequence();
    XNamespace ns = "http://tempuri.org";
 
    var endpoint = new Endpoint()
    {
        Uri = new Uri("http://localhost:8090/Sequence1/Operation1"),
        Binding = new BasicHttpBinding()
    };
 
    var response = new Variable<string>();
    result.Variables.Add(response);
 
    var handle = new Variable<CorrelationHandle>();
    result.Variables.Add(handle);
 
    var person = new Person()
    {
        Id = 7,
        FirstName = "Maurice",
        LastName = "de Beijer",
        BirthDate = new DateTime(1962, 8, 24)
    };
 
    var request = new Send()
    {
        OperationName = "Operation1",
        Endpoint = endpoint,
        AdditionalCorrelations =
        {
           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle)}
        },
        ServiceContractName = ns + "MyService",
        Value = new InArgument<Person>(person)
    };
    result.Activities.Add(request);
 
 
    var reply = new ReceiveReply()
    {
        Request = request,
        Value = new OutArgument<string>(response)
    };
    result.Activities.Add(reply);
 
    var write = new WriteLine()
    {
        Text = new InArgument<string>(response)
    };
    result.Activities.Add(write);
 
    return result;
}

Again a very minor change.

 

So how about the C# console client from the second blog post?

The service contract changes a little and is now:

[ServiceContract]
interface MyService
{
    [OperationContract]
    Operation1Response Operation1(Person request);
}

 

The person class is very different from the service though and looks like this:

[MessageContract(WrapperNamespace = "urn:WF4Sample:person")]
internal class Person
{
    [MessageBodyMember(Namespace = "urn:WF4Sample:person")]
    public int Id { get; set; }
    [MessageBodyMember(Namespace = "urn:WF4Sample:person")]
    public string FirstName { get; set; }
    [MessageBodyMember(Namespace = "urn:WF4Sample:person")]
    public string LastName { get; set; }
    [MessageBodyMember(Namespace = "urn:WF4Sample:person")]
    public DateTime BirthDate { get; set; }
}

But besides the difference from using a message contract as opposed to a data contract this isn’t a big deal.

The main program changes a bit but not a whole lot. Again the only changes are due to the fact we are passing in a Person instead of a string.

static void Main(string[] args)
{
    var binding = new BasicHttpBinding();
    var endpoint = new EndpointAddress("http://localhost:8090/Sequence1/Operation1");
    var factory = new ChannelFactory<MyService>(binding, endpoint);
    var proxy = factory.CreateChannel();
 
    var request = new Person()
    {
        Id = 23,
        FirstName = "Joe",
        LastName = "Regular",
        BirthDate = new DateTime(1975, 7, 1)
    };
    var result = proxy.Operation1(request);
    Console.WriteLine(result.Value);
 
    Console.WriteLine("Regular client is done.");
    Console.ReadLine();
}

 

All together not bad.

 

But so far we have only used a very simple workflow with a single message send to it. Suppose you want to send multiple messages to the same workflow? Well in that case we need to use message correlation as we will see next time.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 6 comment(s)

In a previous blog post I described how to use the WorkflowServiceHost and host a workflow with a Receive activity that waits for WCF messages. I also added a WF4 client that called the service and received a response. However a lot of clients out there are not going to be workflows but “regular” code that calls into out workflow. So what does it take to have a simple console application talk to our WF4 service?

 

The first thing we need is an service contract describing the operation we can call. In our workflow the operation is named “Operation1” and our service contact name was “MyService” with the default namespace of “http://tempuri.org”. So our service contract looks like this:

[ServiceContract]
interface MyService
{
    [OperationContract]
    Operation1Response Operation1(Operation1Request request);
}

 

Next up are the request and response objects. In this case I am using just two strings so they are pretty simple as well.

[MessageContract(IsWrapped = false)]
public class Operation1Request
{
    [MessageBodyMember(
        Namespace = "http://schemas.microsoft.com/2003/10/Serialization/",
        Name = "string")]
    public string Value { get; set; }
}
 
[MessageContract(IsWrapped = false)]
public class Operation1Response
{
    [MessageBodyMember(
        Namespace = "http://schemas.microsoft.com/2003/10/Serialization/",
        Name = "string")]
    public string Value { get; set; }
}

The interesting to note here is that I have to use a MessageContact instead of a DataContract and I have to make sure the data isn’t wrapped in an extra element. Also interesting is the fact that the data is send across using the name “string”, not the easiest C# identifier to work with so I called them “Value” instead.

 

Using these WCF definitions creating a simple console app that calls the workflow is a breeze.

static void Main(string[] args)
{
    var binding = new BasicHttpBinding();
    var endpoint = new EndpointAddress("http://localhost:8090/Sequence1/Operation1");
    var factory = new ChannelFactory<MyService>(binding, endpoint);
    var proxy = factory.CreateChannel();
 
    var request = new Operation1Request()
    {
        Value = "Test from a console."
    };
    var result = proxy.Operation1(request);
 
    Console.WriteLine(result.Value);
 
    Console.WriteLine("Regular client is done.");
    Console.ReadLine();
}

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 3 comment(s)
Filed under: , , , , ,

Michael Kennedy, one of the co instructors from DevelopMentor, has created a combined feed from all the blogs of the various DevelopMentor. An awesome group of people with a lot of knowledge to share. Highly recommended in you RSS reader.

http://feeds.feedburner.com/DevelopmentorInstructors

 

If you want to check out a list of the individual blog feeds look here.

 

Enjoy!

 

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

There are several ways to use WCF in combination with Windows Workflow Foundation 4. The two can be combined inside of a XAMLX file as Ron Jacobs describes here.

Another option is using the WF4 Receive and SendReply activities and hosting the workflow yourself using a WorkflowServiceHost. This is quite a useful option but, at least at the moment, not quite straightforward. When I was trying to get things working using a declarative workflow and the designer Visual Studio 2010 would keep in locking up so all workflows in this example will be coded using C#. Not quite the way things are supposed to be done but it does give some useful insight.

 

Creating a workflow to handle requests

The basic activity we need on the service side is the Receive activity. This allows us to wait for incoming requests and process them, either by using a new workflow or an existing one. If we want to send a response we need to use a SendReply activity as well, if not we are creating a one way contract.

Note that there is not going to be a formal contract as we normally have in WCF. Instead we configure the Receive and SendReply and a contract is derived from that. Not quite sure if I like that as a service contract is supposed to be very much locked down.

The code for the service workflow looks like this:

private static WorkflowElement CreateWorkflow()
{
    var result = new Sequence();
    var input = new Variable<string>();
    result.Variables.Add(input);
    XNamespace ns = "http://tempuri.org";
 
    var handle = new Variable<CorrelationHandle>();
    result.Variables.Add(handle);
 
    var receive = new Receive()
    {
        OperationName = "Operation1",
        ServiceContractName = ns + "MyService",
        Value = new OutArgument<string>(input),
        AdditionalCorrelations = {
           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle)}
        },
        CanCreateInstance = true
    };
 
    result.Activities.Add(receive);
 
    var write = new WriteLine()
    {
        Text = new InArgument<string>(env => string.Format("The workflow was called with '{0}'.", input.Get(env)))
    };
    result.Activities.Add(write);
 
    var reply = new SendReply()
    {
        Request = receive,
        Value = new InArgument<string>("The result")
    };
    result.Activities.Add(reply);
 
    return result;
}

 

For the most part quite straightforward. We receive a message as defined by the Receive activity Value property, in this case just a string. The message is printed and we return another string through the Value property of the SendReply. Using C# code really shows where we need to use arguments and variables, something that is somewhat hidden when using XAML.

One thing I don’t like is having to create and add the CorrelationHandle. It might be needed internally but I don’t really want to be bothered. Setting the Request property on the SendReply to hook the two together should be enough. Lets hope this disappears before WF4 ships.

 

Hosting the service workflow

With the service workflow in place we need to create a WorkflowServiceHost and actually make the workflow available. The WorkflowServiceHost  is configured using a Service definition which in turn contains a WorkflowServiceImplementation containing the actual workflow and a WCF endpoint. The hosting code looks like this:

XNamespace ns = "http://tempuri.org";
 
var service = new Service();
service.Implementation = new WorkflowServiceImplementation()
{
    Name = ns + "MyService",
    Body = CreateWorkflow()
};
 
service.Endpoints.Add(new Endpoint()
{
    Uri = new Uri("Operation1", UriKind.Relative),
    Binding = new BasicHttpBinding(),
    ServiceContractName = ns + "MyService"
});
 
var host = new WorkflowServiceHost(
    service,
    new Uri("http://localhost:8090/Sequence1"));
 
host.Open();
 
Console.WriteLine("Press enter to stop");
Console.ReadLine();
 
host.Close();

 

The main point here is making sure the Name and ServiceContractName, both for the Endpoint and Receive, are configured correctly or the WorkflowServiceHost will not be able to start and you will get a System.InvalidOperationException with a message something like “Cannot add endpoint because ContractDescription with Name='MyService' and Namespace='http://tempuri.org' can not be found.”.

 

Creating a calling workflow

With the service side in place we now need to create a client workflow to call into our service. In this case the main activity to use is the Send which sends the request to the WCF service and the ReceiveReply which waits for the response to arrive. The whole is very similar to the service workflow and again we need to use the annoying CorrelationHandle. The workflow code looks like this:

private static WorkflowElement CreateWorkflow()
{
    var result = new Sequence();
 
    XNamespace ns = "http://tempuri.org";
 
    var endpoint = new Endpoint()
    {
        Uri = new Uri("http://localhost:8090/Sequence1/Operation1"),
        Binding = new BasicHttpBinding()
    };
 
    var response = new Variable<string>();
    result.Variables.Add(response);
 
    var handle = new Variable<CorrelationHandle>();
    result.Variables.Add(handle);
 
    var request = new Send()
    {
        OperationName = "Operation1",
        Endpoint = endpoint,
        AdditionalCorrelations =
        {
           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle)}
        },
        ServiceContractName = ns + "MyService",
        Value = new InArgument<string>("Test")
    };
    result.Activities.Add(request);
 
 
    var reply = new ReceiveReply()
    {
        Request = request,
        Value = new OutArgument<string>(response)
    };
    result.Activities.Add(reply);
 
    var write = new WriteLine()
    {
        Text=new InArgument<string>(response)
    };
    result.Activities.Add(write);
    return result;
}

Just like any other WCF service the main point is to make sure that the service contract names, namespaces and data types match or message will not be processed correctly (or at all). Running this workflow has no special hosting requirements so using the WorkflowInvoker will do just fine.

var workflow = CreateWorkflow();
WorkflowInvoker.Invoke(workflow);
 
Console.WriteLine("Client Done.");
Console.ReadLine();

 

Conclusion

Using WCF and WF4 together is not hard. Unfortunately the designer seems to be letting me down but fortunately everything we can do using the designer can also be done using code.

 

Enjoy!

 

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 4 comment(s)
Filed under: , , ,

 

One thing that has completely changed in Windows Workflow Foundation is the way we work with data in a workflow. In WF 3 we used properties to store data. We could use regular .NET properties but most of the time dependency properties where the smarter choice. Dependency properties left the way data was stored to be handled by the workflow runtime but in our program we could use them just like any other property. And the big bonus was we could use property binding to tie different properties on different activities together in any way we saw fit. These dependency properties where much like the dependency properties found in WPF although there was no common base class or interface used.

 

In WF 4 this has all changed!

We are no longer using properties to store data. Most of the time we will need to use variables, represented by types deriving from Variable, or arguments, types deriving from Argument, to work with data. These arguments and variables don’t actually store the data, they just describe the data and let you get at it. The actual data is stored somewhere inside of the workflow using a LocationEnvironment. To store some data inside of your workflow we need to add a Variable to the workflow or one of the nested activities. The basic rule is to move it as close as possible to the activities that need it at a place there all activities that need access to the data can find the value in the path to the r