WF4: How To Access Out Arguments
To get a value from your workflow you must access the dictionary of output arguments returned from the workflow.
Note: This example is based on Visual Studio 2010 Beta 2
Consider a workflow that accepts two arguments
Name | Direction | Argument Type |
UserName | In | String |
Greeting | Out | String |
The workflow uses an assign activity to set the result of the expression "Hello " & UserName & " from Workflow 4" to the Greeting out argument. The hosting program can access the Greeting out argument from the dictionary of output values returned from the WorkflowInvoker or WorkflowApplication.
Because the values in the output dictionary are stored as objects with a string as the key, there are three possible outcomes when you try to access an argument value.
- The key does not exist in the dictionary
- The key exists but the type of the value is not what you expected
- The key exists and the type is compatible with what you expected
Whenever you access an out argument you must consider these outcomes. In the event that the key does not exist or the type of the value is not what is expected you may encounter a KeyNotFoundException or InvalidCastException
You can choose to
- Let these exceptions propagate possibly terminating the host with an unhandled exception
- Catch the exceptions
- Code defensively using TryGetValue to access the key from the collection or the C# keyword “as” or VB function TryCast to avoid the invalid cast
Accessing Out Arguments with WorkflowInvoker
The following example shows a method that Invokes the SayHello workflow passing in the UserName and accessing the out argument named “Greeting” after the workflow completes.
Watch Out
This example is not coding defensively or catching exceptions so if the key did not exist or the type of the value was not compatible the exception would propagate to the caller.
C#
private static void GetArgumentsFromWorkflowInvoker()
{
IDictionary<string, object> output = WorkflowInvoker.Invoke(
new SayHello() { UserName = "Test" });
string greeting = (string)output["Greeting"];
Console.WriteLine("WorkflowInvoker said {0}", greeting);
}
Visual Basic
Shared Sub GetArgumentsFromWorkflowInvoker()
Dim output As IDictionary(Of String, Object) =
WorkflowInvoker.Invoke(New SayHello() With {.UserName = "Test"})
Dim greeting As String = output("Greeting")
Console.WriteLine("WorkflowInvoker said {0}", greeting)
End Sub
Accessing Out Arguments with WorkflowApplication
WorkflowApplication invokes the workflow on a thread from the CLR threadpool. To capture the outputs you must assign a delegate to the WorkflowApplication.Completed property. Keep in mind that your delegate is called whenever the workflow completes successfully or not. You can check the CompletionState property to find out if the activity closed or faulted. Because accessing the Outputs dictionary may result in an exception you should wrap the access with a try/catch/finally block as shown.
C#
private static void GetArgumentsFromWorkflowApplication()
{
AutoResetEvent sync = new AutoResetEvent(false);
string greeting = null;
Exception argException = null;
WorkflowApplication wfApp = new WorkflowApplication(
new SayHello()
{
UserName = "Test"
});
wfApp.Completed = (e) =>
{
// Did the workflow complete without error?
if (e.CompletionState == ActivityInstanceState.Closed)
{
try
{
// Accessing the output arguments dictionary
// might throw a KeyNotFoundException or
// InvalidCastException
greeting = (string)e.Outputs["Greeting"];
}
catch (Exception ex)
{
argException = ex;
}
finally
{
// Must be sure to unblock the main thread
sync.Set();
}
}
};
wfApp.Run();
sync.WaitOne();
// Show the exception from the background thread
if (argException != null)
Console.WriteLine("WorkflowApplication error {0}", argException.Message);
else
Console.WriteLine("WorkflowApplication said {0}", greeting);
}
Visual Basic
Shared Sub GetArgumentsFromWorkflowApplication()
Dim sync As AutoResetEvent = New AutoResetEvent(False)
Dim greeting As String = Nothing
Dim argException As Exception = Nothing
Dim wfApp As WorkflowApplication =
New WorkflowApplication(New SayHello() With {.UserName = "Test"})
wfApp.Completed = Function(args)
If (args.CompletionState =
ActivityInstanceState.Closed) Then
Try
' Accessing the output arguments dictionary
' might throw a KeyNotFoundException or
' InvalidCastException
greeting = args.Outputs("Greeting")
Catch ex As Exception
argException = ex
Finally
' Must be sure to unblock the main thread
sync.Set()
End Try
End If
' VB requires lambda expressions to return a value
Return Nothing
End Function
wfApp.Run()
sync.WaitOne()
' Show the exception from the background thread
If (argException Is Nothing) Then
Console.WriteLine("WorkflowApplication said {0}", greeting)
Else
Console.WriteLine("WorkflowApplication error {0}", argException.Message)
End If
End Sub
Accessing Out Arguments with Defensive Coding Style
In this example, we are accessing the Out Argument with a defensive coding style that will insure no exceptions are thrown.
ContainsKey() vs. TryGet()
You should use TryGet instead of ContainsKey() to first check for the key and then Get() to access the key. The reason for this is that you will iterate over the collection twice, once to determine if the key is present and again to access the value.
C#
private static void GetArgumentsFromWorkflowInvokerDefensive()
{
IDictionary<string, object> output = WorkflowInvoker.Invoke(
new SayHello() { UserName = "Test" });
object obj = null;
string greeting = null;
if (!output.TryGetValue("Greeting", out obj))
{
Console.WriteLine("Greeting not found");
}
else
{
greeting = obj as string;
if (greeting == null)
Console.WriteLine("Greeting could not be converted to a string");
else
Console.WriteLine("WorkflowInvoker said {0}", greeting);
}
}
Visual Basic
Shared Sub GetArgumentsFromWorkflowInvokerDefensive()
Dim output As IDictionary(Of String, Object) =
WorkflowInvoker.Invoke(New SayHello() With {.UserName = "Test"})
Dim obj As Object = Nothing
Dim greeting As String
' TryGetValue will not throw an exception
If (Not output.TryGetValue("Greeting", obj)) Then
Console.WriteLine("Greeting not found")
Else
' Not sure what type it is, try to convert it
greeting = TryCast(obj, String)
If (greeting Is Nothing) Then
Console.WriteLine("Greeting could not be converted to a string")
Else
Console.WriteLine("WorkflowInvoker said {0}", greeting)
End If
End If
End Sub