Click here to Skip to main content
15,860,943 members
Articles / Web Development / HTML
Article

Vacation Request 2.0 - Windows Workflow Foundation (WF) and DotNetNuke™

Rate me:
Please Sign up or sign in to vote.
3.57/5 (5 votes)
10 Oct 2008GPL39 min read 44.9K   1.1K   25   9
A DotNetNuke module communicating with a WF workflow hosted in the Open Source project IWebWF

Image 1

Introduction

Windows WorkFlow (WF) is a powerful framework for creating enterprise level workflows. Such power also brings great complexity. Much of WF is "roll your own", you are provided the core components but much of the required elements are up to you to provide. There are many ways of hosting workflows, this article describes hosting workflows using .asmx web services.

This example project shows a complete WF solution composed of a DotNetNuke module communicating with a WF workflow hosted in the Open Source project IWebWF (IWebWF.com). The project is called Vacation Requests and it allows users to make requests for time off from work. The requests will be processed by the workflow using the following business rules:

  • If Employee has enough days available and they are requesting less than 4 days, automatically approve the request.
  • If they don’t have enough days available, or more than 3 days are requested, then require approval.

This article examines the project using the following outline:

  • Overview of Vacation Request 2.0
  • Set-up
  • Make a request
    • How to call a web service dynamically
  • Approve a request
    • Create a CheckDigit and call the Vacation Request Workflow web service
    • Call the DNN web service and update the record
  • Logging
    • DNN Module
      • History Page
    • IWebWF
      • Using the IWebCore for Logging and Emails
      • Status page shows actively persisted workflows
      • Details page identifies the workflow
  • Security
    • Workflow is "disconnected". It only uses web services in and out.
    • The CheckDigit is used to open and close "the door"

Overview of Vacation Request 2.0

The following diagram shows the basic structure of the Vacation Request application:

Image 2

The "front-end" of the application resides in a module running in the DotNetNuke website. The "back-end" processing for the application resides in the workflow running in the IWebWF website. The DotNetNuke module and the workflow communicate using web services.

The DotNetNuke module exposes the following web services:

  • UpdateVacationRequest - Allows the workflow the ability to update a vacation request record.
  • GetUserDetails - Provides details about a vacation request including the days a user has available.

The workflow exposes these web services:

  • StartWorkflow - Allows the DNN module the ability to start a vacation request workflow.
  • ApproveRequest - Allows the DNN module the ability to approve a vacation request.

WF workflows can be hosted in a number of different ways. A workflow could even be hosted in the DotNetNuke website. This design was chosen because it allows the solution to be scaled by adding multiple instances of the IWebWF application. In addition, the workflow components and persistence services can be resource intensive. It is desirable to off-load this from the DotNetNuke website.

Windows Communication Foundation (WCF) can be used instead of the .asmx web services described in this solution. While this works, WCF introduces complications when constructing the WCF services that provide unnecessary challenges, such as needing complex configuration files to bind the protocol and the transport.

Set-up

To set-up the application, you need to install IWebWF and DotNetNuke. You can download IWebWF from: http://www.codeplex.com/IWebWF and DotNetNuke from: http://DotNetNuke.com.

IWebWF

Image 3

After installing IWebWF, log in as the administrator and configure and test the Email settings.

Image 4

Unzip the files from the VacationRequestWorkflow.zip file and place the VacationRequest.dll file in the "Bin" directory and the VacationRequest.asmx file in the "Webservice" directory.

DotNetNuke

Install the Vacation Request_02.00.00_Install.zip module using the normal DotNetNuke module installation process (if using DNN4 Run LinqPrep first).

Image 5

Log in as an administrator and click the [Set Webservice URL] link and set the web service link to point to the VacationRequest.asmx page in the IWebWF site.

Image 6

The [Edit Users Vacation Days] (from the main page of the module) allows the administrator to set the available days for users.

Also, ensure the DNN admin account has an email address and the SMTP settings are configured in the DNN site.

Make a Request

Image 7

