Introduction
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.51 SP1 (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
) is created that indicates the web methods that will be used:
using System;
namespace VacationRequest
{
public interface IVacationRequest
{
void StartWorkflow();
bool RequestedDays(int RequestedDays);
String VacationRequestStatus();
void StopWorkflow();
}
}
A Sequential Workflow (VacationRequestWorkflow
) 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 EventDrivenActivity
s 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 EventDrivenActivity
s 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();
}
private void CheckIsTimeToStop(object sender, ConditionalEventArgs e)
{
e.Result = !(isTimeToStop);
}
private void ApproveRequest_ExecuteCode(object sender, EventArgs e)
{
RequestStatus = (RequestDays < 3) ? ''Approved'' : ''Not Approved'';}
private void webServiceInputActivity1_InputReceived(object sender, EventArgs e)
{
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)
{
VacationRequestWorkflow_WebService VacationRequest =
new VacationRequestWorkflow_WebService();
VacationRequest.CookieContainer = new System.Net.CookieContainer();
VacationRequest.Url = txtWebserviceURL.Text.Trim();
VacationRequest.StartWorkflow();
Uri VacationRequestUri = new Uri(VacationRequest.Url);
CookieCollection mycollection =
VacationRequest.CookieContainer.GetCookies(VacationRequestUri);
foreach (Cookie Cookie in mycollection)
{
if (Cookie.Name == ''WF_WorkflowInstanceId'')
{
txtCurrentWorkflowID.Text = Cookie.Value;
}
}
}
The next step is to request days.
The following is the code used to perform this task:
protected void btnRequestDays_Click(object sender, EventArgs e)
{
VacationRequestWorkflow_WebService VacationRequest =
new VacationRequestWorkflow_WebService();
VacationRequest.Url = txtWebserviceURL.Text.Trim();
Uri VacationRequestUri = new Uri(VacationRequest.Url);
VacationRequest.CookieContainer = new System.Net.CookieContainer();
CookieCollection mycollection =
VacationRequest.CookieContainer.GetCookies(VacationRequestUri);
VacationRequest.CookieContainer.SetCookies
(
VacationRequestUri,
String.Format(''{0}={1}'', ''WF_WorkflowInstanceId'',
txtCurrentWorkflowID.Text.Trim())
);
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:
protected void btnGetStatus_Click(object sender, EventArgs e)
{
VacationRequestWorkflow_WebService VacationRequest =
new VacationRequestWorkflow_WebService();
VacationRequest.Url = txtWebserviceURL.Text.Trim();
Uri VacationRequestUri = new Uri(VacationRequest.Url);
VacationRequest.CookieContainer = new System.Net.CookieContainer();
CookieCollection mycollection =
VacationRequest.CookieContainer.GetCookies(VacationRequestUri);
VacationRequest.CookieContainer.SetCookies
(
VacationRequestUri,
String.Format(''{0}={1}'', ''WF_WorkflowInstanceId'',
txtCurrentWorkflowID.Text.Trim())
);
lblRequestStatus.Text = VacationRequest.VacationRequestStatus();
}
The final step is to stop the workflow.
The following is the code used to perform this task:
protected void btnStopWorkflow_Click(object sender, EventArgs e)
{
VacationRequestWorkflow_WebService VacationRequest =
new VacationRequestWorkflow_WebService();
VacationRequest.Url = txtWebserviceURL.Text.Trim();
Uri VacationRequestUri = new Uri(VacationRequest.Url);
VacationRequest.CookieContainer = new System.Net.CookieContainer();
CookieCollection mycollection =
VacationRequest.CookieContainer.GetCookies(VacationRequestUri);
VacationRequest.CookieContainer.SetCookies
(
VacationRequestUri,
String.Format(''{0}={1}'', ''WF_WorkflowInstanceId'',
txtCurrentWorkflowID.Text.Trim()));
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 its ability to allow you to easily change a workflow. A workflow can send emails, communicate with legacy systems, and log all events. The workflow can also have events that will send an email if too much time has passed before a request is approved.
In addition, client technologies such as Silverlight can be used to provide a deeper richer interface to perform tasks.