Home > Windows Workflow > Creating Custom Activities in Windows Workflow

Creating Custom Activities in Windows Workflow

There are two main types of custom activity: a custom activity that encapsulates a discrete task and a custom composite activity that groups a collection of lower-level activities into a higher-level activity.

A custom activity inherits directly or indirectly from the Activity class; a custom composite activity inherits directly or indirectly from the CompositeActivity class. You can annotate an Activity class with attributes that describe various design-time and run-time characteristics of the activity.

Activity Type

Details

Custom You use custom activities to implement distinct tasks in a single reusable activity such as an activity to send an e-mail message to a user. You can define the To and From address, in addition to the mail server to use as properties of the activity, and then implement logic to send the e-mail message in the Execute method for the activity.
Custom composite You use custom composite activities to encapsulate reusable sequences of activities. This enables you to define a sequence of activities once and then distribute the custom composite activity throughout your workflow implementation. This reduces development time in your application.

You define activity classes by creating a Workflow Activity Library project in Visual Studio.

Defining Properties in a Custom Activity

When you design custom activities, there are three types of property at your disposal: instance properties, which apply to each instance of the activity and can be changed at run time; meta properties, which apply to all instances of the activity and are usually used to describe something in the activity itself; and dependency properties, which enable other activities in the workflow to access and work with properties in your activity

Property Types

Instance
Instance properties are modifiable at run time. You can assign a literal value at design time or you can bind the property to instance data in the workflow that contains the activity. You can also modify the value of the property directly during workflow execution.

You can implement an instance property in the same way as you implement a normal .NET Framework property or you can implement it as a dependency property.

There are several property attributes that you use to define the property behaviors.

Meta
Meta properties are immutable at run time. You must assign a literal value at design time. Meta properties relate to activities in the same way that attributes relate to classes in the .NET Framework.

For example, System.Workflow.ComponentModel.Activity.Name is a string that represents the activity name, is metadata, and is set at design time and embedded in the compiled workflow so that it cannot be changed.

Dependency
When you define an activity class, you typically define most of its properties as dependency properties rather than instance properties or meta properties. Dependency properties provide a centralized repository of the state of a workflow, which is accessible from any activities in the workflow. The Activity class inherits from DependencyObject, which means that all activities inherit the capability to get and set dependency properties in a workflow. You can bind dependency properties to available values in the workflow, such as workflow fields, properties, and methods, and to other activity property values

WF provides several attributes that you can use to annotate a property in an activity. For example, you can use the Browsable attribute to specify that the property should appear in the Visual Studio Properties window.

The following code examples show how to define an instance property that is named ToAddress for the SendEmailActivity class. The ToAddress property specifies the e-mail address for the recipient of the e-mail message. The property is annotated with attributes that provide Visual Studio with information that describes how to display the property in the Properties window.