A vacation request starts in the DNN module. It makes a web service call to the workflow web service. The DNN module creates a random number and saves it in the database as a CheckDigit. The RequestID, the CheckDigit, the Portal administrator and users email addresses are passed to the workflow web service.

Creating a proxy in DNN to call an external web service

Image 8

To allow a DNN module to call an external web service and change the address dynamically, a VacationWebService project is created that contains a normal .asmx web proxy that connects to the workflow web service in the IWebWF site.

The project is compiled and the VacationWebService.dll file that is created, is placed in the "Bin" directory of the DNN site. This allows you to instantiate the class with code such as this:
VacationRequestWorkflow_WebService wsVacationRequest = new VacationRequestWorkflow_WebService();
and alter the web service address dynamically with code like this:
// Set the address to the web service      
wsVacationRequest.Url = GetWebServiceURL();
The following, shows the complete code to create a random CheckDigit code and call the workflow web service:
// Create a record
VacationRequestDAL VacationRequestDAL = new VacationRequestDAL();
VacationRequest VacationRequest = new VacationRequest();
VacationRequest.Approved = false;
VacationRequest.CheckDigit = GetRandomNumber();
VacationRequest.Complete = false;
VacationRequest.CreatedDate = DateTime.Now;
VacationRequest.DateRequested = Convert.ToDateTime(txtRequestedDate.Text);
VacationRequest.DaysRequested = Convert.ToInt32(txtRequestedDays.Text);
VacationRequest.LastActiveDate = DateTime.Now;
VacationRequest.NeedsApproval = false;
VacationRequest.UserID = UserId;
VacationRequest.WorkflowInstanceId = "";

VacationRequestDAL.VacationRequests.InsertOnSubmit(VacationRequest);
VacationRequestDAL.SubmitChanges();

// Reference to the web service            
VacationRequestWorkflow_WebService wsVacationRequest = new VacationRequestWorkflow_WebService();
// Enable cookies            
wsVacationRequest.CookieContainer = new System.Net.CookieContainer();
// Set the address to the web service           
wsVacationRequest.Url = GetWebServiceURL();
// Call the method to start the workflow            
Guid WorkflowInstanceID = wsVacationRequest.StartWorkflow(VacationRequest.RequestID,
    VacationRequest.CheckDigit, GetLocalWebserviceURL(), PortalSettings.Email, UserInfo.Email);

// Update the WorkflowInstanceID
UpdateWorkflowInstanceID(VacationRequest.RequestID, WorkflowInstanceID);

pnlVacationRequest.Visible = false;
lblError.Text = "Request submitted. You will receive an email with a response";

VacationRequest Workflow

The VacationRequest workflow has an interface that defines the web services that it exposes (StartWorkflow and ApproveRequest):

    public interface IVacationRequest
{
    Guid StartWorkflow(int parmRecordID, int parmCheckDigit, string parmWebService,
        string parmAdministratorEmailstring, string parmEmployeeEmail);

    bool ApproveRequest(int parmRecordID, int parmCheckDigit, bool parmApproval);
}

Image 9

The VacationRequestWorkflow.cs file contains the workflow logic for the workflow.

Image 10

The StartVacationRequest activity is bound to the StartWorkflow web method that starts the workflow. After the workflow is started, the wsGetUserDetails activity calls the GetUserDetails web service in the DNN site.

Image 11

The workflow is able to set the web address dynamically by wiring-up a method to the Invoking event and setting the url of the .asmx web proxy that points to the web service in the DNN module.

// Set the URL to the web service to the URL that was passed whe the Workflow was started
wsVacationRequest.WebService objWebService = (wsVacationRequest.WebService)e.WebServiceProxy;
objWebService.Url = WebService; 

The workflow calls this web service method in the DNN site which returns information about the vacation request:

public UserDetails GetUserDetails(int RecordID, int CheckDigit)
{
    UserDetails UserDetails = new UserDetails();

    VacationRequestDAL VacationRequestDAL = new VacationRequestDAL();

    // Search for a matching record
    var VacationRequestsResult = (from VacationRequests in VacationRequestDAL.VacationRequests
                                  where VacationRequests.RequestID == RecordID &
                                  VacationRequests.CheckDigit == CheckDigit &
                                  VacationRequests.CheckDigit != 0
                                  select VacationRequests).FirstOrDefault();

    // If the record is found return the result
    if (VacationRequestsResult != null)
    {
        var VacationDaysResult = (from Days in VacationRequestDAL.VacationDays
                                  where Days.UserID == VacationRequestsResult.UserID
                                  select Days).FirstOrDefault();

        UserDetails.RequestedDate = VacationRequestsResult.DateRequested;
        UserDetails.DaysRequested = VacationRequestsResult.DaysRequested;
        UserDetails.VacationDays = VacationDaysResult.VacationDays;
    }
    else
    {
        // Set the values so that the Workflow service will know the request is bad
        // The workflow will receive these values and terminate the workflow
        UserDetails.RequestedDate = new DateTime(1900,1,1);
        UserDetails.DaysRequested = 0;
        UserDetails.VacationDays = 0;
    }

    return UserDetails;
}

Image 12

After the workflow receives the information about the vacation request, the process reaches the ifElseActivity_ProcessRequest decision point. There are three possible activity groups that could be processed based on the values received from the DNN web service.

Image 13

When you right-click on the TerminateWorkflowBranch...

Image 14

and select Properties, you will be able to see the see the rule conditions that determine the execution of that branch (the days requested, and the vacation days are 0).

Image 15

The same process can be performed to see the rule conditions for the NeedsApprovalBranch (the days requested is more than 3 days, or the days requested is more than the vacation days, or the vacation days minus the days requested would be less than 0).

(see this article for more information on using the rules engine)

Image 16

The ApprovedBranch does not have a rule condition and will be executed by default if the rule conditions for TerminateWorkflowBranch and NeedsApprovalBranch are not met (the ifElseActivity branches are evaluated from left-to-right).

Approve a Request

If the days requested and the vacation days are 0 the workflow will terminate. If a request is for less than 4 days and there are sufficient days available, the request is automatically approved. An email is sent to the user, and the workflow terminates.

Image 17

For all other situations, the NeedsApprovalBranch branch is executed. This branch contains activities that call the web service in the DNN site to update the record. It sets the record as needing approval, and sends an email to the administrator indicating that there is a record that needs approval.

Image 18

The workflow then proceeds to the WaitForApproval activity. This activity contains the ApprovalWebService group.

Image 19

The WaitForApproval activity is a WhileActivity that will stay in a loop until the Code Condition is met. It will not break out of the loop until the value of CheckIsTimeToStop method returns true.

Image 20

The wsApprovalRequest_Input activity is the first activity in the ApprovalWebService group. The wsApprovalRequest_Input activity is bound to the ApproveRequest web method in the workflow. It will now wait for a web service call from the DNN site to approve the request.

DNN Module

When logged into the DNN site as an administrator, the [Approve Requests] link will take you to the approval page.

Image 21

The following code creates a new random CheckDigit and calls the workflow web service:

bool boolApproval = false;
string strCommandArgument = (string)e.CommandArgument;
int intRecordID = Convert.ToInt32(e.CommandName);

int CheckDigit = GetRandomNumber();
Guid WorkflowInstanceID = UpdateCheckDigitAndGetWorkflowInstanceID(intRecordID, CheckDigit);

if (strCommandArgument == "Approve")
{
    // Approve
    boolApproval = true;
}
else
{
    // Decline
    boolApproval = false;
}

// Reference to the web service
VacationRequestWorkflow_WebService wsVacationRequest = new VacationRequestWorkflow_WebService();
// Enable cookies
wsVacationRequest.CookieContainer = new System.Net.CookieContainer();
// Set the address to the web service
wsVacationRequest.Url = GetWebServiceURL();
// Create a URI
Uri VacationRequestUri = new Uri(wsVacationRequest.Url);
// Enable cookies
wsVacationRequest.CookieContainer = new System.Net.CookieContainer();
// Use the URI to obtain a collection of the cookies
CookieCollection mycollection = wsVacationRequest.CookieContainer.GetCookies(VacationRequestUri);
// Add the current WorkflowInstanceId to the cookie collection
// that will be passed to the web service
wsVacationRequest.CookieContainer.SetCookies
    (
    VacationRequestUri,
    String.Format("{0}={1}", "WF_WorkflowInstanceId", WorkflowInstanceID.ToString())
    );
// Call the method on the workflow
wsVacationRequest.ApproveRequest(intRecordID, CheckDigit, boolApproval);

Vacation Request Workflow

Image 22

The workflow receives the approval call from the DNN site and processes the activities in the ApprovalProcess activity group. The wsUpdateVacationRequestApproval activity calls the following UpdateVacationRequest web service method in the DNN site to update the record:

public bool UpdateVacationRequest(int RecordID, int CheckDigit, int VacationDays, bool NeedsApproval, bool Approved, bool Complete)
{
    VacationRequestDAL VacationRequestDAL = new VacationRequestDAL();

    // Search for a matching record
    var VacationRequestsResult = (from VacationRequests in VacationRequestDAL.VacationRequests
                                  where VacationRequests.RequestID == RecordID &
                                  VacationRequests.CheckDigit == CheckDigit &
                                  VacationRequests.CheckDigit != 0
                                  select VacationRequests).FirstOrDefault();

    // Only update if record is found
    if (VacationRequestsResult != null)
    {
        VacationRequestsResult.Approved = Approved;
        VacationRequestsResult.Complete = Complete;
        VacationRequestsResult.NeedsApproval = NeedsApproval;
        VacationRequestsResult.LastActiveDate = DateTime.Now;
        // Set Check Digit to 0 so that the record can not be updated by web services
        VacationRequestsResult.CheckDigit = 0;

        // Update Vacation Days
        var VacationDaysResult = (from Days in VacationRequestDAL.VacationDays
                                  where Days.UserID == VacationRequestsResult.UserID
                                  select Days).FirstOrDefault();

        VacationDaysResult.VacationDays = VacationDays;

        // Commit changes
        VacationRequestDAL.SubmitChanges();
    }

    return true;
}

In the workflow, the isTimeToStop value is set to true which causes the CheckIsTimeToStop method to return true.

#region CheckIsTimeToStop
// 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);
}
#endregion
This causes WaitForApproval activity to stop and the workflow terminates. The process is complete.

Logging

When workflows are running it is hard to know what is going on without logging.

Image 23

The DNN module provides some logging by clicking on the [History] link (when logged in as an administrator).

Image 24

The VacationRequest workflow project contains a reference to the IWebCore project that allows it to easily log actions with code such as this:

#region WriteToIWebWFLog
private void WriteToIWebWFLog(string LogEvent)
{
    ApplicationLog.AddToLog(string.Format("VacationRequest Event - WorkflowInstanceID: {0} Event: {1}",
        this.WorkflowInstanceId.ToString(), LogEvent));
}

private void WriteErrorToIWebWFLog(string Error)
{
    ApplicationLog.AddToLog(string.Format("VacationRequest Error - WorkflowInstanceID: {0} Error: {1}",
        this.WorkflowInstanceId.ToString(), Error));
}
#endregion

The IWebCore project also allows emails to be sent using code such as this:

#region SendEmailtoEmployee
private void SendEmailtoEmployee()
{
    try
    {
        Email Email = new Email();
        string strEmailMessage = String.Format("Your Vacation Request for {0} {1}.",
            objUserDetails.RequestedDate.ToShortDateString(), (Approval) ? "has been approved" : "has been declined");

        Email.SendMail(EmployeeEmail, "", "", "", "Email From VacationRequest",
            strEmailMessage, "");
        WriteToIWebWFLog(strEmailMessage);
    }
    catch (Exception ex)
    {
        WriteErrorToIWebWFLog(String.Format("{0} - {1} ", ex.Message, ex.StackTrace));
    }
}
#endregion

