Click here to Skip to main content
Click here to Skip to main content
Go to top

Hello, WF!

, 12 Nov 2006
Rate this:
Please Sign up or sign in to vote.
Explains the very basics of Windows Workflow Foundation, using The World's Stupidest WF Application.

Introduction

This article contains the world’s most trivial and useless Windows Workflow Foundation (WF) application, as well as some background on WF itself. The purpose of this software freak show is to expose a WF newcomer to the basics of how to get started with this exciting new part of .NET 3.0. It does not get into any sophisticated scenarios and certainly does not contain any WF best practices. It just shows you how to put together a minimal WF app. If you have more realistic needs and requirements, you might use this code as a starting place for real development.

I will admit right here (New York City) and now (November 2006) that I am by no means a WF expert. I’m just really excited about WF, and have been reading about it and playing around with it in my free time. This article was written purely out of excitement for the technology, so I hope that excitement shines through and helps get you excited too.

The application shown in this article is a mutation of the demo app used in the beginning of the excellent book “Essential Windows Workflow Foundation” by Dharma Shukla and Bob Schmidt. I highly recommend that book if you want to get serious about learning WF. You might also want to check out this article on MSDN, by Don Box and Dharma Shukla.

WTF is WF?

Before we get into building a WF application, let’s take a moment to grasp the general ideas behind WF. WF stands for Windows Workflow Foundation. It is a subsystem of the .NET Framework 3.0 which provides a runtime for creating and executing workflow-based applications. That’s nice marketing babble, but what exactly does that mean? Good question…

From a very high-level perspective, WF allows you to create programs that can be persisted to a backing store when they are inactive and then resumed when necessary. You can declare the overall program flow in an XML-based language known as eXtensible Application Markup Language (XAML) and load/execute your XAML workflow at runtime. Expressing your application’s general logic flow in a markup language simplifies the development process greatly, especially because it opens new possibilities for the use of graphical design tools in creating software.

Why Would I Use WF?

Many applications are reactive by nature. They sit around for an indefinite period of time waiting for something to happen (perhaps a file to be created in a certain directory). When that external stimulus arrives the application gets busy processing the incoming data. The problem with this common scenario is that performance and scalability are greatly affected by the fact that while an application is waiting for external input, it consumes processing time from whatever thread it runs on.

WF provides a solution to that problem. Since a workflow in WF is represented as a tree of objects, and those objects support being serialized/deserialized, WF workflows have built-in support for being saved to and loaded from a database. I don’t mean that just the data manipulated by the application is saved; I mean that the “program” itself is saved.

The state of the application is effectively “frozen” and put into a cryogenic freezer, so to speak. When it comes time to resume the workflow processing (i.e. the external stimulus arrives) the “frozen” workflow is thawed out and it continues executing as normal. Keep in mind that the workflow could be resumed on a different computer, a thousand miles away from where it was frozen, fifteen months later. This brings tremendous gains in terms of performance and scalability, because the workflow is not bound to a specific thread, process, or even computer.

That’s all of the introductory material I’m going to provide on WF. I didn’t explain nearly all of what WF is all about. If you are interested in a more thorough and circumspect explanation of WF, I recommend you read the book mentioned previously (“Essential Windows Workflow Foundation”). Now let’s play with some WF code!

The World’s Stupidest WF Application

The application I’m going to present to you here asks the user for his/her name, waits for the user to type it in, and then prints “Hello, UserName!” to the console. Obviously this application is only of interest because it uses Windows Workflow Foundation to perform its magic. I tried to keep the use of WF as simple and minimal as possible, just so that it’s easy to see how one goes about setting up an application which uses WF.

There are two assemblies involved in this application:

  1. FirstWFLibrary.DLL – This assembly contains the custom WF Activities which are used to perform the tasks of printing output to the console, and getting the user’s name.
  2. FirstWFApp.EXE – This assembly is a console app which loads the WF WorkflowRuntime and puts the Activities in FirstWFLibrary to use.

Each of the two assemblies contains two pieces of the puzzle:

FirstWFLibrary.DLL

  • Custom Activities – The fundamental unit of work in a WF workflow is an 'activity.' All custom activities in a WF application derive from the Activity base class and override its protected Execute method to provide their own execution logic.
  • Namespace Mapping – In order to be able to use our custom Activities in XAML we need to provide a way to tell the XAML parser the namespace(s) in which those classes exist. This is accomplished by applying an attribute to the assembly, as we will see later.

FirstWFApp.EXE

  • Workflow Declaration – In this application the workflow required for reading the user’s name and displaying it back to him/her is expressed in XAML. The XAML file in this assembly uses the custom Activities declared in the FirstWFLibrary assembly. Note, it is not required that workflows are declared in XAML. It is entirely possible to declare them in other formats, such as C# code. I chose to express the workflow in XAML for this application because it’s more fun that way. Smile | :)
  • Workflow Runtime Host – The last piece of the puzzle is a class which loads the WF runtime, configures it, and then tells it to start running. The EntryPoint class in this application takes care of that job.

