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.

  1. The key does not exist in the dictionary
  2. The key exists but the type of the value is not what you expected
  3. 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