A question on StackOverflow about scheduling child activities with some additional input parameters made me remember that this is kind on not obvious to figure out and even though I new the answer I had to think hard before I could code up a demo. So I figured I might as well post it on my blog for future reference.
The problem is that activities are normally just scheduled and their inputs and outputs are configured using Visual Basic expressions. Most of the time that is just fine and does exactly what you need. However in some cases, as in the question mentioned, one activity does some work with the data and then wants to schedule another child activity with this data. The outer activity in the question was simple, it had two input arguments, added those to a dictionary and scheduled the child activity to do some work with this dictionary. Sounds simple right?
The problem however is that input arguments want to work with data the workflow way and won’t let you pass the dictionary straight to the child activity.
One way of solving this is adding an extra Variable and storing the dictionary in there before scheduling the child activity. This works but WF4 actually has a mechanism build in to handle this case and that is an ActivityAction, or ActivityFunc of you also want to return a result. In the standard activity library there are a few cases where ActivityAction is used. For example in the ForEach activity where the ForEach itself is responsible for iterating over the enumeration of items and scheduling the body once for each item. Now that could have been done with a variable but in the case of the ParallelForEach, where all child activities are scheduled at the same time the variable approach would not have worked.
The child activity to execute looks like this:
public class Child : CodeActivity<object>
{
public InArgument<Dictionary<string, object>> Data { get; set; }
protected override object Execute(CodeActivityContext context)
{
Dictionary<string, object> data = Data.Get(context);
foreach (var item in data)
{
Console.WriteLine(item);
}
return "Some result";
}
}
The parent activity that is responsible for transforming the inputs and scheduling the child activity looks like this:
public class Parent : NativeActivity<object>
{
public InArgument<int> Value1 { get; set; }
public InArgument<string> Value2 { get; set; }
private ActivityFunc<Dictionary<string, object>, object> Body { get; set; }
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
var arg = new DelegateInArgument<Dictionary<string, object>>();
Body = new ActivityFunc<Dictionary<string, object>, object>
{
Argument = arg,
Handler = new Child()
{
Data = arg
}
};
metadata.AddImplementationDelegate(Body);
base.CacheMetadata(metadata);
}
protected override void Execute(NativeActivityContext context)
{
var data = new Dictionary<string, object>();
data.Add("value1", Value1.Get(context));
data.Add("value2", Value2.Get(context));
context.ScheduleFunc<Dictionary<string, object>, object>(Body, data, ChildCompletionCallback);
}
private void ChildCompletionCallback(NativeActivityContext context, ActivityInstance completedInstance, object result)
{
this.Result.Set(context, result);
}
}
The DelegateInArgument defined in the CacheMetadata() actually ties the input for the ActivityFunc, the Argument, to the InArgument of the child activity.
Please note that I made the Body property of type ActivityFunc<Dictionary<string, object>, object>. So basically it expects a Dictionary<string, object> as input and returns an object as the result. I also made this a private property and used metadata.AddImplementationDelegate() to register this with the workflow runtime in order to prevent it from showing up in the produced XAML.
Enjoy!
[f1]
[f2]