The rest of this article examines each part of the application listed above.

Custom Activities

As mentioned previously, this demo application has three tasks to perform. First it must display a message in the console window which asks the user for his/her name. Then it must wait for the user to enter their name and press Enter. Finally it displays another message to the user, which includes their name in it.

Each of those tasks is represented as a separate Activity-derived class. Instances of those classes will perform the actual work necessary to make the program execute. First let’s see how the initial prompt is displayed to the console.

/// <summary>
/// Asks the user for their name.
/// </summary>
public class PromptForUserName : Activity
{
 protected override ActivityExecutionStatus Execute(
                                  ActivityExecutionContext executionContext )
 {
  Console.Write( "Please enter your name and press Enter: " );
  return ActivityExecutionStatus.Closed;
 }
}

This class is a perfect demonstration of how to create an activity which can be included in a WF workflow. It inherits from the System.Workflow.ComponentModel.Activity class and overrides the Execute method to provide custom activity execution logic. Since this activity is logically complete after it writes a message to the console, it returns ActivityExecutionStatus.Closed to inform the WF runtime that it is done.

You might be wondering why an activity would return from the Execute method if it was not done executing. Remember earlier on I mentioned that WF workflows can be “passivated” and stored in a database until it needs to continue executing? Well, an activity’s Execute method will return ActivityExecutionStatus.Executing if it cannot complete until external input eventually arrives.

In fact, the next step of The World’s Stupidest WF Application requires an indefinite period of time to elapse before it can continue processing. It might take the user three seconds or three days to type in his/her name. During that time the workflow will have nothing to process. If this was a less stupid WF application, we might decide to passivate the workflow until the user name finally arrives, at which point we would resume the workflow and let it continue. We’re not doing that here, but this next activity shows how to set up a “bookmark” so that the WF runtime can inform the activity when input has arrived.

/// <summary>
/// An activity which represents reading a line of text from the console.
/// </summary>
public class ReadConsoleLine : Activity
{
 #region InputText Property

 private string inputText;
 public string InputText
 {
  get { return this.inputText; }
 }

 #endregion // InputText Property

 #region Execute [override]

 protected override ActivityExecutionStatus Execute( 
    ActivityExecutionContext executionContext )
 {
  // Create a WorkflowQueue, which allows this activity to "bookmark" 
  // where it should continue executing once the external input arrives 
  // (in this case, a string is read from the console).
  WorkflowQueue workflowQueue = this.GetWorkflowQueue( executionContext );

  // Attach a handler which processes the external input.
  workflowQueue.QueueItemAvailable += ProcessQueueItemAvailable;

  // Attach a handler which cleans up after the input has been processed.
  workflowQueue.QueueItemAvailable += CloseActivity;

  // Indicate to the Workflow runtime that this activity is logically still 
  // executing, even though it will not do anything until input arrives.
  return ActivityExecutionStatus.Executing;
 }

 #endregion // Execute [override]

 #region Event Handlers

 void ProcessQueueItemAvailable( object sender, QueueEventArgs e )
 {
  // The external input has arrived, so wake up and process it.
  WorkflowQueue workflowQueue = this.GetWorkflowQueue( 
    sender as ActivityExecutionContext );

  if( workflowQueue.Count > 0 )
   this.inputText = workflowQueue.Dequeue() as string;
 }

 void CloseActivity( object sender, QueueEventArgs e )
 {
  // The external input has arrived and been processed, so throw away the 
  // WorkflowQueue we used, and tell the WF runtime the activity is finished.
  ActivityExecutionContext executionContext = 
    sender as ActivityExecutionContext;

  WorkflowQueuingService queuingService = 
    executionContext.GetService<WorkflowQueuingService>();

  queuingService.DeleteWorkflowQueue( this.Name );

  executionContext.CloseActivity();
 }

 #endregion // Event Handlers

 #region Private Helpers

 // Helper method which returns a WorkflowQueue.
 WorkflowQueue GetWorkflowQueue( ActivityExecutionContext executionContext )
 {
  WorkflowQueue queue;
  WorkflowQueuingService queuingService = 
    executionContext.GetService<WorkflowQueuingService>();

  if( queuingService.Exists( this.Name ) )
   queue = queuingService.GetWorkflowQueue( this.Name );
  else
   queue = queuingService.CreateWorkflowQueue( this.Name, true );

  return queue;
 }

 #endregion // Private Helpers
}

In the ReadConsoleLine’s Execute method a “bookmark” is established and the method immediately returns control back to the WF runtime. However, it returns ‘Executing’ to let the WF runtime know that it should not go on processing any other activities yet. When the user input finally arrives, the ReadConsoleLine’s ProcessQueueItemAvailable and CloseActivity event handling methods will be invoked. The first of those methods stores the user’s name in a private variable. The other method cleans up and closes the activity, so that the WF runtime can continue processing other activities.

The last activity is responsible for printing out a greeting to the user, with his/her name in it. This activity has a dependency property called UserName. As we will see later, this property is bound to the input value received by the ReadConsoleLine activity. The data binding is established in the XAML declaration of these objects. We’ll get to that soon, but now let’s see the GreetUser activity.

/// <summary>
/// Prints a greeting to the user.
/// </summary>
public class GreetUser : Activity
{
 public static readonly DependencyProperty UserNameProperty;

 static GreetUser()
 {
  UserNameProperty = DependencyProperty.Register( 
   "UserName", 
   typeof( string ), 
   typeof( GreetUser ) );
 }

 // UserName is a dependency property so that it can be bound to the
 // InputText property of the ReadConsoleLine activity.
 public string UserName
 {
  get { return (string)GetValue( UserNameProperty ); }
  set { SetValue( UserNameProperty, value ); }
 }

 protected override ActivityExecutionStatus Execute(
                                 ActivityExecutionContext executionContext )
 {
  string greeting = String.Format( "Hello, {0}!", this.UserName );
  Console.WriteLine( greeting );

  return ActivityExecutionStatus.Closed;
 }
}

Namespace Mapping

In order to use our custom activities in XAML we need to provide a way for the XAML parser to know what CLR namespace those classes reside in. Since XAML is an XML-language, we need to provide a way of associating the CLR namespace with an arbitrary XML namespace (basically, a URI). This can be done in any code file in the project, but I created an AssemblyInfo.cs just for the sake of tradition. Here’s the contents of that file:

using System.Workflow.ComponentModel.Serialization;

// This attribute makes it possible to use our custom activities in XAML.
[assembly: XmlnsDefinition( "http://FirstWFLibrary", "FirstWFLibrary" )]

Workflow Declaration

Now that we have the custom activities needed to perform our program logic, and their namespace is mapped, we can create instances of those types. In this demo we will create them in XAML. Think of XAML as just an all-purpose object instantiation markup language. It allows you to configure objects and express the hierarchical relationships between them very easily.

Here is the XAML declaration of The World’s Stupidest Workflow:

<wf:SequenceActivity 
  xmlns="http://FirstWFLibrary" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow"
  >
  <PromptForUserName />
  <ReadConsoleLine x:Name="getUserName" />
  <GreetUser UserName="{wf:ActivityBind Name=getUserName, Path=InputText}" />
</wf:SequenceActivity>

The root activity in the workflow is a SequenceActivity object, which is a class provided in the WF framework. It is a CompositeActivity-derived class which executes its child activities in the order they are declared, only executing one child activity after the previous child activity is complete.

The root activity contains the XML namespace mappings. The default XML namespace is mapped to the URI specified in the XmlnsDefinition attribute in the previous section of the article. That allows us to refer to our custom activity types without any namespace prefix.

Another point of interest is the relationship between the ReadConsoleLine and the GreetUser activities. The UserName property of the latter is bound to the InputText property of the former. This binding allows the text typed into the console to be transferred from one activity to another. For a property to be the target of a binding, it must be a 'dependency property.' As we saw in the 'Custom Activities' section, there is a little more code involved with creating a dependency property than just a normal property, but dependency properties can be used in ways that normal properties cannot. You can read about those differences in the SDK, if you care.

Workflow Runtime Host

You can host the WF runtime in a variety of ways, but this demo just uses a plain vanilla console application. There are a few steps you must follow to get the WF runtime up and running in your AppDomain. The following method is where The World’s Stupidest WF Application hosts the WF runtime (this method is, of course, in a class):

public static void Main()
{
 // Create an instance of WorkflowRuntime, which will execute and coordinate 
 // all of our workflow activities.
 using( WorkflowRuntime workflowRuntime = new WorkflowRuntime() )
 {
  // Tell the Workflow runtime where to find our custom activity types.
  TypeProvider typeProvider = new TypeProvider( workflowRuntime );
  typeProvider.AddAssemblyReference( "FirstWFLibrary.dll" );
  workflowRuntime.AddService( typeProvider );

  // Activate the Workflow runtime.
  workflowRuntime.StartRuntime();

  // Load the XAML file which contains the declaration of our simple workflow
  // and create an instance of it.  Once it is loaded, the workflow is started
  // so that the activities in it will execute.
  WorkflowInstance workflowInstance;
  using( XmlTextReader xmlReader = 
    new XmlTextReader( @"..\..\HelloUserWorkflow.xaml" ) )
  {
   workflowInstance = workflowRuntime.CreateWorkflow( xmlReader );
   workflowInstance.Start();
  }    

  // The ReadConsoleLine activity uses a "bookmark" to indicate that it must
  // wait for external input before it can complete.  In this case, the 
  // external input is the user's name typed into the console window.
  string userName = Console.ReadLine();
  workflowInstance.EnqueueItem( "getUserName", userName, null, null );
  
  // Pause here so that the workflow can display the greeting.
  Console.ReadLine();
      
  // Tear down all of the Workflow services and runtime.
  // (This is probably redundant since the 'runtime' object is in
  // a using block).
  workflowRuntime.StopRuntime();
 }
}