Image 25

The logged events show in the administration page in the IWebWF website.

Image 26

In addition, the workflow status page in the IWebWF website shows any currently persisted workflows. These are workflows that are waiting for some sort of action before they can terminate. The persistence service automatically saves the state of idled workflows and automatically reactivates them when needed. This allows long running workflows to survive during server restarts.

Image 27

Clicking on a workflow GUID on the Status page displays the workflow name and it's activities.

Security

Windows workflow allows you to design security in any way you prefer. This example uses the following security design:

  • The workflow is "disconnected". It only communicates using web services
  • The workflow does not "believe" any information passed to it. The available days for an employee cannot be passed to it directly. When the workflow is started, it uses the vacation RequestID and the CheckDigit to retrieve the information needed for it's calculations. This prevents a hacker from passing false information.
  • The web service methods in the DNN module only allow records to be retrieved and updated for a short period of time. In most cases only a few seconds. The DNN module essentially "opens the door" by creating a record and sending the RequestID and the CheckDigit to the workflow. The workflow updates the record and then sets the CheckDigit to 0. This "closes the door" to the record.

Versioning

If you deploy a workflow and you change it while you still have workflows that are still running using the old version, you will get an error when those workflows try to re-load. To avoid this, you will need to use workflow versioning.

See this article for information on workflow versioning with IWebWF

Summary

The question remains, why would you want to go through all this trouble? This example barely qualifies as a justifiable use of WF. The reasons to use workflow rather than simple procedural code is:

  • If the rules become more complex the workflow is easily adjusted
  • The Workflow can perform jobs better and faster if complex calculations are needed to determine when a request is approved

In addition, using IWebWF eliminates a lot of unnecessary work.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer (Senior) http://ADefWebserver.com
United States United States
Michael Washington is a Microsoft MVP. He is a ASP.NET and
C# programmer.
He is the founder of
AiHelpWebsite.com,
LightSwitchHelpWebsite.com, and
HoloLensHelpWebsite.com.

He has a son, Zachary and resides in Los Angeles with his wife Valerie.

He is the Author of:

Comments and Discussions

 
QuestionObject reference not set to an instance of an object - step: Install Database Script - IWebWF Pin
Member 1185754727-Jul-15 4:07
Member 1185754727-Jul-15 4:07 
AnswerRe: Object reference not set to an instance of an object - step: Install Database Script - IWebWF Pin
defwebserver27-Jul-15 4:19
defwebserver27-Jul-15 4:19 
General"WorkflowRuntime" cannot be found error Pin
mcsenow13-May-10 10:40
mcsenow13-May-10 10:40 
GeneralRe: "WorkflowRuntime" cannot be found error Pin
defwebserver13-May-10 12:43
defwebserver13-May-10 12:43 
QuestionTracking service question Pin
jst9-Feb-09 4:19
jst9-Feb-09 4:19 
Hi Michel, I'm trying to use IWebWF with Tracking Service enabled, due to I need to monitor my workflows. I added the line " <add type="System.Workflow.Runtime.Tracking.SqlTrackingService, System.Workflow.Runtime, Version=3.0.00000.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35"> " in the web.config (IWebWF site) but I get an error, the workflow stop working.
Do you know why I cann´t use Tracking Service with IWebWF?

Thanks in advance, José

qq

AnswerRe: Tracking service question Pin
defwebserver9-Feb-09 4:36
defwebserver9-Feb-09 4:36 
AnswerRe: Tracking service question Pin
jst9-Feb-09 4:53
jst9-Feb-09 4:53 
GeneralRe: Tracking service question Pin
defwebserver9-Feb-09 5:27
defwebserver9-Feb-09 5:27 
GeneralRe: Tracking service question Pin
jst9-Feb-09 8:23
jst9-Feb-09 8:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.