|
YOUR FEEDBACK
|
TOP MICROSOFT .NET LINKS Workflow ASP.NET
Using Workflow Foundation in creating dynamic navigation applications in ASP.NET
By: Adam Calderon
Jan. 11, 2007 04:00 PM
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.
Hosting WF in ASP.NET 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 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 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 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 YOUR FEEDBACK
MICROSOFT .NET LATEST STORIES
SUBSCRIBE TO THE WORLD'S MOST POWERFUL NEWSLETTERS SUBSCRIBE TO OUR RSS FEEDS & GET YOUR SYS-CON NEWS LIVE!
|
SYS-CON FEATURED WHITEPAPERS MOST READ THIS WEEK BREAKING NEWS FROM THE WIRES
|
||||||||||||||||||||||||||||||||||||||||