Passing data into a Windows Workflow Foundation 4 workflow
Passing parameters into a workflow is similar in WF4 as it was in WF3.
In both case a Dictionary<string, object> is passed in when creating the workflow instance object. Alternatively when using the WF4 WorkflowInvoker you can pass it into the Invoke() method.
This is quite simple but I have never been a fan of the very loose coupling here. basically on the outside you have to create a dictionary with as key something inside of the workflow. No compile time checking. No improvement where, unless you consider the somewhat easier unit testing with the WorkflowInvoker.
The actual behavior between WF3 and WF4 is quite different however.
In Wf3 if you pass in a key that doesn’t match up you get a System.Reflection.TargetInvocationException with a System.InvalidOperationException inner exception with the text: “This operation can not be performed at runtime.”. Great help in debugging
In WF4 on the other hand you get no error at all. The value is just ignored so it is even harder to detect typos. Well I guess not every change can be an improvement. And just in case you where thinking “But a unit test will take care of that right?” keep in mind that workflows tend to be complex and long running things not very well suited to unit testing.
Starting a workflow with a parameter:
Shared Sub Main()
Dim inArgs As New Dictionary(Of String, Object)
inArgs("Name") = "Maurice"
Dim myInstance As WorkflowInstance = New WorkflowInstance(New Sequence1(), inArgs)
myInstance.OnCompleted = AddressOf OnWorkflowCompleted
myInstance.OnAborted = AddressOf OnWorkflowAborted
myInstance.OnUnhandledException = AddressOf OnWorkflowUnhandledException
So where is this Name value actually stored?
Well in WF3 that would have been stored in a property, possibly a dependency property, or a field. The types must match, exactly.
In WF4 the notion of properties and fields is gone. No more dependency properties to tie everything together. So instead we have Variables and Arguments. Basically a variable is just like a variable in a function, only here it holds some value for an activity. Big difference is that a Variable is in scope, i.e. visible, to all nested activities as well. So basically it is a way of sharing state between different activities.
To allows us to pass data into a workflow, or get something out, we need to use an Argument.
In this case I have defined two arguments, the Name to pass into the workflow and the Greeting that will be the result of the workflow.
Getting the result out is done in the OnCompleted delegate. This delegate is passed a WorkflowCompletedEventArgs which in turn contains another dictionary with all out argument.
Shared Sub OnWorkflowCompleted(ByVal e As WorkflowCompletedEventArgs)
Dim greeting As String = e.Outputs("Greeting")
Again not very different from WF3 where we used the WorkflowCompleted event and the OutputParameters dictionary from the WorkflowRuntime.
While there are quite a few differences in the details the basic approach remains the same. And as the old approach with dictionaries was rather error prone I consider this a missed chance