Welcome!

.NET Authors: Bruce Armstrong, Pat Romanski, Liz McMillan, Yeshim Deniz, Dmitry Sotnikov

Related Topics: .NET

.NET: Article

ASP.NET

Using Workflow Foundation in creating dynamic navigation applications in ASP.NET

There are a few things to think about when using Windows Workflow Foundation (WF) in ASP.NET and this article is going to cover creating a simple wizard application using lessons learned from a large-scale application using these technologies. The basis of this article and the companion code is a wizard used to enter employee expenses and relies on WF to control what page to display and to provide data for the page. Before we get into the code, let me explain the application in a little more detail.

The employee expense wizard (see Figure 1 and Figure 2) is based on a StateMachine workflow (see Table 1) and collects expense information for an employee. The first step in the wizard collects employee information consisting of first name, last name, and department and the second step in the wizard collects expense information consisting of item description and amount. The user can move forward and backward through the wizard and the buttons are enabled or disabled depending on the valid events of the current state in the workflow. The user can cancel the wizard at any step and finish the wizard only in the second step.

Hosting WF in ASP.NET
WF (see Table 1) is designed to be used in many different host applications. In the case of ASP.NET, a few key architectural differences in the technology require a different approach to programming than in a WinForms- or Service-type application. These differences are related to memory usage, threading, and persistence and how they are affected by an environment that handles many simultaneous requests while requiring the ability store state in a reliable manner that can be accessed by multiple servers in a Web farm.

Memory is a huge issue on a Web server and having many workflows in memory at one time can overwhelm the server and hamper its ability to process requests. WF is designed to handle this using the persistence services (see Table 1) that are designed to hydrate a workflow when it's idle and then re-hydrate it when needed again. In our example the StateMachine workflow will be hydrated after each page request until the workflow reaches a completed state, which happens when the cancel or finished button is selected.

The default persistence service is the SqlWorkflowPersistenceService (see Listing 1), which stores the workflow in SQL until it's completed. The sample application stores the data from each step in the wizard in the StateMachine workflow and with the persistence services installed will store the workflow in SQL between each request. This use of persistence is similar to the SQL-based session state in ASP.NET allowing multiple Web servers to handle the wizard page requests without the request having to go back to the same server.

The next thing to consider when using WF with ASP.NET is threading.

The default behavior of WF is to create a thread for each workflow and run the workflow in that thread. The downside is that ASP.NET does the same thing when it processes page requests, which can lead to two threads being created for each page request, which is very inefficient. The creators of WF realized this and created the ManualWorkflowSchedulerService (see Listing 1) that lets WF run on the calling thread making WF much more efficient when running in ASP.NET.

The last thing to consider when using WF is the requirement of one instance of WF running in a single application domain.

An ASP.NET application runs in an application domain and therefore can only have one instance of WF running at the same time. There are a few ways to ensure that only one instance is created and in the sample application provided with this article does this using a singleton class. The DontatedWorkflowRuntime class (see Listing 1) brings together the need to use the ManualWorkflowSchedulerService with the requirement of having only one workflow runtime instance running at one time. The class has a static method CurrentRuntime that returns a reference to the Windows workflow runtime and a static method RunWorkflowInstance that manually schedules the workflow to run.

Communicating with WF from ASP.NET
Host applications, in our case the ASP.NET application, and workflows communicate with each other using methods and events that work in conjunction with the messaging-based Local Communication Service (see Listing 1). The host uses events to pass information into the workflow where they are received by the HandleExternalEvent activity (see Listing 1) and methods are used by workflow to pass information into the host using the CallExternalMethod activity (see Listing 1) on the workflow side and an exposed method on the host side. There are three things required to get the host to communicate with the workflow: an interface, an event argument class, and a service.

The interface, which is decorated with the ExternalDataExchange attribute, serves as the definition for how the host will communicate with the workflow and defines events and methods that are used by both sides. The Local Communication Service uses the required ExternalDataExchange attribute (see Listing 1) to identity a service that supports communication. In the sample application, the WorkflowInterfaces.IStateMachine interface defines the SendWorkflowInformationToHost method that the workflow uses to send data back to the host and the Next, Previous, Cancel, and Finish event handlers used to send data to the workflow.