[Visual C#]
private string _toAddress;

[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Browsable(true)]
[Description("Please specify the e-mail address of the recipient.")]
[Category("E-mail message")]
public string ToAddress
{
get { return toAddress; }
set { toAddress = value; }
}

The following code examples show how to define a dependency property that is named FromAddress for the SendEmailActivity class. The FromAddress property specifies the e-mail address for the sender of the e-mail message. The property is a string and has a default value of admin@contoso.com.

public static DependencyProperty FromAddressProperty =   DependencyProperty.Register(

“FromAddress”,
typeof(string),
typeof(SendEmailActivity),
new PropertyMetadata(“admin@contoso.com“));

[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Browsable(true)]
[Description("Please specify the e-mail address of the sender.")]
[Category(MessagePropertiesCategory)]
public string FromAddress
{
get
{
return ((string)(base.GetValue(FromAddressProperty)));
}
set
{
base.SetValue(FromAddressProperty, value);
}
}

There are 3 main motivations to use Custom Activities:

  • Reusability: when the need comes to make a change, you can do it on only one place and see how it reflects in the rest of the application. We can reuse the logic and code in any WF.
  • Extensibility: if the activities in the library are not useful to fulfill a specific need, we can build new primitive activities, which will be treated as the rest ones in the activity library.
  • DSL: you may use Custom Activities to create specific domain languages, with which you can build WF. E. g.: the activity “send request to provider” can capture a call to a web service or other internal logic process. This activity will belong to the “requests” specific domain, and this way any developer can use it being this more effective than the utilization of the primitive activities in the activity library.

There are 2 possibilities to create Custom Activities, one is through composition (Activity Composition) and the other is through inheritance (Activity Derivation).

To create Custom Activities through Composition is very similar to creating a WF. You use the designer to drag, drop and configure activities within a new Custom Activity, which works as container. The method of the Composition is the easiest and fastest way of creating Custom Activites.

In the Activity Derivation Method, a new activity coming from the activity class is created. It can also be derivate from other classes which may use Activity, in order to inherit more functionality. This method gives us a higher level of control as well as a way to extend WF with our own code.

Activity Composition

To create a custom activity through Composition we must create a new project of the kind of Workflow Activity Library, with the name “WFActivityComposition”. This project will include a predefined activity (Activity1.cs), which is of the same type than the SequenceActivity one, which will allow us to take activities from the tool box and deposit them inside the activity into a sequence.

For our example we will first rename the class Activity1.cs us ActivityCompositonExample.cs and then we put inside it a WhileActivity, and two CodeActivity, one inside the WhileActivity and the other beneath it, as shown in the image below:

Once built, we’ll add the code to the CodeActivities and the condition to the WhileAcivity. This code for the example is to be able to show a “Welcome to WF!!” message within the iteration.

Public partial class ActivityCompositionExample: SequenceActivity
{
    int _counter;

    public int Counter
    {
        get { return _counter; }
        set { _counter = value; }
    }

    public Activity1()
    {
        InitializeComponent();
    }

    private void codeActivity1_ExecuteCode(object sender, EventArgs e)
    {
        Console.WriteLine("Welcome to WF!!");
  Counter++;
    }

    private void d codeActivity2_ExecuteCode(object sender, EventArgs e)
    {	
        Console.ReadKey();
    }

}

It only lasts to add the condition of execution of the WhileActivity1, which we’ll do so within the Conditions property, which is located in the Property List of the activity. Once there, we’ll select the type of condition, that in this case will be a Declarative Rule Condition one, and then we’ll create a new one with the name WhileActivityRule, and whose condition will be this.Counter < 10 (you may use intellisense inside the dialog to enter the condition). With this we will achieve the WhileActivity1 to execute while the value of our counter remains below 10. Finally, our activity is already completed, it only needs to be compiled to produce the Assembly and be able to distribute it to use in our workflows.

Activity Derivation

Activity derivation is the other method to create Custom Activities. Unlike the composition, where with several basic activities we can create a new large and complex activity, on this method we focus on creating one single activity, defining their properties and execution model. Let’s look at an example of an activity created by this method, which will be aiming to display a message on the console.

In order to do that we´ll create a new project of type Workflow Activity Library. We will name it “WFActivityDerivation” and add a new class called ActivityDerivationExample.cs, which the following code:

public class ActivityDerivationExample : Activity
{
    string _text;

    public string Text
    {
        get { return _text; }
        set { _text = value; }
    }

    protected override ActivityExecutionStatus Execute(
    ActivityExecutionContext executionContext)
    {
        Console.WriteLine(Text);
  Console.ReadKey();
        return ActivityExecutionStatus.Closed;
    }
}

The class inherits from

System.Workflow.ComponentModel.Activity, which is the base of all activities in WF. Our example activity exposes a property called “Text” whose value can be set from the workflow designer and which will be displayed on the console when the activity runs.

The main feature of this class is the Execute method. When we override this method we are assuming full responsibility for the behavior of the activity. When the time comes to execute our custom activity, WF runtime will invoke our Execute method and then show our message on the console.

After that we need to tell the WF runtime that our activity execution has ended, returning the value Closed of the ActivityExecutionStatus.

At this point we can build our activity and create the assembly for distribution and build an example to test our both activities.

In order to do that, we need to create a new project of Sequential Workflow Console Application type. To view our custom activities on the Tool Box, we have two possibilities. One is to have both custom activities proyect and the workflow project on the same solution and the other is adding them on the Tool Box with the “Choose Items” option that appears on the Tool Box´s context menu.

Now we´ll drop the ActivityCompositonExample activity on the Sequential Workflow, renaming it like CompositeActivity, and setting the Counter property, then we´ll do the same with the ActivityDerivationExample putting it below the previous activity and renaming it like WriteActivity and setting this Text property.

Our Workflow has to look like the following picture:

We could notice that the activities within CompositeActivity can’t be edited from the Workflow. This is because it is a closed activity, and any needed change could only be done on the project that created the activity.

Once the Workflow is done we are ready to compile it and test our activities.

Fine tuning our activities

WF provides us with methods to control the way we present and validate our activities at design time. These mechanisms are: Property Attributtes, Activity Designers and Activity Validators. Let’s review them one by one.

Property Attributes

If we look at the Text property of WriteActivity (or any other property it has) we can notice that it belongs to a group named Misc. This can be better organized by using the attributes WF provides. For example, if we add some attributes to the Text property of the ActivityDerivationExample activity we can add a predefined value, a description and a category to belong to.

//ActivityDerivationExample.cs
[Category("Text fot ActivityDerivationExample")]
[DefaultValue("Test WF")]
[Description("Text to show")]
public string Text
{
    get { return _text; }
    set { _text = value;
}

Activity Designers

The Activity Designers are used to control the appearance of the activities at design time. To create a designer we need a new class that inherits from ActivityDesigner and override their virtual methods. The following example shows how to override the method OnPaint, who draws our activity in the designer’s workflow:

public class ActivityDerivationDesigner : ActivityDesigner
{
    ActivityDerivationExample _activity;
    protected override void Initialize(Activity activity)
    {
        _activity = activity as ActivityDerivationExample;
        base.Initialize(activity);
    }

    protected override void OnPaint(ActivityDesignerPaintEventArgs e)
    {
        e.Graphics.FillRectangle(Brushes.Black,Location.X, Location.Y,      Size.Width, Size.Height);
        Rectangle rect = new Rectangle(Location.X, Location.Y,Size.Width, 15);
        Font font = new Font("Lucida Console", 8);
        e.Graphics.DrawString(@"C:\> " + _activity.Text, font, Brushes.White, rect.X, rect.Y + 10);

    }
}

The following syntax is used to associate the designer to our activity:

//ActivityDerivationExample.cs
[Designer(typeof(ActivityDerivationDesigner))]
public class ActivityDerivationExample : Activity
{
    //
}

Activity Validators

The Activity Validators run at design and build time, and are used to ensure that the activity has the correct settings to run. To create a validator we need a new class that inherits from the class ActivityValidator and overwrites the Validate method.

For example the following validator will ensure that our activity has a valid value in the Text property:

class ActivityDerivationValidator : ActivityValidator
{
    public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
    {
        ValidationErrorCollection errors = base.Validate(manager, obj);
        ActivityDerivationExample activity = obj as                           ActivityDerivationExample;
        if (activity.Parent != null && String.IsNullOrEmpty(activity.Text))
        {
            errors.Add(ValidationError.GetNotSetValidationError("Text"));
        }
            return errors;
    }
}

All validation errors are added to the ValidationErrorCollection collection and returned to the caller.

The following syntax is used to associate the validator to our activity:

//ActivityDerivationExampe.cs
[ActivityValidator(typeof(ActivityDerivationValidator))]
public class ActivityDerivationExample : Activity
{
    //
}

Communication between activities

It is highly probable that -at design time- we don´t know the property values of the activities we create. For example, let’s say we have a purchase order workflow, whose first activity asks for a vendor number and the next activity uses this number to send back a list of the order items. Definitively there is no way to set the activity’s vendor number at design time, as this would be against the workflow functionality. What we need is something to propagate information from an activity property to the rest of the activities needing it. WF provides us with this mechanism, which is called Activity Binding.

I will explain it with an example. The first we will do is to create a Dependency Property called MessageProperty in our ActivityCompositionExample activity, which we will use to receive a display message from the workflow runtime.

Dependency properties provide a centralized repository for the workflow states. DependencyObject is basically a hash table storing values from any Dependency Property assigned to it, and allows us to bind that with other properties in different activities.

The code to define a Dependency Property is as follows:

//ActivityCompositionExample.cs
public static DependencyProperty MessageProperty =
        System.Workflow.ComponentModel.DependencyProperty.Register("Message", typeof(string),
        typeof(ActivityCompositonExample));
        [Description("Message")]
        [Category("Dependency Properties")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string Message
        {
            get
            {
                return ((string)(base.GetValue(ActivityCompositonExample.MessageProperty)));
            }
            set
            {
                base.SetValue(ActivityCompositonExample.MessageProperty, value);
            }
        }

Once our Dependency Property is created, we need to bind it to the property which will hold the value we want to display as a message. To do this we will create a propery in the Workflow1 class which we will bind to our Dependency Property. The code to do that is the following:

//Workflow1.cs
string _messageToBind;

public String MessageToBind
{
    get
    {
        return _messageToBind;
    }
    set
    {
        _messageToBind = value;
    }
 }

Once our property is created, we need to bind it to our activity’s Dependency Property. To do that we access the properties of the CompositeActivity activity on the workflow designer and we look for the property which should look as shown on the image:

Double-clicking it will display the following window, where we will decide if we bind the property to an existing or a new member. In our case we will use an existing member, the MessageToBind property, as show on the image:

This will bind the Message Dependency Property of the CompositeActivity activity to the MessageToBind property of the Workflow class (Workflow1.cs). To see this let’s get back to the Message property of the CompositeActivity and we will see its value is now the following:

Activity=Workflow1, Path=MessageToBind

Had we chosen the “Bind To A New Member” option, a new Dependency Property would have been generated on the Workflow.cs class code, bound to the activity’s Dependency Property.

Now it only rests to add the logic for the workflow to load the property. This is done from the code which instantiates the workflow runtime by generating a dictionary containing in each entry the property name we want to assign and the desired value, and then passing this as a parameter to the runtime CreateWorkflow method, as seen in the example:

//Program.cs
Dictionary parameters = new Dictionary();
                parameters.Add("MessageToBind", "Hello WF!! (Dependecy Properties Example)");
                WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication.Workflow1),parameters);

Thus we did assign a value to the CompositeActivity_Message1 property of the Workflow1 class, which is bound to our Message property on the CompositeActivity, which allows to use this property value within our activity.

If what we need is communication among different activities, the only thing we need to change in the example is bind the Dependency Property to some member of another activity and not to the workflow designer. For example, we can add a new Dependency Property to the ActivityDerivationExample activity and bind it to the Counter property of ActivityCompositonExample, and print how many times the content of the previous activity was executed. Let’s see the code for ActivityDerivationExample’s Dependency Property:

//ActivityDerivationExample.cs
public static DependencyProperty CounterProperty =
       System.Workflow.ComponentModel.DependencyProperty.Register("Counter", typeof(int),
       typeof(ActivityDerivationExample));
        [Description("Counter")]
        [Category("Dependency Properties")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public int Counter
        {
            get
            {
                return ((int)(base.GetValue(ActivityDerivationExample.CounterProperty)));
            }
            set
            {
                base.SetValue(ActivityDerivationExample.CounterProperty, value);
            }
        }

Once we have the Dependency Property created we will bind it from the WriteActivity activity with the CompositeActivity’s Counter property. With that, each time WriteActivity executes it has access to the counter used in the CompositeActivity activity

Hope this would be helpful !

About these ads
Categories: Windows Workflow
  1. April 25, 2013 at 10:58 pm

    I really like your blog.. very nice colors & theme.
    Did you design this website yourself or did
    you hire someone to do it for you? Plz reply as I’m looking to design my own blog and would like to know where u got this from. appreciate it

  2. download whatsapp on
    July 9, 2013 at 3:13 am

    I needed to thank you for this great read!!
    I certainly enjoyed every bit of it. I’ve got you bookmarked to look at new things you post…

  3. July 23, 2013 at 4:09 pm

    I do not even understand how I stopped up here, but I
    assumed this submit was once great. I don’t realize who you might be however definitely you are going to a famous blogger when you aren’t already.

    Cheers!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 25 other followers

%d bloggers like this: