Data and Windows Workflow Foundation 4

 

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 root activity.

The root path is a big difference from WF3 where any activity could get to any other piece of data anywhere in the workflow. This makes for some hard to maintain workflows and means that persistence always has to store everything. With the new model you are much restricted in where you can store data but it makes for a much cleaner, and therefore faster, persistence model.

 

Take the following class:

public class Customer


{


    public Customer(string name, CreditRating creditRating)


    {


        Name = name;


        CreditRating = creditRating;


    }


 


    public string Name { get; set; }


    public CreditRating CreditRating { get; set; }


}


 


public enum CreditRating


{


    Good,


    Average,


    Poor


}

 

Now I want to create a workflow that prints the customer with its credit rating, updates the credit rating and prints the data gain. Not very complex but enough to display the steps.

The workflow looks like this:

image

Because all three activities need to use the same customer object we need to store it at a shared parent. In this case the workflow is very simple and the shared parent is the root Sequence itself.

Adding new variable is done using the variables menu at the left bottom of the designer.

image

The Name is used throughout the workflow to store and retrieve the data. The Default can be left empty but in this case I create a new Customer object my entering the following expression:

New WorkflowConsoleApplication4.Customer("Maurice", WorkflowConsoleApplication4.CreditRating.Average)

Selecting the data type is done using the .NET type selector dialog shown below. Personally I find the type selector not the easiest to work with but it gets the job done.

image

 

The text to print in the WriteLine activities is quite straightforward and you get full IntelliSense, even on our own types. The same is true for the Assign activity.

String.Format("Customer '{0}' has a credit rating of {1}.", theCustomer.Name, theCustomer.CreditRating)

image

 

Not bad and easy enough to do.

 

Creating the workflow using code

Every workflow you create using the designer you can also create using code so what would this same workflow look like when done using pure code?

var theCustomer = new Variable<Customer>()


{


    Default = new Customer("Maurice de Beijer", CreditRating.Average)


};


 


var result = new Sequence();


result.Variables.Add(theCustomer);


 


result.Activities.Add(new WriteLine()


{


    Text = new InArgument<string>(env => string.Format("Customer '{0}' has a credit rating of {1}.",


        theCustomer.Get(env).Name,


        theCustomer.Get(env).CreditRating))


});


 


result.Activities.Add(new Assign<CreditRating>()


{


    To = new OutArgument<CreditRating>(env => theCustomer.Get(env).CreditRating),


    Value = CreditRating.Good


});


 


result.Activities.Add(new WriteLine()


{


    Text = new InArgument<string>(env => string.Format("Customer '{0}' has a credit rating of {1}.",


        theCustomer.Get(env).Name,


        theCustomer.Get(env).CreditRating))


});

 

The same WriteLine activity with the simple String.Format() expression now looks quite a bit different. And not different in a good sort of way [:(]

Instead of using “theCustomer.Name” as we could in the designer now we have to use the expression “theCustomer.Get(env).Name” and instead of just typing the complete expression we have to wrap it in an InArgument<string>. Not very pretty but it does show is what is going on under the covers so lets take a closer look.

The WriteLine activity needs something to print and this is input for the activity as it never modifies it. So we need to use an InArgument. In the case of the Assign activity it needs change some data so it needs to use an OutArgument. The third option, not used here, is an InOutArgument which lets an activity read and modify some data.

The InArgument has several constructors, in the case I am using one that takes an expression with a LocationEnvironment which we can use to retrieve the actual customer object.

So where is this customer stored?

Just like in the designer we need to create a variable. This is done at the top using this code:

 

var result = new Sequence();


var theCustomer = new Variable<Customer>()


{


    Default = new Customer("Maurice de Beijer", CreditRating.Average)


};


result.Variables.Add(theCustomer);



 



So all in all this is very different in WF4 as compared to WF3. The code use to create a workflow doesn’t look very pretty, but then again the code use to create and link dependency properties didn’t either. Fortunately the designer hides most of the complexity for us by just letting us enter the expression we want and not bother us with the way data is actually handled inside of a workflow.



 



Enjoy!



[f1]
[f2]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>