The event handlers pass data to the workflow using event arguments inherited from ExternalDataEventArgs (see Listing 1). In the sample application, the WorkflowInterfaces.StateMachineEventArgs inherits from ExternalDataEventArgs and consists of the instanceId of the workflow and data to pass into the workflow, which is a custom class of the PageData type that will be detailed later. The instanceId is used to associate the event argument with the correct workflow, and data represents the control information and the page data gathered during data entry on the page.

The implementation behind the methods and events defined in the interface is the in the service class which is used by all requests for workflows that support communication using the defined service. In the sample application, the WorkflowServices.StateMachineService implements the WorkflowInterfaces.IStateMachine interface and provides implementation behind the SendWorkflowInformationToHost method and the Next, Previous, Cancel, and Finish event handlers. There are some things to consider when the same service is used to process simultaneous requests. The first thing to consider is how to process multiple calls into SendWorkflowInformationToHost and keep track of which data goes with which workflow. This is done by using a dictionary keyed off the instanceId of the workflow that is guaranteed to be unique. This dictionary stores the page data, which is a PageData type, until the application is ready to fetch the data using the RetrieveDataFromWorkflow method of StateMachineService that takes an instanceId as a parameter.

The next thing to consider is how to raise the events that pass data into the workflow. This is done by the SendXXXEventToWorkflow methods for the Next, Previous, Cancel, and Finish events that all take an instanceId and an object of the PageData type and raise their respective events by bundling the parameters into the StateMachineWorkflowEventArgs values. In the StateMachineService you'll notice the use of the waitHandle member, which is an AutoResetEvent type in the SendWorkflowToHost and RetrieveDataFromWorkflow methods. This is necessary because, as the SDK states, the use of ManualWorkflowSchedulerService will only block the call to the workflow until it becomes idle and then the calling code will continue to execute. The use of the waitHandle member keeps the call to the service blocked until the data has been returned.

Workflow and Activities
The workflow for the wizard is based on the WebNavigationWorkflowLibrary.ExpenseWorkflow (see Figure 3), which is a StateMachineWorkflow (see Table 1). A StateMachine workflow uses StateActivity activities (see Table 1) to represent the various states a workflow can be in. In the application the Employee Information Collection step is represented by the employeeCollectionState StateActivity and the Expense Information Collection step is represented by the expenseCollectionState StateActivity. Each of these state activities contains a StateInitializationActivity (see Table 1) represented by xxxCollectionStateIntialization and a StateFinalizationActivity (see Table 1) represented by xxxCollectionStateFinalization. These activities essentially work as constructors and destructors for the state activities enabling the activities to retrieve new or existing data during initialization and save data during finalization.

The first step in the workflow is the ExpenseWorkflowInitialState that essentially moves the flow of the workflow to the employeeCollectionState state. This is done because the workflow lets the user move freely between the employeeCollectionState state and the expenseCollectionState state and the initialization activities of these states check the internal dictionary sessionData using the GetEmployeeData custom activity to see if a keyed entry with the state's name exists or creates a new one if needed and sends the information back to the host using the CallExternalMethod and the SendWorkflowInformationToHost method. This storage of data in the sessionData member is how the workflow remembers the user control and dependent page data for each state (step) in the workflow.

The employeeCollectionState state activity can handle two types of events, Next and Cancel. These EventDrivenActivity-type activities (see Table 1) are essentially sinks that capture the events raised by the host. In our case, these activities move the workflow to the Next state, which is expenseCollectionState, or to the Cancel state, which is cancelState. Before the employeeCollectionState state moves to the expenseCollectionState state when the Next event is handled, the StateFinalizaitonActivity is executed saving the PageData to the sessionData dictionary described above using the SetPageData custom activity.

The expenseCollectionState state activity works with the Previous event, which moves the workflow state to the employeeCollection state, the Finish event, which moves the finshState state and completes the workflow successfully, and the Cancel event, which moves to the cancelState state and exits the workflow. The behavior of this state activity is the same as employeeCollectionState where each time the state is entered the StateInitialziationActivity is executed and data is returned to the host and when the state is exited the StateFinalizationActivity is executed saving the data to the sessionData member variable of the workflow.

The final step of the workflow is ExpenseWorkflowCompleted state, which acts as a marker ending the workflow and signaling to the workflow runtime that the workflow no longer needs to be persisted.

Responding to ASP.NET Page Requests
There are a few other classes that round out the plumbing required for the ASP.NET pages to communicate with the workflow. These classes are WorkflowData.PageData, WorkflowServices.StateMachineChannel, and WorkflowController.WorkflowManager.

WorkflowData.PageData class contains the data that is passed into the workflow as part of the StateMachineWorkflowEventArgs and passed out of the workflow as a parameter in the SendWorkflowInformationToHost. This class contains a property named ControlName that contains the control to be loaded and a Data property that contains the data for the page.

The WorkflowServices.StateMachineChannel class is a static class that abstracts a lot of the internal calling to the StateMachineService class simplifying the calling pattern in the WorkflowManager (see Listing 2). This class handles getting a reference to the service, calling the methods of the service that expose the events, and calling into the service to get the data returned by the workflow. The ProcessNextRequest, ProcessPreviousRequest, ProcessCancelRequest, and ProcessFinalRequest all call into the service raising their respective events and then call back into the service to retrieve the data. This is all done in one single call simplifying access into the StateMachineSevice.

The WorkflowController.WorkflowManager class provides an entry point into the workflow processing for the ASP.NET pages and uses the DontatedWorkflowRuntime class and the StateMachineChannel class. The WorkflowManager class handles creating the workflow type by using the workflow type name and workflow assembly name that is passed into its constructor to get the workflow type dynamically. This enables the WorkflowManager to work with many different workflows without code modifications. The ProcessRequest method handles all of the request types from initial to finished creating the workflow, calling into the StateMachineChannel, and returning the page data. The interesting thing this method does is call a private method that loops through the state activities looking for all of the HandleExternalEventActivity type activities and getting the event name that's associated with them. The ASP.NET page later uses this information to enable and disable the buttons on the page based on the event names.

The discussion of page processing is not complete without a description of how the ASP.NET pages interact with the WorkflowManager. The Default.aspx page is a framework page that handles all of the requests to the wizard. The page is designed to work with ViewState to store the workflowId and the control name so the page can be reconstructed on post back and be handled by different servers in a Web farm. The page calls into the WorkflowManager class sending the workflow name and the assembly the workflow is contained in. The return value from the ProcessRequest method is a keyed dictionary that has the new control name to be loaded, the data for the control, and a list of buttons that should be enabled. This information is then used by the page in the Page_PreRender method to create the user control, assign the data to the control, and store the instanceId of the workflow and the control name in the ViewState of the page.

Conclusion
WF is a very exciting technology and very useful in creating dynamic navigation applications in ASP.NET. The sample application described in this article should give you plenty of ideas for creating your own type of application that uses WF and with the final release of .NET 3.0 just about out there's no excuse to wait any longer.

More Stories By Adam Calderon

Adam Calderon is a C# MVP and the Application Development Practice Lead at Interknowlogy. He is an accomplished software developer, author, teacher and speaker with over 14 years of experience designing and developing solutions on the Microsoft platform. Since joining InterKnowlogy, Adam has worked with many of their top tier clients designing and developing WPF, ASP.NET and ASP.NET AJAX solutions on the .NET 3.0 and .NET 3.5 platforms. His newly released book Advanced ASP.NET AJAX Server Controls that he coauthored with Joel Rumerman is chock-full of information gained from working with these clients.

Comments (5) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
mark bosley 07/23/08 02:53:00 PM EDT

Good article. Please post the code or send it to me. It appears you've addressed an issue I'm having and would like to see your implementation.

DavidL 07/09/08 12:47:43 PM EDT

Thanks for the demo it has been very helpful understanding state machine workflows.

But a question, when I run the demo, I can never find data being restored from the workflow after it has been passed into the workflow. Specifically this line in default.aspx.cs:

((IData)this.dynamicControl).Data = workflowData["Data"];

always returns a null value from workflowData["Data"]. What is missing here to get a value back out of workflow?

Thanks!

Rolf Gasber 02/08/07 12:34:35 PM EST

There is an error showing Listing 1 in your article about WF and ASP.NET. Please, can you send me a corrected version.

Thanks

Vasu 02/02/07 11:35:58 AM EST

The listings mentioned in the article does not seem to work.
Is it possible to get the source code for this article? I did not see any download.
Thanks

SOA Web Services Journal News 01/11/07 02:28:21 PM EST

There are a few things to think about when using Windows Workflow Foundation (WF) in ASP.NET and this article is going to cover creating a simple wizard application using lessons learned from a large-scale application using these technologies. The basis of this article and the companion code is a wizard used to enter employee expenses and relies on WF to control what page to display and to provide data for the page. Before we get into the code, let me explain the application in a little more detail.