Creating a Simple Procedural Workflow using VS 2010 and Windows Workflow WF 4.0

Posted by: Mahesh Sabnis , on 8/5/2009, in Category .NET Framework
Views: 70503
Abstract: I hope most of you are by now aware that in .NET 4.0, there have been some major changes 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.
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’.
AddNewItem
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:
Arguments
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:
Parameters
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:
Parameters_1
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:
Parameters_2
After completing the above configuration, the ‘If’ activity will look like as below:
If_Activity
Step 9: Go back to the main workflow design by clicking on ‘Banking_App’ path.
BankingApplication
The workflow will be displayed as below:
Workflow
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_Reference
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:
Form1
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.

Absolutely Awesome Book on C# and .NET

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!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
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


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Fabio on Friday, May 6, 2011 10:58 AM
Good afternoon ...
I did what you're saying in this article, only it is giving error when I call the InvokeMethod:

System.ArgumentNullException: Value can not be null.
Parameter Name: TargetObject

What can be?
Comment posted by sena reddy on Monday, September 17, 2012 2:12 AM
i am unable to set variable type as CbankingApp. Could you please give me suggesion to me how to set user defined datatype to variable type

Thank you
Comment posted by sunil on Friday, March 27, 2015 7:10 AM
With this we can now queue a new build and this time the entire build-deploy-test workflow will succeed since the deploy and test activities in the workflow are now using a local directory for the build result: