Passing arguments to Workflow Activities (again)
Way back in September 2009 I wrote WF4: Passing Arguments to Activities. In the years since I’ve learned a few things.
Download code for this post Windows Workflow Foundation (WF4) - Workflow Arguments Example
Watch Out for Initialization Syntax
I used to like the way we made a Workflow definition look like any other CLR object even allowing you to do initialization syntax for some types.
static void Main(string[] args)
{
// What is SayHello? It looks like any other CLR object
WorkflowInvoker.Invoke(new SayHello() { Name = "Ron" });
}
API Hypocrisy
I once heard someone say that Hypocrisy is behavior that tells a lie. It looks like one thing is happening, but in reality something very different is going on. What you see in the code above is an example of this. It looks like a simple reference but what is actually going on is we are constructing an InArgument<T> by taking the string value “Ron” and converting it into a Literal<T>.
What happens if the argument is not something that can be treated as a literal. Suppose we create a Person object?
static void Main(string[] args)
{
var me = new Person() { Name = "Ron", Age = 46 };
// Will this work?
WorkflowInvoker.Invoke(new SayHello() { Person = me });
}
Not so fast… When you run this you get an exception
System.Activities.InvalidWorkflowException was unhandled Message=The following errors were encountered while processing the workflow tree: 'Literal<Person>': Literal only supports value types and the immutable type System.String. The type WorkflowConsoleApplication1.Person cannot be used as a literal.
Now what? The hypocrisy is revealed! The activity you are working with is not just any old CLR object. Not to mention that if you create a new one every time you will feel significant performance pain.
If you want to use a reference type you have to create a Dictionary<string, object> to pass the arguments like this
private static void SayHello3()
{
var me = new Person { Name = "Ron", Age = 46 };
// have to construct a dictionary
var input = new Dictionary<string, object> { { "Person", me } };
// And pass it to the activity
var output = WorkflowInvoker.Invoke(new SayHello(), input);
// have to access the output with indexer
Console.WriteLine("Workflow said {0}", output["Greeting"]);
}
Input Dictionary vs. Property Syntax
What happens if you use both a property initializer and the input dictionary?
private static void SayHelloSimpleWithBoth()
{
Console.WriteLine("What if you use both property initializer and input dictionary?");
var activityDefinition = new SayHelloSimple() { Name = "Initializer" };
var input = new Dictionary<string, object> { { "Name", "Input Dictionary" } };
WorkflowInvoker.Invoke(activityDefinition, input);
// Hint: The Input Dictionary wins every time...
}
What this means is that the initializer syntax creates a default value that will be used if no value is supplied by the input dictionary.
New Microsoft.Activities.WorkflowArguments Class
After exploring ASP.NET MVC for a while and working with the ViewBag dynamic class I thought wouldn’t it be cool if I could do the same thing for Workflow arguments. So I added a new class to Microsoft.Activities v1.83.
To use it, I just install the package with NuGet Package Manager which installs the package and adds a reference for me.
PM> install-package Microsoft.Activities
Successfully installed 'Microsoft.Activities 1.8.3.526'.
Successfully added 'Microsoft.Activities 1.8.3.526' to WorkflowConsoleApplication1.
Then I can modify my code like this
private static void SayHello4()
{
// Create a dynamic object
dynamic input = new WorkflowArguments();
// The property names have to match the workflow argument names
input.Person = new Person { Name = "Ron", Age = 46 };
// pass it to the activity no need to cast it
// You can do the same on the output
var output = WorkflowArguments.FromDictionary(WorkflowInvoker.Invoke(new SayHello(), input));
// Access the output with property syntax
Console.WriteLine("Workflow said {0}", output.Greeting);
// Or access the output with indexer
Console.WriteLine("Workflow said {0}", output["Greeting"]);
}
And it totally works! Are dynamic objects API Hypocrisy? Maybe. It’s just a little syntactic sugar over the inner dictionary but it is a lot of fun.
Happy Coding!
Ron Jacobs
https://blogs.msdn.com/rjacobs
Twitter: @ronljacobs https://twitter.com/ronljacobs
Comments
- Anonymous
May 29, 2011
Hy ron Thanks for sharing. I added a similar helper some time ago to the ninject.extensions.wf package which I created last year. github.com/ ninject/ninject.extensions.wf Happy coding!