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