Creating and Using Custom Activity in WF 4.0

Posted by: Mahesh Sabnis , on 9/2/2009, in Category .NET Framework
Views: 32724
Abstract: 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.
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:
AddNewProject
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:
Toolbox
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.
               Arguments
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:
Workflow
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:
Properties
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.
Output
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.

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 Gowtham on Wednesday, August 22, 2012 12:27 PM

i need to create custom workflow in  sharepoint 2010 using visual syudio.
Actually my requirement Creating the WHILE loop work flow programmatically.please let me know the sample code