(WF4) Lesser Known WF Features: WorkflowDataContext

Sometimes it happens that via the forums I learn about a new [to me] beast in the WF Zoo. Today that animal is WorkflowDataContext. The MSDN document is terse:

“Represents the data context of the current workflow environment and provides a bridge to bring workflow arguments and variables into the scope of data binding.”

Let's put that into everyday WF terms. How would you actually use one of these?

 

Application 1: Reading Variable and Argument Values from inside a C# custom Activity

image

 

It’s a simple workflow. Not shown in the picture above is an InArgument<string> called “argument1”. Also not shown above is that CodeActivity1 has an InArgument<string> called “Text” which is assigned expression “someText”.

CodeActivity1

Here’s a custom C# activity CodeActivity1 which will access the WorfklowDataContext.

public sealed class CodeActivity1 : CodeActivity

{

    public InArgument<string> Text { get; set; }

 

    protected override void Execute(CodeActivityContext context)

    {

        string s = Text.Get(context);

 

        WorkflowDataContext dc = context.DataContext;

        foreach (var p in dc.GetProperties())

        {

            Console.WriteLine("Property {0}: {1}", ((PropertyDescriptor)p).Name, ((PropertyDescriptor)p).GetValue(dc));

        }

    }

}

Notes:
-The DataContext property is defined on ActivityContext  base class , so it's also available for writing NativeActivity and AsyncCodeActivity.
-The required parameter to PropertyDescriptor.GetValue is the WorkflowDataContext object. It acts as an explicit ‘this’ object for doing a property get.

Output

Here’s what we see if we run a simple console app which invokes the workflow and passes in a value for argument1.

image

Notably absent from this list is ‘Property Text’, the InArgument<string> of CodeActivity1 itself.

Application 2: Setting Variable Values or Argument Values from inside a CodeActivity

PropertyDescriptor.SetValue() seems to work equally well as PropertyDescriptor.GetValue() does.

 

Limitations - What can’t you see in WorfklowDataContext?

We noticed above that we can’t actually get the value of the InArgument<string> ‘Text’ which is on this activity from our WorkflowDataContext . Is there anything else we can’t see from WorkflowDataContext? Well, yes. Let’s refactor the above workflow by introducing a composite Activity ‘Activity1’ which wraps the inner Sequence, Sequence2:

Workflow1.xaml

Worfklow1.xaml (Activity1 nested inside)

Activity1.xaml

Activity1.xaml (CodeActivity1 nested inside)

Output

We can run the program again, and get a different result:

image

The conclusion?

Only variables (and arguments) in the parent activity’s public scope are visible in WorkflowDataContext.

For any CodeActivity you drop into a composite activity XAML (<Activity x:Class>), it is effectively limited to seeing only Variables and Arguments from that same XAML file. (This sounds like a good thing from a composability view.)


When is it useful?

Seeing WorkflowDataContext used as a solution to a problem, I immediately react “Wow, this class is really interesting…. especially if it works. But when is using it a good idea?”

The main thing I am thinking about while asking that question is the surprise factor. This feature isn’t dealing with anything ‘esoteric’ or ‘invisible’ in the workflow, such as context properties or extensions. In fact the opposite – it’s dealing directly with everyday Variables and Arguments. As a WF4 designer user I’m only accustomed to a few ways of doing things to specific variables in my workflow:

  1. Initializing variables with default initializers
  2. In the property grid, or custom designer, passing the variable as the parameter to an InArgument<T> or an OutArgument<T> of some activity (for OutArgument, often it is called ‘Result’ and we use ‘Set’).
  3. Use the variable directly in a VBExpression

Based on your accustomed experience to these scenarios, there is a ‘rule’ you can derive: Every use of a variable or argument is explicitly visible somewhere in the designer. The surprise factor is that WorkflowDataContext allows us to invisibly break that rule all over the place. Thereby causing spooky (invisible) action at a distance and so workflows can be created which are ridiculously hard to debug.

So I’d normally not choose to use this technique for data flow, Extensions, or Execution Properties, or regular variable/arguments can be preferred in may cases. And f you are using this for data flow hacks, remember it's maybe a good idea to make that explicit to workflow authors by being visible in the designer. 

WorkflowDataContext is still very interesting for its ability to reflect over all the variables and arguments in scope; like our test activity where we printed out the value of all the variables in our workflow. In this case it really didn't matter what variables are available in the workflow, so we avoided depending on specific variables and arguments existing.