Using Activity Delegates

Activity delegates enable activity authors to expose callbacks with specific signatures, for which users of the activity can provide activity-based handlers. Two types of activity delegates are available: ActivityAction<T> is used to define activity delegates that do not have a return value, and ActivityFunc<TResult> is used to define activity delegates that do have a return value.

Activity delegates are useful in scenarios where a child activity must be constrained to having a certain signature. For example, a While activity can contain any type of child activity with no constraints, but the body of a ForEach<T> activity is an ActivityAction<T>, and the child activity that is ultimately executed by ForEach<T> must have an InArgument<T> that is the same type of the members of the collection that the ForEach<T> enumerates.

Using ActivityAction

Several .NET Framework 4.6.1 activities use activity actions, such as the Catch activity and the ForEach<T> activity. In each case, the activity action represents a location where the workflow author specifies an activity to provide the desired behavior when composing a workflow using one of these activities. In the following example, a ForEach<T> activity is used to display text to the console window. The body of the ForEach<T> is specified by using an ActivityAction<T> that matches the type of the ForEach<T> which is string. The WriteLine activity specified in the Handler has its Text argument bound to the string values in the collection that the ForEach<T> activity iterates.

DelegateInArgument<string> actionArgument = new DelegateInArgument<string>();

Activity wf = new ForEach<string>
{
    Body = new ActivityAction<string>
    {
        Argument = actionArgument,
        Handler = new WriteLine
        {
            Text = new InArgument<string>(actionArgument)
        }
    }
};

List<string> items = new List<string>();
items.Add("Hello");
items.Add("World.");

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Values", items);

WorkflowInvoker.Invoke(wf, inputs);

The actionArgument is used to flow the individual items in the collection to the WriteLine. When the workflow is invoked, the following output is displayed to the console.

HelloWorld.

The examples in this topic use object initialization syntax. Object initialization syntax can be a useful way to create workflow definitions in code because it provides a hierarchical view of the activities in the workflow and shows the relationship between the activities. There is no requirement to use object initialization syntax when you programmatically create workflows. The following example is functionally equivalent to the previous example.

DelegateInArgument<string> actionArgument = new DelegateInArgument<string>();

WriteLine output = new WriteLine();
output.Text = new InArgument<string>(actionArgument);

ActivityAction<string> body = new ActivityAction<string>();
body.Argument = actionArgument;
body.Handler = output;

ForEach<string> wf = new ForEach<string>();
wf.Body = body;

List<string> items = new List<string>();
items.Add("Hello");
items.Add("World.");

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Values", items);

WorkflowInvoker.Invoke(wf, inputs);

