devxlogo

Using Windows Workflow to Control ASP.NET Navigation

Using Windows Workflow to Control ASP.NET Navigation

his article describes one simple solution for achieving interoperability between Windows Workflow (WF) and ASP.NET. It leverages the power of WF’s powerful visual logic to design page-flow based on user choices. Without WF, enforcing page flow would most likely entail hard-coding such logic. First, the article covers the three main tasks involved in building the solution: creating the Workflow side, writing a middle bit?a service layer?which involves writing an interface, and building the ASP.NET side. Then it provides a step by step walkthrough that describes how to create the solution. To follow along, you must have installed Visual Studio 2005, the Web Application project for Visual Studio 2005, and .NET Framework 3.0, which includes Windows Workflow.

How WF Communicates with ASP.NET
Everything in a workflow is an activity. An activity communicates with the outside world through a Workflow Service. The activity tells the service what it wants. For example, it might ask a service for the user’s name and email address, after which it waits for a response.

ASP.NET code responds to events fired from this service, redirecting the user to an appropriate page with input fields for the name and address. When the user has filled out the page and submitted the data back to the server, ASP.NET fires an event that lets the WF service know that a response is available.

In this scenario, the relevant data is stored in a simple dictionary, passed between WF, ASP.NET, and the service layer in serialized form.

I’ll describe the service layer first, as that’s the code that requires the most work. I’ll then describe what you need to do on the WF side and the ASP.NET side to get all the parts talking to each other.

The Service Layer
WF activities communicate with things outside the workflow by using Workflow Services. A service is described by its interface. In this case, you need an interface with one method and one event. The WF activities will call the interface method to request data, and subscribe to the interface event to know when data has been received.

Here’s an IGenericInformationService interface that defines the example service. This interface provides most of what WF needs to operate, as you’ll see later in this article:

   [ExternalDataExchange]   public interface IGenericInformationService   {      string InformationName      { get; set; }         event EventHandler          InformationReceived;         void RequestInformation( string informationName );   }

As you can see, the preceding interface has one property, one event, and one method, making it a rather simple service, but one that has everything you need to request and receive information. Note that this interface is used solely by WF, and that WF doesn’t care what happens within an implemented version of the RequestInformation method or what code fires the InformationReceived event.

Of course, you must supply an implementation of the above interface. Listing 1 shows a GenericInformationService class that implements the IGenericInformation interface.

In addition to implementing the IGenericInformationService, the GenericInformationService class also adds another event, InformationRequested and another method, SubmitInformation. Both WF and the ASP.NET code will use this class; the WF activity will call RequestInformation and wait for the InformationReceived event. ASP.NET will wait for the InformationRequested event and will call SubmitInformation when the user has filled it in.

Note that the InformationReceived event uses a GenericInformationEventArgs class, defined as:

   [Serializable]   public class GenericInformationEventArgs      : ExternalDataEventArgs   {      Dictionary _propertyBag;         public Dictionary PropertyBag      {         get { return _propertyBag; }         set { _propertyBag = value; }      }         public GenericInformationEventArgs( Guid instanceId,          Dictionary propertyBag )         : base( instanceId )      {         _propertyBag = propertyBag;      }   }

GenericInformationEventArgs is a simple class that exposes a property bag (Dictionary) and extends the ExternalDataEventArgs class defined in System.Workflow.Activities.

The Workflow Side
Remember that everything in a Workflow is based on an activity. To control page flow with WF you create activities that ask for information, activities that wait for data, and activities that perform some logic based upon the returned data.

The activities use a service to request and receive data. WF ships with an existing service called the ExternalDataExchangeService. This is really a container of other services, one of which will be ours?the GenericInformationService described in the previous section.

The ASP.NET side
The ASP.NET application will use the GenericInformationService to wait for an event (the InformationRequested event) that signifies WF is requesting information. Based on the InformationName, it will decide what page to display to the user. When the user has filled in the appropriate page, the information is submitted back to the service using the SubmitInformation method.

Author’s Note: Installing .NET 3.0 should install Workflow Performance Counters, but there are some situations where that doesn’t happen. If they are not installed WF throws an exception to the effect that a counter cannot be opened. Reinstalling .NET 3 fixed it for me.

Sample Application Walkthrough
Create a blank Visual Studio solution (File –> New Project –> Other Project Types –> Visual Studio Solutions –> Blank Solution) as shown in Figure 1.

