Serialize Workflows and Activities to and from XAML

In addition to being compiled into types that are contained in assemblies, workflow definitions can also be serialized to XAML. These serialized definitions can be reloaded for editing or inspection, passed to a build system for compilation, or loaded and invoked. This topic provides an overview of serializing workflow definitions and working with XAML workflow definitions.

Work with XAML Workflow definitions

To create a workflow definition for serialization, the ActivityBuilder class is used. Creating an ActivityBuilder is very similar to creating a DynamicActivity. Any desired arguments are specified, and the activities that constitute the behavior are configured. In the following example, an Add activity is created that takes two input arguments, adds them together, and returns the result. Because this activity returns a result, the generic ActivityBuilder<TResult> class is used.

ActivityBuilder<int> ab = new ActivityBuilder<int>();
ab.Name = "Add";
ab.Properties.Add(new DynamicActivityProperty { Name = "Operand1", Type = typeof(InArgument<int>) });
ab.Properties.Add(new DynamicActivityProperty { Name = "Operand2", Type = typeof(InArgument<int>) });
ab.Implementation = new Sequence
{
    Activities =
    {
        new WriteLine
        {
            Text = new VisualBasicValue<string>("Operand1.ToString() + \" + \" + Operand2.ToString()")
        },
        new Assign<int>
        {
            To = new ArgumentReference<int> { ArgumentName = "Result" },
            Value = new VisualBasicValue<int>("Operand1 + Operand2")
        }
    }
};

Each of the DynamicActivityProperty instances represents one of the input arguments to the workflow, and the Implementation contains the activities that make up the logic of the workflow. Note that the r-value expressions in this example are Visual Basic expressions. Lambda expressions are not serializable to XAML unless Convert is used. If the serialized workflows are intended to be opened or edited in the workflow designer, then Visual Basic expressions should be used. For more information, see Authoring Workflows, Activities, and Expressions Using Imperative Code.

To serialize the workflow definition represented by the ActivityBuilder instance to XAML, use ActivityXamlServices to create a XamlWriter, and then use XamlServices to serialize the workflow definition by using the XamlWriter. ActivityXamlServices has methods to map ActivityBuilder instances to and from XAML and to load XAML workflows and return a DynamicActivity that can be invoked. In the following example, the ActivityBuilder instance from the previous example is serialized to a string and saved to a file.

// Serialize the workflow to XAML and store it in a string.
StringBuilder sb = new StringBuilder();
StringWriter tw = new StringWriter(sb);
XamlWriter xw = ActivityXamlServices.CreateBuilderWriter(new XamlXmlWriter(tw, new XamlSchemaContext()));
XamlServices.Save(xw, ab);
string serializedAB = sb.ToString();

// Display the XAML to the console.
Console.WriteLine(serializedAB);

// Serialize the workflow to XAML and save it to a file.
StreamWriter sw = File.CreateText(@"C:\Workflows\add.xaml");
XamlWriter xw2 = ActivityXamlServices.CreateBuilderWriter(new XamlXmlWriter(sw, new XamlSchemaContext()));
XamlServices.Save(xw2, ab);
sw.Close();

The following example represents the serialized workflow.

<Activity
  x:TypeArguments="x:Int32"
  x:Class="Add"
  xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <x:Members>
    <x:Property Name="Operand1" Type="InArgument(x:Int32)" />
    <x:Property Name="Operand2" Type="InArgument(x:Int32)" />
  </x:Members>
  <Sequence>
    <WriteLine Text="[Operand1.ToString() + " + " + Operand2.ToString()]" />
    <Assign x:TypeArguments="x:Int32" Value="[Operand1 + Operand2]">
      <Assign.To>
        <OutArgument x:TypeArguments="x:Int32">
          <ArgumentReference x:TypeArguments="x:Int32" ArgumentName="Result" />
          </OutArgument>
      </Assign.To>
    </Assign>
  </Sequence>
</Activity>

To load a serialized workflow, use the ActivityXamlServices Load method. This takes the serialized workflow definition and returns a DynamicActivity that represents the workflow definition. Note that the XAML is not deserialized until CacheMetadata is called on the body of the DynamicActivity during the validation process. If validation is not explicitly called, then it is performed when the workflow is invoked. If the XAML workflow definition is invalid, then an ArgumentException exception is thrown. Any exceptions thrown from CacheMetadata escape from the call to Validate and must be handled by the caller. In the following example, the serialized workflow from the previous example is loaded and invoked by using WorkflowInvoker.

// Load the workflow definition from XAML and invoke it.
DynamicActivity<int> wf = ActivityXamlServices.Load(new StringReader(serializedAB)) as DynamicActivity<int>;
Dictionary<string, object> wfParams = new Dictionary<string, object>
{
    { "Operand1", 25 },
    { "Operand2", 15 }
};

int result = WorkflowInvoker.Invoke(wf, wfParams);
Console.WriteLine(result);

When this workflow is invoked, the following output is displayed to the console.

25 + 15
40

Note

For more information about invoking workflows with input and output arguments, see Using WorkflowInvoker and WorkflowApplication and Invoke.

If the serialized workflow contains C# expressions, then an ActivityXamlServicesSettings instance with its CompileExpressions property set to true must be passed as a parameter to ActivityXamlServices.Load, otherwise a NotSupportedException will be thrown with a message similar to the following: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled.

ActivityXamlServicesSettings settings = new ActivityXamlServicesSettings
{
    CompileExpressions = true
};

DynamicActivity<int> wf = ActivityXamlServices.Load(new StringReader(serializedAB), settings) as DynamicActivity<int>;

For more information, see C# Expressions.

A serialized workflow definition can also be loaded into an ActivityBuilder instance by using the ActivityXamlServices CreateBuilderReader method. After a serialized workflow is loaded into an ActivityBuilder instance, it can be inspected and modified. This is useful for custom workflow designer authors and provides a mechanism for saving and reloading workflow definitions during the design process. In the following example, the serialized workflow definition from the previous example is loaded and its properties are inspected.

// Create a new ActivityBuilder and initialize it using the serialized
// workflow definition.
ActivityBuilder<int> ab2 = XamlServices.Load(
    ActivityXamlServices.CreateBuilderReader(
    new XamlXmlReader(new StringReader(serializedAB)))) as ActivityBuilder<int>;

// Now you can continue working with the ActivityBuilder, inspect
// properties, etc...
Console.WriteLine("There are {0} arguments in the activity builder.", ab2.Properties.Count);
foreach (var prop in ab2.Properties)
{
    Console.WriteLine("Name: {0}, Type: {1}", prop.Name, prop.Type);
}