Creating and Using Custom Activity in WF (Windows Workflow) 4.0
In this article, we will go through a mechanism of developing and using Custom Activity using WF 4.0. Those who have worked with WF 3.x, might have already designed several custom activities. .NET 3.x has provided mechanisms of designing and developing custom activities for implementing non-standard operations using Workflow. On the same basis, WF 4.0 has also provided us mechanisms of custom activities.
I have observed that creating custom activities in WF 4.0 is comparatively easier than provided in .NET 3.x. In this article, I will share my experiences with you.
Consider a scenario that you are working on a design assignment for a workflow and need to get all rows from a specific database server and from a specific database/table for reporting purpose. Here if we try to use predefined activities from the toolbox, you may find it a little difficult to get this job done.
Step 1: Open VS 2010 and create a blank solution. Name this solution as ‘WF_CustomDaatActivity’.
Step 2: Add a new Activity Library project from Workflow Template in the solution, name this as ‘WF-CustomDataActivity’ as below:
When you add this project, you will get a XAML file > delete this file since we are going to create activity using code.
Step 3: In this project add a new class and name this class as ‘GetAllDataActivity’. This class define Input and Output arguments to and from the activity. The code of the class is as below:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Data;
using System.Data.SqlClient;
namespace WF_CustomDataActivity
{
public class GetAllDataActivity:CodeActivity<string>
{
InArgument<string> _DbServerName;
public InArgument<string> DbServerName
{
get { return _DbServerName; }
set { _DbServerName = value; }
}
InArgument<string> _DbName;
public InArgument<string> DbName
{
get { return _DbName; }
set { _DbName = value; }
}
InArgument<string> _TbName;
public InArgument<string> TbName
{
get { return _TbName; }
set { _TbName = value; }
}
OutArgument<DataSet> _Ds;
public OutArgument<DataSet> Ds
{
get { return _Ds; }
set { _Ds = value; }
}
protected override void Execute(CodeActivityContext context)
{
string dbServerName = DbServerName.Get(context);
string dbName = DbName.Get(context);
string tbName = TbName.Get(context);
DataSet oDs = Ds.Get(context);
SqlConnection Conn = new SqlConnection("Data Source=" + dbServerName + ";Initial Catalog=" + dbName +";Integrated Security=SSPI");
SqlDataAdapter AdTanble = new SqlDataAdapter("Select * from " + tbName, Conn);
AdTanble.Fill(oDs, tbName);
}
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Activities
Imports System.Data
Imports System.Data.SqlClient
Namespace WF_CustomDataActivity
Public Class GetAllDataActivity
Inherits CodeActivity(Of String)
Private _DbServerName As InArgument(Of String)
Public Property DbServerName() As InArgument(Of String)
Get
Return _DbServerName
End Get
Set(ByVal value As InArgument(Of String))
_DbServerName = value
End Set
End Property
Private _DbName As InArgument(Of String)
Public Property DbName() As InArgument(Of String)
Get
Return _DbName
End Get
Set(ByVal value As InArgument(Of String))
_DbName = value
End Set
End Property
Private _TbName As InArgument(Of String)
Public Property TbName() As InArgument(Of String)
Get
Return _TbName
End Get
Set(ByVal value As InArgument(Of String))
_TbName = value
End Set
End Property
Private _Ds As OutArgument(Of DataSet)
Public Property Ds() As OutArgument(Of DataSet)
Get
Return _Ds
End Get
Set(ByVal value As OutArgument(Of DataSet))
_Ds = value
End Set
End Property
Protected Overrides Sub Execute(ByVal context As CodeActivityContext)
Dim dbServerName As String = Me.DbServerName.Get(context)
Dim dbName As String = Me.DbName.Get(context)
Dim tbName As String = Me.TbName.Get(context)
Dim oDs As DataSet = Ds.Get(context)
Dim Conn As New SqlConnection("Data Source=" & dbServerName & ";Initial Catalog=" & dbName & ";Integrated Security=SSPI")
Dim AdTanble As New SqlDataAdapter("Select * from " & tbName, Conn)
AdTanble.Fill(oDs, tbName)
End Sub
End Class
End Namespace
‘GetAllDataActivity’, class is derived from generic ‘CodeActivity<string>’. This abstract base class defines the behavior of the newly defined activity. This is the imperative behavior defined within ‘Execute’. This uses ‘System.Activities.CodeActivity.Execute’.
There are three ‘InArgument’ defined by the class. ‘InArgument’ defines the flow of data into an activity. Similarly ‘OutArgument’ defines flow of data out from the activity.
The class also overrides the ‘Execute’ method. This contains code operation supposed to be performed by the activity.
Step 4: In the solution, add a new Sequential Workflow Console application and name it as ‘WF_CustomActivityContainer’. Add the reference to the ‘WF_CustomDataActivity’ in this project.
Since the custom activity and the solution is in one solution, the custom activity will be displayed in the toolbox a below:
Step 5: Since the custom activity requires the input and output arguments, declare the following arguments in Workflow, using Arguments button on the bottom left of the workflow designer.
Step 6: Now drag the custom activity from the toolbox and drop it in the sequence activity on the workflow designer. The workflow will displayed as below:
Note: Ignore the red bubbles as they denote Xml serialization errors. Do not worry, your workflow will be compiled and executed successfully.
Step 7: Set the properties of the custom activity by passing arguments declared in step 5 to the input arguments declared in the custom activity as below:
Step 8: Open Program.cs in the workflow project. This already contains the code for hosting and running the workflow. Modify the code as below:
C#
DataSet Ds = new DataSet();
#region Pass The Parameter
Dictionary<string, object> Par = new Dictionary<string, object>();
Par.Add("dbServerName",".");
Par.Add("dbName","Company");
Par.Add("tbName","Employee");
Par.Add("oDs",Ds);
#endregion
WorkflowInstance myInstance = new WorkflowInstance(new WF_DataActivity(),Par);
myInstance.OnCompleted = delegate(WorkflowCompletedEventArgs e)
{
Ds = (DataSet) e.Outputs["oDs"];
Console.WriteLine(Ds.GetXml());
syncEvent.Set();
};
VB.NET
Dim Ds As New DataSet()
'#Region "Pass The Parameter"
Dim Par As New Dictionary(Of String, Object)()
Par.Add("dbServerName",".")
Par.Add("dbName","Company")
Par.Add("tbName","Employee")
Par.Add("oDs",Ds)
' #End Region
Dim myInstance As New WorkflowInstance(New WF_DataActivity(),Par)
myInstance.OnCompleted = Function(e) AnonymousMethod1(e, Ds)
Private Function AnonymousMethod1(ByVal e As WorkflowCompletedEventArgs, ByVal Ds As DataSet) As Boolean
Ds = CType(e.Outputs("oDs"), DataSet)
Console.WriteLine(Ds.GetXml())
syncEvent.Set()
Return True
End Function
The DataSet is declared as the return or output argument from the workflow. The Generic dictionary is declared for passing parameters to the workflow. On the successful execution of the workflow, the following code will be executed which will print the data from the dataset in XML form.
C#
myInstance.OnCompleted = delegate(WorkflowCompletedEventArgs e)
{
Ds = (DataSet) e.Outputs["oDs"];
Console.WriteLine(Ds.GetXml());
syncEvent.Set();
};
VB.NET
myInstance.OnCompleted = Function(e) AnonymousMethod1(e)
Private Function AnonymousMethod1(ByVal e As WorkflowCompletedEventArgs) As Boolean
Ds = CType(e.Outputs("oDs"), DataSet)
Console.WriteLine(Ds.GetXml())
syncEvent.Set()
Return True
End Function
Step 9: Run the application, the following output will be displayed.
Conclusion: WF 4.0, has provided an easy mechanism of developing custom activities. Based upon the requirements of your application, you can choose and develop custom activities. Following are some of the examples where you can use custom activities:
· Database Backup Activity.
· FileTransfer Activity and so on.
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