Vacation Request - Windows Workflow Foundation (WF) and DotNetNuke

Windows Workflow Foundation is a framework for managing workflows. This article describes an implementation using a DotNetNuke module communicating with a Windows Workflow service hosted in a web service.

Windows Workflow Foundation is a powerful enterprise level framework that allows you to model business processes and execute and manage workflows. Yes, you could achieve the same functionally purely in procedural code, however, for complex business processes you will usually end up with an unmanageable heap of spaghetti code. Windows Workflow Foundation allows you to graphically model the workflow processes and easily change them.

The example application is called "Vacation Request". The application allows a user to start a vacation request and have it approved (or not). The application is not really practical, it is simply an example of the minimum required components needed to create an application using a DotNetNuke module communicating with a Windows Workflow service (hosted as a web service).

The following is not a tutorial. It is just an overview of the steps used to create the sample code. It does not contain important components you would need to add in a real workflow application such as the SqlWorkflowPersistenceService. It also does not contain security for the web methods. See Implementing "Super Tight Security" for an example of web method security.

Creating the Web Service

ASP.NET 3.51SP1 (or higher) includes additional components to host Windows Workflow using WCF. This example however, uses normal .asmx web services.

Visual Studio 2008 is used to create a new project called VacationRequest.

A interface class (IVacationRequest.cs) is created that indicates the web methods that will be used:

using System;
 
namespace VacationRequest
{
    public interface IVacationRequest
    {
        /// Activate the workflow
        void StartWorkflow();
 
        /// Request Days
        bool RequestedDays(int RequestedDays);
 
        /// Get Status
        String VacationRequestStatus();
 
        /// Allow the workflow to stop
        void StopWorkflow();
    }
}

A Sequential Workflow (VacationRequestWorkflow.cs) class is added to the project.

Activities for the class (to implement the flow) are assembled using the designer in Visual Studio.

A WhileActivity (ProcessRequest) is used to keep the workflow instance active while the EventActivities are repeatedly called (note, that this example does not use persistence services so the workflow instance will eventually terminate when the asp.net process terminates).

ProcessRequest will normally terminate only when the CheckIsTimeToStop variable (in the code behind) is true. Notice that CheckIsTimeToStop is set as the Condition.

The ProcessRequest activity uses three EventDrivenActivitys to group the  RequestedDays, VacationRequestStatus, and StopWorkflow web methods (the StartWorkflow method is mapped to the StartVacationRequest activity (at the top of the workflow diagram)).

 

For example, EventDrivenActivity1 is used to group a  WebServiceInputActivity (RequestedDaysInput), a CodeActivity (ApproveRequest) , and a WebServiceOutputActivity (RequestedDaysOutput).

The WebServiceInputActivity (RequestedDaysInput) is used to map the RequestedDays web service method, and the parameters passed to it, to the workflow.

The CodeActivity (ApproveRequest) executes code (ApproveRequest_ExecuteCode) that will change the status of the Vacation Request (the RequestStatus variable in the code behind).

The WebServiceOutputActivity (RequestedDaysOutput) completes the web service call. This activity could return values from the workflow. In this example it simply returns a positive value.

The following code is the complete code behind for the class:

using System;
using System.Workflow.Activities;
 
namespace VacationRequest
{
    public sealed partial class VacationRequestWorkflow : SequentialWorkflowActivity
    {
        public int RequestDays;
        public string RequestStatus;
        public Boolean isTimeToStop = false;
 
        public VacationRequestWorkflow()
        {
            InitializeComponent();
        }
 
        // This method will return the value of isTimeToStop
        // The value of isTimeToStop will be set by other
        // methods in the workflow
        private void CheckIsTimeToStop(object sender, ConditionalEventArgs e)
        {
            e.Result = !(isTimeToStop);
        }
 
        // Request will be approved if RequestDays is less than 3 days
        private void ApproveRequest_ExecuteCode(object sender, EventArgs e)
        {
            RequestStatus = (RequestDays < 3) ? "Approved" : "Not Approved";
        }
 
        // When this method is called isTimeToStop will be set to true
        // This will cause the workflow to terminate
        private void webServiceInputActivity1_InputReceived(object sender, EventArgs e)
        {
            //Stop the WhileActivity
            isTimeToStop = true;
        }
    }
}

The code is complete. Publish as Web Service is now selected.

A web service project is created and when the VacationRequest.VacationRequestWorkflow_WebService.asmx page is viewed in the web browser...

The web methods are displayed.

NOTE: If the sample code for the web service is run using IIS7, the Application Pool must be set to "Classic .NET AppPool".

The DotNetNuke Module Web Service Reference

A DotNetNuke module is then created that will be able to communicate with the Windows Workflow Foundation web service. First, a web proxy is created.

The DotNetNuke website is opened in Visual Studio and a new project is added.

A simple class project called VacationWebService is created.

A web reference is created that points to the web service created in the earlier step. Note, we will be able to change the address to the web service programmatically in a later step.

The DotNetNuke Module

The DotNetNuke module allows you to enter the URL to the web service and after clicking the Create Vacation Request button, retrieve the WorkflowID. The WorkflowID will be passed in all subsequent requests for the current instance of the workflow. The web service is expecting the WorkflowID to be passed in the header.

The following is the code for the button that performs the task:

        protected void btnCreateVacationRequest_Click(object sender, EventArgs e)
        {
            // Reference to the web service
            VacationRequestWorkflow_WebService VacationRequest = new VacationRequestWorkflow_WebService();
            // Enable cookies
            VacationRequest.CookieContainer = new System.Net.CookieContainer();
            // Set the address to the web service
            VacationRequest.Url = txtWebserviceURL.Text.Trim();
            // Call the method to start the workflow
            VacationRequest.StartWorkflow();
            // Create a URI
            Uri VacationRequestUri = new Uri(VacationRequest.Url);
            // The web service will pass the WorkflowInstanceId back in the cookie collection 
            // Use the URI to obtain a collection of the cookies
            CookieCollection mycollection = VacationRequest.CookieContainer.GetCookies(VacationRequestUri);
            // Loop through each cookie until the WF_WorkflowInstanceId cookie is found
            foreach (Cookie Cookie in mycollection)
            {
                if (Cookie.Name == "WF_WorkflowInstanceId")
                {
                    // Display the WF_WorkflowInstanceId  value in the text box
                    txtCurrentWorkflowID.Text = Cookie.Value;
                }
            }

The next step is to request days.

The following is the code used to perform this task:

            // Reference to the web service
            VacationRequestWorkflow_WebService VacationRequest = new VacationRequestWorkflow_WebService();
            // Set the address to the web service
            VacationRequest.Url = txtWebserviceURL.Text.Trim();
            // Create a URI
            Uri VacationRequestUri = new Uri(VacationRequest.Url);
            // Enable cookies
            VacationRequest.CookieContainer = new System.Net.CookieContainer();
            // Use the URI to obtain a collection of the cookies
            CookieCollection mycollection = VacationRequest.CookieContainer.GetCookies(VacationRequestUri);
            // Add the current WorkflowInstanceId to the cookie collection 
            // that will be passed to the web service
            VacationRequest.CookieContainer.SetCookies
                (
                VacationRequestUri,
                String.Format("{0}={1}", "WF_WorkflowInstanceId", txtCurrentWorkflowID.Text.Trim())
                );
            // Call the RequestedDays web method and pass the requested days
            VacationRequest.RequestedDays(Convert.ToInt32(txtRequestedDays.Text));

Now the status of the Vacation Request can be retrieved. If the request is not approved you can change the days and resubmit using the same workflow instance.

The following is the code used to perform this task:

            // Reference to the web service
            VacationRequestWorkflow_WebService VacationRequest = new VacationRequestWorkflow_WebService();
            // Set the address to the web service
            VacationRequest.Url = txtWebserviceURL.Text.Trim();
            // Create a URI
            Uri VacationRequestUri = new Uri(VacationRequest.Url);
            // Enable cookies
            VacationRequest.CookieContainer = new System.Net.CookieContainer();
            // Use the URI to obtain a collection of the cookies
            CookieCollection mycollection = VacationRequest.CookieContainer.GetCookies(VacationRequestUri);
            // Add the current WorkflowInstanceId to the cookie collection 
            // that will be passed to the web service
            VacationRequest.CookieContainer.SetCookies
                (
                VacationRequestUri,
                String.Format("{0}={1}", "WF_WorkflowInstanceId", txtCurrentWorkflowID.Text.Trim())
                );
            // Call the VacationRequestStatus web method and retrieve the status
            lblRequestStatus.Text = VacationRequest.VacationRequestStatus();

The final step is to stop the workflow.

The following is the code used to perform this task:

            // Reference to the web service
            VacationRequestWorkflow_WebService VacationRequest = new VacationRequestWorkflow_WebService();
            // Set the address to the web service
            VacationRequest.Url = txtWebserviceURL.Text.Trim();
            // Create a URI
            Uri VacationRequestUri = new Uri(VacationRequest.Url);
            // Enable cookies
            VacationRequest.CookieContainer = new System.Net.CookieContainer();
            // Use the URI to obtain a collection of the cookies
            CookieCollection mycollection = VacationRequest.CookieContainer.GetCookies(VacationRequestUri);
            // Add the current WorkflowInstanceId to the cookie collection 
            // that will be passed to the web service
            VacationRequest.CookieContainer.SetCookies
                (
                VacationRequestUri,
                String.Format("{0}={1}", "WF_WorkflowInstanceId", txtCurrentWorkflowID.Text.Trim())
                );
            // Stop the workflow and delete the workflow instance
            VacationRequest.StopWorkflow();

If you try to request status of the WorkflowInstanceID after the workflow has been stopped and deleted, you will get an error because the workflow instance no longer exists.

A Simple Example with Big Possibilities

The real power of Windows Workflow Foundation is it's ability to allow you to easily change the workflow. The workflow can send emails, communicate with legacy systems and log all events. The workflow can have also have events that will send an email if too much time has passed before a request is approved.

In addition, client technology such as Silverlight can be used to provide a deeper richer interface to perform tasks.

Sample Code

Vacation Request Workflow web service: wfVacationRequest.zip (must have ASP.NET 3.51SP1 or higher installed and use Visual Studio 2008 to open)

DotNetNuke module: VacationRequest_01.00.00_Install.zip (note: this has only been tested on a DotNetNuke site running under full trust)

 

[Back to: The ADefWebserver DotNetNuke HELP WebSite]


 DotNetNuke Powered!DotNetNuke is a registered trademark of DotNetNuke Corporation.