Creating a Simple Procedural Workflow using VS 2010 and Windows Workflow WF 4.0
I hope most of you are by now aware that in .NET 4.0, there have been some major changes made in Windows Workflow 4.0. Most of the activities of WF 3.x are now changed by new activities. In this article, we will see how a workflow can be designed using procedural activities, how input arguments can be specified in a workflow and how a hosting application can pass these input arguments. We will be using Beta 1 release of VS2010, .NET 4.0.
This article explains following activities of WF 4.0:
- Sequence Activity.
- IfElse activity.
- InvokeMethod.
Note: There are different runtimes provided for different workflow versions. WF 3.x and WF 4.0 are two separate runtime. Because of this facility, older version of WF and new version of WF can execute side-by-side.
Workflow needs to be hosted in a managed hosting environment. The ‘System.Activities’ namespaces is used for it. ‘WorkflowInstance’ is the class used for creating an instance of the workflow. Following are some of the methods of ‘WorkflowInstance’ class:
· Run: To start execution of the instance.
· Cancel: To cancel the workflow.
· Persists: Save the workflow to a persistence store. For this persistence provider must be used.
· Terminate: Terminate the workflow, when a specific exception occurred.
The application demonstrated below is a simple banking application where account creation and banking transaction requests by the WinForm client application, are handled by Workflow.
Designing and Configuring Simple Procedural Workflow in WF 4.0
Step 1: Open Visual Studio 2010 and create a blank solution. Name this solution as ‘WF_BankingApplication’. In this blank solution add a new ‘Sequential Workflow Console Application’. Name this as ‘WF_BankingApplication’.
Step 2: After Step 1, you will get a default workflow sequence activity. Delete this activity. Now right click on the workflow project and add a new Sequence activity, name this as ‘WF_BankingApp’ as shown below. Change the display name of the sequence activity as ‘Banking_App’.
Step 3: In this step we will create a class. This class will expose methods for creating account and performing transactions. In the workflow project add a new class and name this class as ‘CBankingApp’. The code of the class is as below:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace WF_BankingApplication
{
public class CBankingApp
{
private string AccName;
private string AccAddress;
private static decimal OpBalance;
private decimal NetBalance;
public void CreateAccount(string accName, string accAddress, decimal opBalalce)
{
AccName = accName;
AccAddress = accAddress;
OpBalance = opBalalce;
}
public decimal Withdrawal(decimal trAmount)
{
NetBalance = OpBalance - trAmount;
return NetBalance;
}
public decimal Deposit(decimal trAmount)
{
NetBalance = OpBalance + trAmount;
return NetBalance;
}
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Namespace WF_BankingApplication
Public Class CBankingApp
Private AccName As String
Private AccAddress As String
Private Shared OpBalance As Decimal
Private NetBalance As Decimal
Public Sub CreateAccount(ByVal accName As String, ByVal accAddress As String, ByVal opBalalce As Decimal)
Me.AccName = accName
Me.AccAddress = accAddress
OpBalance = opBalalce
End Sub
Public Function Withdrawal(ByVal trAmount As Decimal) As Decimal
NetBalance = OpBalance - trAmount
Return NetBalance
End Function
Public Function Deposit(ByVal trAmount As Decimal) As Decimal
NetBalance = OpBalance + trAmount
Return NetBalance
End Function
End Class
End Namespace
Step 4: Now we will design the workflow. This workflow should have capabilities of accepting calling method from the ‘CBankingApp’ class. Since from the class, along with method ‘CreateAccount()’, either ‘Deposit()’ or ‘Withdrawal()’ will be called, the workflow should have capability making decision of only one method call. To make this possible we will design workflow with following properties:
· InvokeMethod: To call method using instance of the class. This activity has the capability of accepting input arguments and returning output argument from the method. This activity has following important properties:
o MethodName: The name of the method to be invoked.
o Parameters: Input parameters to the method.
o Result: Output parameter from the method.
o TargetObject: The object of the class from which this method is to be called.
o TargetType: Fully-qualified name of the class from which method is to be called. (Note: Either TargetObject or TargetType property needs to be set).
· If: This activity is used for decision making.
Step 5: In this step, we will define input and output arguments to and from the workflow. In the lower left corner, you will find ‘Arguments’, tab > clicking on it, you will get the palette where you can define arguments. Define arguments as below:
Name
|
Direction
|
Argument Type
|
AccountName
|
In
|
String
|
AccountAddress
|
In
|
String
|
OpeningBalance
|
In
|
Decimal
|
TransactionAmount
|
In
|
Decimal
|
NetBalance
|
Out
|
Decimal
|
TransactionType
|
In
|
String
|
Arguments define will look as like below:
Step 6: Click on the ‘Variables’, tab and create an object variable variable as below:
Name
|
Variable Type
|
Scope
|
objBankingApp
|
CBankingApp
|
Banking_App
|
The above object is used by ‘InvokeMethod’ activity for invoking method.
Step 7: Drag-drop the InvokeMethod activity on the sequence activity. Set properties of the activity as below:
Property Name
|
Value
|
Target Object
|
objBankingApp
|
Method Name
|
CreateAccount
|
The activity is now configured for invoking ‘CreateAccount’ method, but this method has input arguments, so select the activity and press ‘F4’. Now from the property pallet you can set arguments for the method. Clicking on the ellipse button of the ‘Parameters’ property, you will get parameters window. In this window you can set parameters as below:
Direction
|
Argument Type
|
Expression
|
In
|
String
|
AccountName
|
In
|
String
|
AccountAddress
|
In
|
Decimal
|
OpeningBalance
|
Note: Expression here is actually Arguments declared in Step 5. Also the order and type of arguments must mach.
Once you set the arguments it will look as shown below:
Step 8: In the sequence below drag-drop the ‘If’ activity. This is a decision making activity, Based upon the expression in the condition, it either executes the ‘Then’ part or the ‘Else’ part. Double-clicking on the ‘If’ activity, you will get the if condition, Then and Else designer. In the ‘If’ condition write the expression and in ‘Then’ and ‘Else’ part, drag-drop ‘InvokeMethod’ activities.
In this case, the ‘InvokeMethod’ in ‘Then’ will invoke ‘Deposit’ method from the class and ‘Else’ will invoke ‘Withdrawal’ method. The following table shows the design of the ‘If’ activity.
Activity
|
Property
|
Value
|
If
|
Condition
|
TransactionType=‘Deposit’
|
In the above case, ‘TransactionType’ is an argument defined at workflow level.
Note: One important thing to note here is that the value expression takes only assignment operator (=), where as when we are using C#, for the value equality in if condition, we use (==). You must be wondering how this works here then. One thing to note is that in WF 4.0, all expressions are based on Visual Basic syntax.
Activities in ‘Then’ and ‘Else’ parts are configured as below:
InvokeMethod in ‘Then’ part:
Property Name
|
Value
|
Target Object
|
objBankingApp
|
Method Name
|
Deposit
|
Result
|
NetBalance
|
The above activity is configured for invoking ‘Deposit’ method. Since this method required input parameters and returns output parameter, we need to configured parameters as below:
Direction
|
Argument Type
|
Expression
|
In
|
Decimal
|
TransactionAmount
|
TransactionAmount is an Argument declared at workflow level. The argument window will be as below:
InvokeMethod in ‘Else’ Part:
Property Name
|
Value
|
Target Object
|
objBankingApp
|
Method Name
|
Withdrawal
|
Result
|
NetBalance
|
The above activity is configured for invoking ‘Withdrawal’ method. Since this method required input parameters and returns output parameter, we need to configured parameters as below:
Direction
|
Argument Type
|
Expression
|
In
|
Decimal
|
TransactionAmount
|
TransactionAmount is an Argument declared at workflow level. The argument window will be as below:
After completing the above configuration, the ‘If’ activity will look like as below:
Step 9: Go back to the main workflow design by clicking on ‘Banking_App’ path.
The workflow will be displayed as below:
Hosting the Simple Procedural Workflow created using WF 4.0
After designing workflow successfully, we need to host workflow. Workflow must be hosted in managed hosting environment. With the release of .NET 4.0, a new workflow runtime for WF 4.0 has been released by Microsoft.
Step 1: In the same solution, add a new WinForm project by selecting language as ‘C#’ or ‘VB.NET’. Name this project as ‘WinForm_BankingClient’.
Step 2: Right click on the WinForm project and add a reference for the Workflow project which we created a short while ago.
Add a reference to ‘System.Activities’, which is used for using workflow hosting API’s.
Step 3: Design the WinForm as below:
Step 4: In the Form.cs, add following namespaces.
C#
using System.Activities;
using System.Threading;
VB.NET
Imports System.Activities
Imports System.Threading
Threading is required for executing workflow. Those who have already worked on WF 3.x, know that, workflow hosting environment picks up a thread from ThreadPool and executes workflow on it. During the process of execution, the Main thread must be in Wait condition. Once the WF execution is over, the thread is released.
Step 5: At the ‘Form1’ class level declare following variables:
C#
Dictionary<string, object> param;
WorkflowInstance wInstance;
AutoResetEvent waitHandle;
decimal NetBalance = 0;
VB.NET
Dim param As Dictionary(Of String, Object)
Dim wInstance As WorkflowInstance
Dim waitHandle As AutoResetEvent
Dim NetBalance As Decimal = 0
Here dictionary object is used to pass input arguments to the workflow from the host and receive output parameters from workflow back. Parameters communication is possible using Key-Value pair. The argument name declared at workflow level is treated as ‘Key’ and for this, the value must be specified by the host.
Step 6: Write the following code in Form Load and in the Button click event:
C#
private void Form1_Load(object sender, EventArgs e)
{
try
{
waitHandle = new AutoResetEvent(false);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnPerformTransaction_Click(object sender, EventArgs e)
{
try
{
param = new Dictionary<string, object>();
param.Add("AccountName", txtaccname.Text);
param.Add("AccountAddress", txtaccaddress.Text);
param.Add("OpeningBalance", Convert.ToDecimal(txtopeningbalance.Text));
if (rdDeposit.Checked)
{
param.Add("TransactionType", rdDeposit.Text);
}
if (rdWithdrawal.Checked)
{
param.Add("TransactionType", rdWithdrawal.Text);
}
param.Add("TransactionAmount", Convert.ToDecimal(txttranamount.Text));
wInstance = new WorkflowInstance(new WF_BankingApplication.WF_BankingApp(), param);
wInstance.OnCompleted = delegate(WorkflowCompletedEventArgs evt)
{
NetBalance = Convert.ToDecimal(evt.Outputs["NetBalance"]);
waitHandle.Set();
};
wInstance.Run();
waitHandle.WaitOne();
txtnetbalance.Text = NetBalance.ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
VB.NET
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs)
Try
waitHandle = New AutoResetEvent(False)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub btnPerformTransaction_Click(ByVal sender As Object, ByVal e As EventArgs)
Try
param = New Dictionary(Of String, Object)()
param.Add("AccountName", txtaccname.Text)
param.Add("AccountAddress", txtaccaddress.Text)
param.Add("OpeningBalance", Convert.ToDecimal(txtopeningbalance.Text))
If rdDeposit.Checked Then
param.Add("TransactionType", rdDeposit.Text)
End If
If rdWithdrawal.Checked Then
param.Add("TransactionType", rdWithdrawal.Text)
End If
param.Add("TransactionAmount", Convert.ToDecimal(txttranamount.Text))
wInstance = New WorkflowInstance(New WF_BankingApplication.WF_BankingApp(), param)
wInstance.OnCompleted = Function(evt) AnonymousMethod1(evt)
wInstance.Run()
waitHandle.WaitOne()
txtnetbalance.Text = NetBalance.ToString()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Function AnonymousMethod1(ByVal evt As WorkflowCompletedEventArgs) As Boolean
NetBalance = Convert.ToDecimal(evt.Outputs("NetBalance"))
waitHandle.Set()
Return True
End Function
In the above code in the button click method, we create dictionary object key value pairs and pass it to the constructor of the ‘WorkflowInstance’ class (shown in Yellow). The output ‘NetBalance’, is collected using anonymous method written for ‘OnCompleted’ (Shown in Green). After calling ‘Run’ method, the Main thread is put in a ‘Wait’ condition and when the workflow completed successfully, the ‘Set’ method is called.
Step 7: Run the application, and enter values for Account Name, Account Address, Opening Balance, Transaction Type (either Deposit or Withdrawal) and Transaction Amount, the following output will be displayed:
Summary:
In this article we have seen:
1. Simple procedural workflow creation using WF 4.0.
2. Writing of the hosting environment using Managed Host.
3. Passing and receiving Arguments to and from Workflow
The entire source code of this article can be downloaded over here
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions), and Front-end technologies like Angular and React. Follow him on twitter @
maheshdotnet or connect with him on
LinkedIn