Testing

webTiger Logo Wide

Serialising and De-Serialising Workflows (in Windows Workflow 4)

Workflow

There may be occasions when you’ll want to save the XAML version of a workflow (root activity) while persisting it. In my case this is normally to ensure that when I persist a workflow and later want to reload it, I’ve got the exact workflow structure I started with. This article discusses how to do just that.

Microsoft’s default persistence implementation (to a SQL database) stores all the metadata associated with a workflow but not the workflow structure itself. The persistence store therefore relies on the developer loading the exact workflow structure again prior to resuming it.

Any changes to the structure of a workflow could break the ability to resume from a persisted state. If your workflows aren’t strictly versioned (and named including that version) then you could easily load an older or newer version of a workflow instead of the one you really wanted to resume.

I tend to serialise the workflow’s root activity and save it in the database against the same workflow instance ID. That way it can be retrieved and de-serialised from that state without any worry about the structure having changed since it was persisted.

For normal activities, serialisation is a simple matter:

string serialisedMarkup = System.Xaml.XamlServices.Save(rootActivty);Code language: C# (cs)

The XamlServices class won’t serialise dynamic activities though, so you need to perform a custom serialisation routine to cater for that:

private string SerialiseDynamic(DynamicActivity activity)
{
    ActivityBuilder builder = new ActivityBuilder();

    foreach (Attribute attribute in acitivity.Attributes)
    {
        builder.Attributes.Add(attribute);
    }

    foreach (Constraint constraint in activity.Constraints)
    {
        builder.Constraints.Add(constraint);
    }

    builder.Implementation = activity.Implementation != null 
        ? activity.Implementation.Invoke() 
        : null;
    builder.Name = activity.Name;

    foreach (DynamicActivityProperty property in activity.Properties)
    {
        builder.Properties.Add(property);
    }

    StringBuilder markup = new StringBuilder();
    XamlWriter writer = ActivityXamlServices.CreateBuilderWriter(
    new XamlXmlWriter(new StringWriter(markup), new XamlSchemaContext()));

    XamlServices.Save(writer, builder);

    // One last correction... the XML produced by the above code may specify UTF-16 
    // as the encoding type, but this will cause issues when attempting to reload 
    // the workflow.
    // Let's replace the XML header definition if that is the case.
    const string UnicodeHeader = "<?xml version=\"1.0\" encoding=\"utf-16\"?>";
    const string NormalHeader = "<?xml version=\"1.0\"?>";

    if (markup.ToString().StartsWith(UnicodeHeader))
    {
        StringBuilder replaced = new StringBuilder(markup.ToString());
        replaced.Remove(0, UnicodeHeader.Length);
        replaced.Insert(0, NormalHeader);
        return replaced.ToString();
    }

    return markup.ToString();
}Code language: C# (cs)

The serialised XAML markup can then be saved to a suitable location, database, etc. as required. De-serialising the data into an Activity again is fairly straightforward but special handling is once again needed for the dynamic activity.

private Activity DeserialiseActivity(string markup)
{
    Activity root = null;
    try
    {
        // DEV-NOTE: Load could be used instead of Parse here.
        root = XamlServices.Parse(markup) as Activity; 
    }
    catch (Exception ex)
    {
        // Not always a good idea to use error handling for flow control but 
        // its the easiest approach here!
        try 
        {
            using (MemoryStream stream = 
                new MemoryStream(Encoding.UTF8.GetBytes(markup)))
            {
                root = ActivityXamlServices.Load(stream);
            }
        }
        catch (Exception ex2)
        {
            // TODO: Log the ex2 error if desired.
            throw ex; // Throw the original error.
        }
    }

    return root;
}Code language: C# (cs)