I’m not going to explain that method line by line, because it is commented well enough. The one point of interest I will mention is that once the WorkflowInstance starts, the method then calls Console.ReadLine to get the user’s name. Once the name is retrieved, it is put onto the “getUserName” workflow queue, which was created by and for the ReadConsoleLine activity. This is an example of external input being provided to the workflow, causing it to resume processing. Once the user’s name is put onto the workflow queue, the ReadConsoleLine activity will have its callback methods invoked so that it can finish executing.

Conclusion

This article showed how to create an application which uses Windows Workflow Foundation. Hopefully it left you feeling like The World’s Stupidest WF Application deserves its name, but also that you understand the fundamentals of WF and how to use it in an application.

As I mentioned before, this application does not at all need to use the powers of WF, but it does convey the basic concepts involved. It shows how WF workflows are a tree of activities, how those activities can use the concept of “bookmarks” to indicate where processing should continue after external stimulus occurs, how a workflow can be declared in XAML, and how to host the WF workflow runtime. Along the way I mentioned that WF workflows can be passivated and resumed, which provides a powerful means of improving an application’s scalability and performance.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Josh creates software, for iOS and Windows.
 
He works at Cynergy Systems as a Senior Experience Developer.
 
Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.
 
Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.
 
Check out his Advanced MVVM[^] book.
 
Visit his WPF blog[^] or stop by his iOS blog[^].
Follow on   Twitter

Comments and Discussions

 
Question"Does not exist in the namespace" errors in .xaml file - how to troubleshoot? Pinmembercharles9226-Jan-14 13:39 
QuestionQuestion on using windows workflow in a web based library circulation system using visual studio 2005 Pinmembersohair zaki17-Jul-08 17:22 
GeneralWord of warning! PinmemberHoward Richards20-Nov-06 22:29 
GeneralRe: Word of warning! Pinmemberbluerocketgo6-Feb-07 3:48 
GeneralRe: Word of warning! Pinmemberbluerocketgo6-Feb-07 3:48 
GeneralRe: Word of warning! PinmemberJames_Lin12-Aug-07 0:25 
GeneralRe: Word of warning! Pinmembercesar_boucas17-Jul-09 10:36 
GeneralRe: Word of warning! Pinmember ThatsAlok 14-Apr-10 23:15 
Generalwish list PinmemberMaxim Astafev20-Nov-06 8:42 
GeneralRe: wish list PinmemberJosh Smith20-Nov-06 9:15 
GeneralRe: wish list PinmemberMaxim Astafev21-Nov-06 1:25 
GeneralInteresting article! Pinmemberpeterchen12-Nov-06 20:55 
GeneralRe: Interesting article! PinmemberJosh Smith13-Nov-06 0:57 
GeneralRe: Interesting article! Pinmemberpeterchen13-Nov-06 1:20 
GeneralRe: Interesting article! PinmemberJosh Smith13-Nov-06 1:30 
GeneralRe: Interesting article! PinprotectorMarc Clifton14-Nov-06 3:10 
GeneralRe: Interesting article! PinmemberJosh Smith14-Nov-06 4:12 
GeneralRe: Interesting article! PinprotectorMarc Clifton14-Nov-06 5:08 
GeneralRe: Interesting article! Pinmemberreinux14-Nov-06 11:13 
GeneralRe: Interesting article! PinmemberJosh Smith14-Nov-06 11:21 
GeneralRe: Interesting article! PinprotectorMarc Clifton14-Nov-06 12:38 
GeneralRe: Interesting article! Pinmemberreinux14-Nov-06 13:25 
GeneralRe: Interesting article! PinmemberJosh Smith14-Nov-06 13:39 
Wow! Excellent information and perspective, Marc. Thanks a lot for enhancing my understanding of WF (and my article, for that matter!). I feel like your "dissertation", as you put it, should be my article, and my article should be a comment! Blush | :O
 
Big Grin | :-D
 

 
:josh:
My WPF Blog[^]

GeneralRe: Interesting article! PinprotectorMarc Clifton14-Nov-06 13:56 
GeneralRe: Interesting article! PinmemberJosh Smith14-Nov-06 14:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 12 Nov 2006
Article Copyright 2006 by Josh Smith
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid