The Problem Solver

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

Google Ads

This Blog

Syndication

Search

Tags

News





  • View Maurice De Beijer's profile on LinkedIn

Community

Email Notifications

Explore

Archives

July 2009 - Posts

Some time ago I got myself an iPhone and I have to admit I really love the little machine. But even good things can go bad and in my case it was a problem with the sound over the headphones. Well the machine has been replaced and I somewhat dreaded getting a new one and having to reinstall all apps and redo all settings.

 

Turns out I didn’t need to worry Smile

As it turns out Apple really thought about this experience and made sure it was as easy as it could be. Today when the replacement iPhone I arrived I just popped in the sim card and connected it to iTunes to activate it. The cool thing was that iTunes detected the new phone and asked if this was to be added as a new phone or a replacement of the known one. I told it it was a replacement and it just restored all phone settings as they where before. All I needed to do was enter a few passwords here and there for things like mail and WIFI account and it was up and running again.

So next I hit sync and all the apps I had on the phone, including all their settings, where back and I was completely in business. An amazingly smooth experience and the way it should be. Basically the only thing I can complain about was that I was without the iPhone for a whole week because it had to send in and it took them a week to return the new one and the fact that the icons where arranged differently on the screen as that seems to be the only things that wasn’t restored. Guess Apple needs some features for a next version of the software too Smile

All in all a very nice experience and I wish more products worked like this.

Posted by Maurice | 1 comment(s)

 

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 Sad

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!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

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

In my previous post I showed how to use an AsyncOperationBlock to do some basic asynchronous work in WF 4. As I mentioned this was for relatively short lived asynchronous operations as the workflow could not be persisted and unloaded while this asynchronous work was executing.

So how about real long running work, the kind you run into with real business application like waiting for an invoice to be paid?

 

With these king of jobs an AsyncOperationBlock will not work because you don’t want to keep the workflow in memory all the time.

So what do we need to use here?

Well the basic building block is called a Bookmark.

 

A bookmark is just a way of saying to the workflow: give a pointer to where you are and I will tell you later to resume from this pointer.

 

Creating a bookmark is easy. In the execute method of our activity we call the CreateNamedBookmark() function of the ActivityExecutionContext passed to us with the name of the bookmark. The bookmark name is no longer an IComparable but just a regular string.

Whenever we are done with our job we can use the WorkflowInstance to call ResumeBookmark() passing in the name of the bookmark and some data. The activity will be signaled as closed and we are ready to go.

One big difference is the activity base class. In the previous sample I was using the CodeActivity as the baseclass for my custom activity while in this case I am using the more powerful NativeActivity<T>.

A word of warning here, these are the names in WF4 Beta 1 and they are likely to change before WF4 is release!

 

So what does my activity look like?

class AsyncWorker : NativeActivity<String>
{
    protected override void Execute(ActivityExecutionContext context)
    {
        Console.WriteLine("CreateNamedBookmark on thread\t{0}", Thread.CurrentThread.ManagedThreadId);
        context.CreateNamedBookmark("bm");
    }
}

It prints a bit of status info and most importantly it creates a new named bookmark called “bm”.

 

The main program looks like this:

var workflow = CreateWorkflow();
var instance = new WorkflowInstance(workflow);
instance.OnIdle = () =>
{
    Console.WriteLine("Idle on thread\t\t\t{0}", Thread.CurrentThread.ManagedThreadId);
    return IdleAction.Nothing;
};
instance.OnCompleted = (e) => { Console.WriteLine("Completed on thread\t\t{0}", Thread.CurrentThread.ManagedThreadId); };
 
instance.Run();
 
