Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Creating Sequential Workflows in Visual Studio 2005 – A Sample

3.25/5 (7 votes)
2 Jun 2007CPOL10 min read 1  
This article explains the steps in creating a sequential workflow in Visual Studio 2005 using Windows Workflow Foundation in .NET Framework 3.0.

Author's Note

This three-part article is an adaptation of one of the tutorials found in MSDN library for creating a sequential workflow ( http://msdn2.microsoft.com/en-us/library/ms734794.aspx). The main purpose of re-producing the same sample here is to give readers more clarity and explanation in performing the steps to create a workflow. Most of my friends and community peers have expressed a deep sense of dissatisfaction in understanding the steps defined in the workflow creation process. They have cited certain sample pages which are thoroughly imprecise and not clear enough to follow through the steps.

The MSDN tutorial consists of three exercises featured in these URLs:

In this article, keeping the major exercises intact, I have explained the steps, tasks, and coding parts in detail, and included a few diagrams so that it would be easier for anyone to start with creating a sequential workflow.

Introduction

This sample application is a Simple Expense Report that consists of a text field to enter an amount and a button to submit the expense report. The workflow uses rules to evaluate the amount and to require approval from a lead if the amount is less than 1000, or approval from a manager if the amount is greater than or equal to 1000. If approval is needed, the workflow communicates back to the application and displays a drop-down panel that contains Approve and Reject buttons. When one of these buttons is clicked, the application notifies the workflow of the response, and the workflow continues to process the event.

This walk-through contains three major steps and sub-tasks under each step:

  1. Create Expense Report Project.
    • Create a Windows Application
    • Create the Sequential Workflow
    • Host the Sequential Workflow
  2. Create Expense Report Service.
    • Define the Workflow Parameters
    • Define the IExpenseReportService Interface
  3. Create Expense Report Sequential Workflow Class Library
    • Create the CallExternalMethod Activities
    • Create the HandleExternalEvent Activities

1. Create Expense Report Project

Create a Windows Application

In Visual Studio 2005, select New Project; under C# project types, select Windows Application.

Change the name of the default form to "MainForm" that appears in the project, drag and drop the controls as required, and design the layout as in the figure below.

Screenshot - pic1.jpg

Here is a table of controls and their properties defined in the form.

Control

Property

Value

Form

Name

MainForm

Text

Simple Expense Report

Label

Name

Label1

Text

Amount

Label

Name

Label2

Text

Result

TextBox

Name

amount

Text

TextBox

Name

result

Text

Label

Name

ApprovalState

Text

Approval

Button

Name

submitButton

Text

Submit

Button

Name

approveButton

Text

Approve

Button

Name

rejectButton

Text

Reject

Panel

Name

Panel1

Add approveButton and rejectButton inside panel1.

Add two event handler methods for the KeyPress and TextChanged events of the amount textbox.

C#
private void amount_KeyPress(object sender, KeyPressEventArgs e) 
{
    if (!Char.IsControl(e.KeyChar) && (!Char.IsDigit(e.KeyChar))) 
        e.KeyChar = Char.MinValue; 
} 
private void amount_TextChanged(object sender, EventArgs e) 
{ 
    submitButton.Enabled = amount.Text.Length > 0; 
}

Ensure that you have replaced the code in Program.cs that runs the application's default form.

C#
Application.Run(new MainForm());

And finally, to complete this first step, add references to the following assemblies.

  • System.Workflow.Activities
  • System.Workflow.ComponentModel
  • System.Workflow.Runtime

Build the application.

Create a Sequential Workflow

In Visual Studio 2005, select New Project from menu; under C# project types, select Workflow, and on the right-side panel of templates, choose Sequential Workflow Library.

Name the project ExpenseReportWorkflow.

In the Workflow designer file (Workflow1.designer.cs) created by default, modify the code in the IntializeComponent method as below:

C#
this.Name = "ExpenseReportWorkflow"; 
this.CanModifyActivities = false; 

Build the project.

Host the Sequential Workflow

To execute a workflow, you must create an instance of the WorkflowRuntime class. Then, create a host class and pass in the type specification of your workflow by using the CreateWorkflow method. You can then call the Start method from the returned WorkflowInstance object to start executing your workflow. Because a sequential workflow does not rely on an external event to execute, it will begin immediately.

To host the Windows Workflow runtime engine

Go back to your Windows application, and add a reference to the class library (ExpenseReportWorkflow.dll) you have just created in the previous task.

In the MainForm class, declare the fields as below:

C#
private WorkflowRuntime workflowRuntime = null; 
private WorkflowInstance workflowInstance = null; 

In the constructor method, after the call to the InitializeComponent method, add the following code. This would start the runtime but does not execute any workflows until the application notifies it to start a workflow.

C#
this.workflowRuntime = new WorkflowRuntime(); 
workflowRuntime.StartRuntime(); 

Create a generic event handler for the WorkflowCompleted event of the workflowRuntime object.

C#
workflowRuntime.WorkflowCompleted += 
      new EventHandler<WorkflowCompletedEventArgs> 
(workflowRuntime_WorkflowCompleted);

And, also add the corresponding event handler method.

C#
void workflowRuntime_WorkflowCompleted(object sender, 
WorkflowCompletedEventArgs e) 
{ 
}

In the submitButton_Click method, create a local Type object equal to the type of the workflow that you created in the previous task. Create the workflow object for that type and start it.

C#
Type type = typeof(ExpenseReportWorkflow); 
// Start the workflow 

workflowInstance = workflowRuntime.CreateWorkflow(type);
workflowInstance.Start(); 

Now, build the project again.

2. Create Expense Report Service

To enable communication between your Windows Forms application and your sequential workflow, define an interface marked with ExternalDataExchangeAttribute and implement that interface in your form class. To make communication between the form and the workflow easier, you will use ExternalDataExchangeService by adding the Windows form class as a service that your workflow can access.

Define the Workflow Parameters

The first task in this exercise is to create two properties in the workflow. These properties enable you to pass parameters from the Windows Forms application or to receive parameters from the workflow.

The host application can set that property before the workflow executes by passing in a parameters collection during the CreateWorkflow method call. To return parameters back to the host application after it finishes executing, you can define a get method for the property. The host application can then access the WorkflowCompletedEventArgs object that is passed to the WorkflowCompleted event. The WorkflowCompletedEventArgs object contains all the properties defined in the workflow that contain get methods.

In the workflow class in your ExpenseReportWorkflow class library project, include a property named "amount" with a set accessor.

C#
private int reportAmount = 0; 
public int Amount 
{ 
    set 
    { 
        this.reportAmount = value; 
    } 
}

Include another property named Result with a get accessor.

C#
private string reportResult = ""; 
public string Result 
{ 
    get 
    { 
        return this.reportResult; 
    } 
} 

Back to your Windows application, SimpleExpenseReport; in submitButton_Click, modify the code as below.

C#
Type type = typeof(ExpenseReportWorkflow.ExpenseReportWorkflow); 
// Construct workflow parameters 

Dictionary<string, object> properties = 
                  new Dictionary<string, object>(); 
properties.Add("Amount", Int32.Parse(this.amount.Text)); 
// Start the workflow 

workflowInstance = workflowRuntime.CreateWorkflow(type, properties); 
workflowInstance.Start(); 

The new code would create a workflow instance for the ExpenseReportWorkflow type and the amount property added in the Dictionary object.

In the workflowRuntime_WorkflowCompleted method in the MainForm class, display the result returned by the workflow and clear the amount text field.

C#
if (this.result.InvokeRequired) 
{ 
    this.result.Invoke(new EventHandler<WorkflowCompletedEventArgs> 
    (this.workflowRuntime_WorkflowCompleted), sender, e); 
} 
else 
{ 
    this.result.Text = e.OutputParameters["Result"].ToString(); 
    // Clear fields 

    this.amount.Text = string.Empty; 
}

Build the project once again to check for errors, if any.

Define the IExpenseReportService Interface

In this second task, define the service interface mentioned earlier and implement the methods that the workflow uses to call a method that is defined in the Windows Form class. Additionally, two events are defined for the host application to notify the workflow when certain events occur. This would enable the host application and workflow to communicate each other.

Add a new interface file in the ExpenseReportWorkflow project by selecting Project -> Add New Item -> Interface.

To decorate the interface with the ExternalDataExchange attribute, import the namespace as below.

C#
using System.Workflow.Activities; 
[ExternalDataExchange] 
public interface IExpenseReportService 
{ 
    void GetLeadApproval(string message); 
    void GetManagerApproval(string message); 
    event EventHandler<ExternalDataEventArgs> ExpenseReportApproved; 
    event EventHandler<ExternalDataEventArgs> ExpenseReportRejected; 
} 

Modify the MainForm class so that it implements the IExpenseReportService interface.

C#
public class MainForm : Form, IExpenseReportService 

Add the following delegate declaration and declare the events that occur during the running of the form. Remember that we have two buttons "accept" and "reject" in the form to raise the appropriate events.

C#
private delegate void GetApprovalDelegate(string message); 
private event EventHandler<ExternalDataEventArgs> reportApproved; 
private event EventHandler<ExternalDataEventArgs> reportRejected; 

Also, add the event handlers for both the events.

C#
public event EventHandler<ExternalDataEventArgs> ExpenseReportApproved 
{ 
    add 
    { 
        reportApproved += value; 
    } 
    remove 
    { 
        reportApproved -= value; 
    } 
} 

public event EventHandler<ExternalDataEventArgs> ExpenseReportRejected 
{ 
    add 
    { 
        reportRejected += value; 
    } 
    remove 
    { 
        reportRejected -= value; 
    } 
}

The next thing you do is very important. Since the form is an implementation of the service IExpenseReportService, it should be added to the ExternalDataExchangeService class.

To do so, create an instance for ExternalDataExchangeService in the MainForm class.

C#
private ExternalDataExchangeService exchangeService = null; 

Modify the code in the constructor method as below.

C#
this.workflowRuntime = new WorkflowRuntime(); 
this.exchangeService = new ExternalDataExchangeService(); 
workflowRuntime.AddService(exchangeService); 
exchangeService.AddService(this); 
workflowRuntime.StartRuntime(); 

Implement the two methods defined in the IExpenseReportService in the MainForm class.

C#
public void GetLeadApproval(string message) 
{ 
} 
public void GetManagerApproval(string message) 
{ 
} 

Build the project and check for errors.

Create Expense Report Sequential Workflow ClassLibrary

The workflow starts with "IfElseActivity" to determine whether a manager or a lead person must approve the value specified in the Amount property that was set when the workflow began executing.

After it determines this, the workflow calls a method defined in the host application to request user input for approval or rejection of the amount. When the Approve or Reject button is clicked, the host application raises the corresponding event, which is handled by one of the HandleExternalEventActivity activities that the workflow is waiting for. As soon as one of the events is raised and handled by the workflow, the workflow sets the Result property and completes its processing.

Create the CallExternalMethod Activities

Open the workflow designer in the ExpenseReportWorkflow project, and drag the "IfElseActivity" control from the toolbox.

Screenshot - pic2.jpg

Drag the CallExternalMethodActivity from the toolbox and drop it in the if branch of IfElseActivity. Drag another CallExternalMethodActivity from the toolbox and drop it in the else branch of IfElseActivity.

Name the Activities appropriately as given below.

Type

Name

IfElseActivity

ifEvaluateAmount

IfElseBranchActivity

elseLeadApprove

IfElseBranchActivity

elseManagerApprove

CallExternalMethodActivity

invokeLeadApproval

CallExternalMethodActivity

invokeManagerApproval

Select the elseLeadApprove rectangular box and press F4 to open the properties window. Set the "Condition" property to "Code Condition". Expand the "Condition" property.

Set the value of the Condition as DetermineApprovalContact.

Write a helper method in the same name as below.

C#
void DetermineApprovalContact(object sender, ConditionalEventArgs e) 
{ 
    if (this.reportAmount < 1000) 
    { 
        e.Result = true; 
    } 
    else 
    { 
        e.Result = false; 
    } 
} 

Now, set the properties for the CallExternalMethodActivity boxes as well.

Select the invokeLeadApproval box, and set the following properties:

Property

Value

Interface

SimpleExpenseReport.IExpenseReportService (to do this, click the ellipse button at the corner of the property, select the type in the dialog shown)

message

"Lead Approval needed"

MethodName

GetLeadApproval (click the drop-down list and select this method)

Select the invokeManagerApproval box, and set the following properties:

Property

Value

Interface

SimpleExpenseReport.IExpenseReportService (to do this, click the ellipse button at the corner of the property, select the type in the dialog shown)

message

"Manager Approval needed"

MethodName

GetManagerApproval (click the drop-down list and select this method)

Add the code to the two methods defined in the IExpenseReportService in the MainForm class as below:

C#
public void GetLeadApproval(string message) 
{ 
    if (this.approvalState.InvokeRequired) 
        this.approvalState.Invoke(new GetApprovalDelegate 
                (this.GetLeadApproval), message); 
    else 
    { 
        this.approvalState.Text = message; 
        this.approveButton.Enabled = true; 
        this.rejectButton.Enabled = true; 
        // expand the panel 
    
        this.Height = this.MinimumSize.Height + this.panel1.Height; 
        this.submitButton.Enabled = false; 
    } 
} 

public void GetManagerApproval(string message) 
{ 
    if (this.approvalState.InvokeRequired) 
        this.approvalState.Invoke(new GetApprovalDelegate 
            (this.GetManagerApproval), message); 
    else 
    { 
        this.approvalState.Text = message; 
        this.approveButton.Enabled = true; 
        this.rejectButton.Enabled = true; 
        // expand the panel 

        this.Height = this.MinimumSize.Height + this.panel1.Height; 
        this.submitButton.Enabled = false; 
    } 
}

Build the project and run the application. You should get the form displayed with the Approve and Reject buttons not working at this time.

Create the HandleExternalEvent Activities

In this task, you create a ListenActivity activity that contains two HandleExternalEventActivity activities to capture an approval or rejection event that is raised by the host application. When an approval or rejection event is raised, the workflow continues, sets the result, and then finishes.

Modify the ExpenseReportWorkflow class so that it can listen for specific events that the host application raises.

Create a new method that accepts an object named sender and an ExternalDataEventArgs named e as parameters.

C#
void approveEvent_Invoked(object sender, ExternalDataEventArgs e) 
{ 
    this.reportResult = "Report Approved"; 
} 
private void rejectEvent_Invoked(object sender,ExternalDataEventArgs e) 
{ 
    this.reportResult = "Report Rejected"; 
}

In the workflow designer, place the ListenActivity diagram by dragging it from the toolbox.

Screenshot - pic3.jpg

Drag the HandleExternalEvent Activity from the toolbox and drop it in both the EventDrivenActivitys.

Name the activities appropriately as below:

Type

Name

ListenActivity

listenApproveOrReject

EventDrivenActivity

eventDriven1

EventDrivenActivity

eventDriven2

HandleExternalEventActivity

ApproveEvent

HandleExternalEventActivity

RejectEvent

Select the ApproveEvent activity box, and press F4 to display the Properties window.

Property

Value

InterfaceType

SimpleExpenseReport.IExpenseReportService (to do this, click the ellipse button at the corner of the property, select the type in the dialog shown)

EventName

ExpenseReportApproved

Invoked

approveEvent_Invoked (click the drop-down list and select this method)

Select the RejectEvent Activity box, and press F4 to display the Properties window.

Property

Value

InterfaceType

SimpleExpenseReport.IExpenseReportService (to do this, click the ellipse button at the corner of the property, select the type in the dialog shown)

EventName

ExpenseReportRejected

Invoked

rejectEvent_Invoked (click the drop-down list and select this method)

The next step is to raise the event from the host application. To do so, add code to the approvebutton_Click handler.

C#
// Raise the ExpenseReportApproved event back
// to the workflow reportApproved(null, new ExternalDataEventArgs 

(this.workflowInstance.InstanceId)); 
this.Height = this.MinimumSize.Height; 
this.submitButton.Enabled = true; 

Similarly, add code to the rejectbutton_Click handler.

C#
// Raise the ExpenseReportRejected event back to the workflow 

reportRejected(null, new ExternalDataEventArgs (
                         this.workflowInstance.InstanceId)); 
this.Height = this.MinimumSize.Height; 
this.submitButton.Enabled = true; 

Build and run the application.

When you submit an expense report amount, the form expands to show the Approve and Reject buttons and a message that is received from the workflow. Clicking one of the buttons does the following:

  1. Collapses the form.
  2. Raises an event back to the workflow.
  3. Retrieves the Results property from the workflow.
  4. Displays it on the form.

Screenshot - pic4.jpg

This is the end of the walk-through for creating a sequential workflow.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)