|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis 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 ApplicationThe 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:
Each of the two assemblies contains two pieces of the puzzle: FirstWFLibrary.DLL
FirstWFApp.EXE
The rest of this article examines each part of the application listed above. Custom ActivitiesAs 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 /// <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 You might be wondering why an activity would return from the 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 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 /// <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 MappingIn 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 DeclarationNow 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 The root activity contains the XML namespace mappings. The default XML namespace is mapped to the URI specified in the Another point of interest is the relationship between the Workflow Runtime HostYou 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 ConclusionThis 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. | ||||||||||||||||||||