Console.WriteLine("Sleep on thread\t\t\t{0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000);
 
Console.WriteLine("ResumeBookmark on thread\t{0}", Thread.CurrentThread.ManagedThreadId);
instance.ResumeBookmark("bm", null);
 
Console.WriteLine("Sleep on thread\t\t\t{0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000);
 
Console.WriteLine("Done on thread\t\t\t{0}", Thread.CurrentThread.ManagedThreadId);
Console.ReadLine();

If you strip out the printing of the status info you are basically left with:

var workflow = CreateWorkflow();
var instance = new WorkflowInstance(workflow);
instance.Run();
instance.ResumeBookmark("bm", null);

 

One interesting thing I noted is that is appears to be ok to resume a bookmark before it is created.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

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

The whole asynchronous execution model in WF 4 has changed quite a bit from WF 3. Not really a surprise as this one of the areas where WF 3 was really hard to work with.

 

Below is a very simple example. It does nothing useful, it’s only task is to show how to get started with asynchronous work in WF 4.

class AsyncWorker : CodeActivity
{
    protected override void Execute(CodeActivityContext context)
    {
        var asyncContext = context.SetupAsyncOperationBlock();
        var task = new Task(DoAsyncWork, asyncContext);
        task.Start();
    }
 
    private static void DoAsyncWork(object state)
    {
        var asyncContext = (AsyncOperationContext)state;
 
        Console.WriteLine("Doing some work.");
        Thread.Sleep(5000);
 
        asyncContext.CompleteOperation((ctx, bm, s) => { }, null);
    }
}

 

The important parts here are:

  • CodeActivityContext.SetupAsyncOperationBlock()
    This basically tells the runtime that the activity is going to do something asynchronously and that the runtime should not continue with the next activity when the Execute() is finished. It also means that the workflow cannot be persisted until this operation is complete.
  • AsyncOperationContext.CompleteOperation()
    This tells the workflow runtime that the operation is complete and that the next activity can be scheduled or the workflow may be persisted.

Because the workflow can’t be persisted this is designed for relatively short running operations. So something like saving some data via a WCF service. If you need the sort of asynchronous work where you might be waiting for day’s on an end user this is not the way to go. In that case you should be looking at bookmarks instead.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

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

 

The question of what Windows Workflow Foundation version 4 means for developers currently developing using WF recently came up. As I mentioned before WF 4 is a complete rewrite and doesn’t use any of the existing WF 3 classes. The design of WF 4 is even quite different from the design of WF 3.

 

So are WF 3 developers completely left in the dark and have to start from scratch? And what about their existing applications, will they still run on .NET 4 or are they stuck in .NET 3.5 until the end of times?

 

Fist of all let me address why I believe Microsoft could make such a radical change. Keep i mind this is just my personal opinion and I might be completely wrong Wink.

WF 3 was never widely adopted so I believe the team felt they could do this and not create too much of a problem. Personally I don't think the low adoption of WF 3 had anything to do with the technology. Sure it has its problems but most people never got to the point that they ran into real technical issues. Instead most people looked, didn't understand and quit. So IMHO the main problem was far more a question if evangelism that technology.

 

So what is the big issue with a migration from WF 3 to WF 4?

One big issue with WF 4 is that Microsoft decided not to release a state machine implementation. In WF 3 there was a choice between state machine and sequential workflows. In WF 4 there is a choice between sequential and flow chart. And in WF 3 a state machine turned out to be far more flexible and useful and quite a few cases are not really covered by the WF 4 model. There are a few workarounds to do something like a state machine in WF 4 but they are not pretty. That said, a number of state machine workflows could very well be done using a flow chart.

 

As far as backward compatibility the story is a bit more complicated. WF 4 ships with an interop activity you can use to host custom WF 3 activities in a WF 4 workflow. How good or bad this is? I really don't know yet but given how different the two designs are I fear it is very limited.

 

But WF 3 developers need not worry about their existing projects!

There is however a second side to the compatibility story. .NET 4 will ship both with the WF 3 and the WF 4 runtimes. The WF 3 runtime will not be prominent but it will still be there so all existing WF 3 applications will continue to run as is. And because it is part of the current .NET framework I suspect this is a legal requirement (but then I am no lawyer so could be wrong here). So there is no reason for existing workflow developers to panic just yet Smile.

 

So existing workflow developers should start learning WF 4 for future projects but can continue to keep using their existing WF 3 solutions for the foreseeable future.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

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

Dear Maurice de Beijer,

Congratulations! We are pleased to present you with the 2009 Microsoft® MVP Award! This award is given to exceptional technical community leaders who actively share their high quality, real world expertise with others. We appreciate your outstanding contributions in Connected System Developer technical communities during the past year.

 

O yes, I am happy again Smile

 

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 2 comment(s)