For more information about object initializers, see How to: Initialize Objects without Calling a Constructor (C# Programming Guide) and How to: Declare an Object by Using an Object Initializer (Visual Basic).

In the following example, a TryCatch activity is used in a workflow. An ApplicationException is thrown by the workflow, and is handled by a Catch<TException> activity. The handler for the Catch<TException> activity's activity action is a WriteLine activity, and the exception detail is flowed through to it using the ex DelegateInArgument<T>.

DelegateInArgument<ApplicationException> ex = new DelegateInArgument<ApplicationException>()
{
    Name = "ex"
};

Activity wf = new TryCatch
{
    Try = new Throw()
    {
        Exception = new InArgument<Exception>((env) =>new ApplicationException("An ApplicationException was thrown."))
    },
    Catches =
    {
        new Catch<ApplicationException>
        {
            Action = new ActivityAction<ApplicationException>
            {
                Argument = ex,
                Handler = new WriteLine()
                {
                    Text = new InArgument<string>((env) => ex.Get(env).Message)
                }
            }
        }
    },
    Finally = new WriteLine()
    {
        Text = "Executing in Finally."
    }
};

When creating a custom activity that defines an ActivityAction<T>, use an InvokeAction<T> to model the invocation of that ActivityAction<T>. In this example, a custom WriteLineWithNotification activity is defined. This activity is composed of a Sequence that contains a WriteLine activity followed by an InvokeAction<T> that invokes an ActivityAction<T> that takes one string argument.

public class WriteLineWithNotification : Activity
{
    public InArgument<string> Text { get; set; }
    public ActivityAction<string> TextProcessedAction { get; set; }

    public WriteLineWithNotification()
    {
        this.Implementation = () => new Sequence
        {
            Activities =
            {
                new WriteLine
                {
                    Text = new InArgument<string>((env) => Text.Get(env))
                },
                new InvokeAction<string>
                {
                    Action = TextProcessedAction,
                    Argument = new InArgument<string>((env) => Text.Get(env))
                }
            }
        };
    }
}

When a workflow is created by using the WriteLineWithNotification activity, the workflow author specifies the desired custom logic in the activity action’s Handler. In this example, a workflow is created that use the WriteLineWithNotification activity, and a WriteLine activity is used as the Handler.

// Create and invoke the workflow without specifying any activity action
// for TextProcessed.
Activity wf = new WriteLineWithNotification
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

// Output:
// Hello World.

// Create and Invoke the workflow with specifying an activity action
// for TextProcessed.
DelegateInArgument<string> msg = new DelegateInArgument<string>();
Activity wf2 = new WriteLineWithNotification
{
    Text = "Hello World with activity action.",
    TextProcessedAction = new ActivityAction<string>
    {
        Argument = msg,
        Handler = new WriteLine
        {
            Text = new InArgument<string>((env) => "Handler of: " + msg.Get(env))
        }
    }
};

// Invoke the workflow with an activity action specified
WorkflowInvoker.Invoke(wf2);

// Output:
// Hello World with activity action.
// Handler of: Hello World with activity action.

There are multiple generic versions of InvokeAction<T> and ActivityAction<T> provided for passing one or more arguments.

Using ActivityFunc

ActivityAction<T> is useful when there is no result value from the activity, and ActivityFunc<TResult> is used when a result value is returned. When creating a custom activity that defines an ActivityFunc<TResult>, use an InvokeFunc<TResult> to model the invocation of that ActivityFunc<TResult>. In the following example, a WriteFillerText activity is defined. To supply the filler text, an InvokeFunc<TResult> is specified that takes an integer argument and has a string result. Once the filler text is retrieved, it is displayed to the console using a WriteLine activity.

public class WriteFillerText : Activity
{
    public ActivityFunc<int, string> GetText { get; set; }
    public InArgument<int> Quantity { get; set; }

    Variable<string> text = new Variable<string>
    {
        Name = "Text"
    };

    public WriteFillerText()
    {
        this.Implementation = () => new Sequence
        {
            Variables =
            {
                text
            },
            Activities =
            {
                new InvokeFunc<int, string>
                {
                    Func = GetText,
                    Argument = new InArgument<int>((env) => Quantity.Get(env)),
                    Result = new OutArgument<string>(text)
                },
                new WriteLine
                {
                    Text = new InArgument<string>(text)
                }
            }
        };
    }
}

To supply the text, an activity must be used that takes one int argument and has a string result. This example shows a TextGenerator activity that meets these requirements.

public class TextGenerator : CodeActivity<string>
{
    public InArgument<int> Quantity { get; set; }
    public InArgument<string> Text { get; set; }

    protected override string Execute(CodeActivityContext context)
    {
        // Provide a quantity of Random Text
        int q = Quantity.Get(context);
        if (q < 1)
        {
            q = 1;
        }

        string text = Text.Get(context) + " ";
        StringBuilder sb = new StringBuilder(text.Length * q);
        for (int i = 0; i < q; i++)
        {
            sb.Append(text);
        }

        return sb.ToString();
    }
}

To use the TextGenerator activity with the WriteFillerText activity, specify it as the Handler.

DelegateInArgument<int> actionArgument = new DelegateInArgument<int>();

Activity wf = new WriteFillerText
{
    Quantity = 5,
    GetText = new ActivityFunc<int, string>
    {
        Argument = actionArgument,
        Handler = new TextGenerator
        {
            Quantity = new InArgument<int>(actionArgument),
            Text = "Hello World."
        }
    }
};

WorkflowInvoker.Invoke(wf);