Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
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!