Add a Class Library project and name it MiddleBit (File –> Add –> New Project –> Visual C# –> Class Library) as shown in Figure 2.

?
Figure 1. New Project Dialog: Create a new blank solution by selecting that template from the “Other Project Types/Visual Studio Solutions” area.
?
Figure 2. Add Class Library Project: Here’s where you’ll find the Class Library project template in the Add New Project dialog.
?
Figure 3. Add Sequential Workflow: The Sequential Workflow project template is under the Workflow template types in the Add New Project dialog.

Paste the code for IGenericInterface, GenericInterface, and GenericInformationEventArgs into this project. You’ll also need to add a reference to System.Workflow.Activities to the project references and add the code using System.Workflow.Activities; at the top.

Now add a new Sequential Workflow Console Application project (see Figure 3) to the solution (File –> Add –> New Project –> Visual C#/Workflow –> Sequential Workflow Console Application), and name it MyWorkflows.

Reference the MiddleBit project from the Sequential Workflow project.

At this point, the workflow designer will open, prompting you to drop activities on the design surface. First, you want to call an external method to tell ASP.NET to go get some information. Drag the CallExternalMethod activity to the canvas. Right click, and select “Properties.” You’ll see something like Figure 4.

?
Figure 4. CallExternalMethod Properties: In this dialog, you set various properties for the CallExternalMethod activity.
Note that you must set the InterfaceType and MethodName properties before you can set the informationName property. The dialog tells WF which method from what interface to call.
?
Figure 5. HandleExternalEvent Properties: Here’s the dialog in which you set properties for the HandleExternalEvent activity.

Rename the activity to getNameAndPageChoicesActivity.

So far you have a workflow that?when run?calls an external method to ask for some information. Now you need to tell the workflow to wait until the data arrives. Drag a HandleExternalEvent activity just underneath the first activity. Set its properties as shown in Figure 5.

The activity property settings in Figure 5 tell the workflow to wait for an event named InformationReceived fired from the IGenericInformationService. Notice that the e parameter?the GenericInformationEventArgs class defined earlier in the event declaration?is not set. Here’s the earlier declaration:

   event EventHandler       InformationReceived;   
?
Figure 6. Bind Parameter: Bind the “e” parameter to a new property named “nameAndPageChoicesArgs.”

Click on the right column of “e” and bind “e” to a new field. Clicking the ellipses (?) brings up a dialog with two tabs. You want to bind to a new member, so the dialog should look like Figure 6.

Clicking OK creates the following code in the workflow1.cs file:

   public static DependencyProperty       nameAndPageChoicesArgsProperty =       DependencyProperty.Register(       "nameAndPageChoicesArgs", typeof(      MiddleBit.GenericInformationEventArgs),       typeof(MyWorkflows.Workflow1));         [DesignerSerializationVisibilityAttribute(          DesignerSerializationVisibility.Visible )]      [BrowsableAttribute( true )]      [CategoryAttribute( "Parameters" )]      public GenericInformationEventArgs nameAndPageChoicesArgs      {         get         {            return ( (MiddleBit.GenericInformationEventArgs)              ( base.GetValue( MyWorkflows.Workflow1.                nameAndPageChoicesArgsProperty ) ) );         }         set         {            base.SetValue(             MyWorkflows.Workflow1.nameAndPageChoicesArgsProperty,               value );         }      }

Note that the new property has the DependencyProperty type, not the GenericInformationEventArgs type. The property calls the base methods GetValue and SetValue.

Setting Up ASP.NET
So far, you have the service layer that allows communication between WF and external items and a simple workflow that calls an external method and waits for an external event. Now you can set up an ASP.NET project.

?
Figure 7. Adding an ASP.NET Web Application: Use the Add New Project dialog to add an ASP.NET Web Application to your solution.

Add a new ASP.NET Web Application Project to the solution and name it MyWeb (see Figure 7).

Add System.Workflow.Activities, System.Workflow.Runtime, System.Workflow.ComponentModel, and MyWorkflows (the Sequential Workflow Console Application you created earlier) to the new project’s dependencies.

Add a new class to the Web Application project named Wrapper (see Listing 2). This isn’t strictly necessary to get things working, but it saves a lot of typing!

Add three Web Forms, named NameAndUserChoices.aspx, Page2.aspx, and Page3.aspx. In NameAndUserChoices add a label with the text “Name,” a text box, a check-box with the caption “Go to page 2,” and a button with the caption “Next.” Double click the button to add a Click event handler and add the following code:

   protected void Button1_Click( object sender, EventArgs e )   {      Dictionary propertyBag = new          Dictionary();      propertyBag[@"name"] = TextBox1.Text ;      propertyBag[@"viewPage2"] = CheckBox1.Checked ;               Wrapper.SubmitInformation(         new Guid( Request.QueryString[ @"id" ] ),         Request.QueryString[ @"informationName" ],         propertyBag );   }

Add some identifiable text and a Next button in Page2.aspx. Add the following code to the Next button’s Click handler:

   protected void Button1_Click( object sender, EventArgs e )   {      Wrapper.SubmitInformation(         new Guid( Request.QueryString[ @"id" ] ),         Request.QueryString[ @"informationName" ], null );   }

Add some identifiable text to Page3.aspx. The ASP.NET application needs an initial page on which to start the workflow. Because the initial page is determined by the workflow itself you don’t actually need a physical page; instead, you can just create an HTTP handler that responds to requests for a specific page name. Add the following code to web.config under the element:

            

Add a new class named WorkflowController containing the code in Listing 3:

Ensure the following using statements are present:

   using System;   using System.Web;   using System.Workflow.Activities;   using System.Workflow.Runtime;   using MiddleBit;

The code in Listing 3 handles any request for WorkflowController.aspx. ASP.NET will call ProcessRequest, which then:

  • Creates an instance of the workflow (remember, this “page” is only called once?at the start of the workflow).
  • Gets an instance of the GenericInformationService (which you add to the ExternalDataExchangeService via the web.config file?you’ll see how below).
  • Adds an event handler for the InformationRequested event.
  • Starts an instance of the workflow called Workflow1.
  • Tells the scheduler service to run the workflow identified by the GUID.

The informationRequested event handler that appears later in Listing 3 simply redirects to the appropriate page depending on what information is being requested. Note that the InstanceId for this workflow is also passed to the page.

You need to add a bit more to the web.config file. Add the following lines immediately under the start of the block in web.config:

        

Then, anywhere under the node, add the following lines:

                                                             

The configuration code adds the GenericInformationService to the ExternalDataExchangeService, and adds the ManualWorkflowSchedulerService to the workflow’s list of services.

You’re nearly done. You just need to tell ASP.NET to start the workflow runtime when ASP.NET starts and to stop the workflow runtime when ASP.NET stops. To do this, add a global.asax to the Web Application project (right-click on the Project item in Solution Explorer, and select Add New Component –> Global Application Class). Accept the default name of global.asax. In the Application_Start section, add the following:

   WorkflowRuntime r = new WorkflowRuntime( @"WorkflowRuntime" );   Application[ "WorkflowRuntime" ] = r;   ExternalDataExchangeService s =     new ExternalDataExchangeService(     @"ExternalDataExchangeWorkflowServices" );   r.AddService( s );      r.StartRuntime( );

In the Application_End section, add:

   WorkflowRuntime r = Application[ "WorkflowRuntime" ]       as WorkflowRuntime;   if (r != null)       r.StopRuntime( );

Finally, add the following using statements to the top of the file:

   using System.Workflow.Activities;   using System.Workflow.Runtime;

If you now debug the MyWeb project and go to WorkflowController.aspx you’ll see it start the workflow runtime, add the services, create the workflow, and run it. As soon as it runs, you’ll see the informationRequested event fire, which will redirect ASP.NET to NameAndUserChoices.aspx. Then, when you fill in this page, you’ll see the information passed back to the workflow. Verify this by sticking breakpoints in the relevant places.

Adding Pageflow Logic
At this point, the workflow can request information (name and user choices) and receive the information back. Now it’s time to add the final bit of logic that controls page flow depending on whether the user chose to “show page 2.”

Go back to the designer canvas of the workflow and add an IfElseActivity underneath the handleNameAndPage activity as shown in Figure 8.

?
Figure 8. Add IfElseActivity: Here’s how the workflow should look after you drag a new IfElseActivity to the design surface.
?
Figure 9. Modified IfElseActivity: Here’s how the workflow should look after deleting the else branch and renaming the activity, along with the new IfElseActivity Properties dialog.

In this case, you’re not going to use the else branch because you just need to check whether “viewPage2” is in the data returned from the initial page. Delete the else branch by selecting the right-hand side of the branch and pressing Delete. You should now end up with just one branch. Select that, and then open its properties dialog. In the dialog, rename the activity

?
Figure 10. Adding a Declarative Rule: When you select the Declarative Rule Condition type, two child rows appear under the Condition item.

to ifPage2Required?the activity and its properties should look like Figure 9.

You also set the condition for IfElseActivities in the activity Property dialog. Select the right-hand side of the Condition row in the properties screen. You’ll see a drop-down list. Clicking the drop-down gives you the choice to add either a code condition or a declarative rule condition. Select the latter. Two child rows will appear as shown in Figure 10.

Type something for the ConditionName field such as page2Specified and then click the ellipsis in the Condition Expression field. This brings up a dialog box with the name of the expression. Click the Edit button and add the following code:

   (bool)this.nameAndPageChoicesArgs.PropertyBag[      "viewPage2"] == true
?
Figure 11. Calling Page Two: An additional CallExternalMethod activity handles the call to page two of the application.

Step one of the workflow asks for data. Step 2 stores the response. This condition checks whether an item in the property bag is true. Remember, this field in the property bag gets set when a user clicks the “Next” button on the first page. So, if the value is true, the user wants to view page 2?so add another CallExternalMethod activity into the ifElseActivity branch (see Figure 11).

?
Figure 12. Final Workflow: Here’s how the workflow should look after adding the final activities.

As in the first request, the code calls the RequestInformation method on the IGenericInformationService interface, and passes in as a parameter the string “page2.”

At this point, the GenericInformationService.RequestInformation method gets called in the concrete class. This then fires the event InformationRequested, which the ASP.NET application receives. Control then shifts to ASP.NET to decide which page to redirect to. When it’s finished, control returns to the workflow.

The very last bit involves waiting for a response from “page 2” and then calling “page 3.” So, add a HandleExternalEvent activity in the ifElse branch and then add another CallExternalMethodActivity (outside of the branch) and set the informationName property to “page 3” as shown in Figure 12

This document has described a simple way for WF and ASP.NET to talk to each other by building an example that shows how to integrate WF into your ASP.NET applications to control simple page-flow logic. There are doubtless many other ways to achieve the same result, but the other ways I’ve seen appear (to me anyway!) to be slightly more complex.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist