Saturday, April 28, 2007

Microsoft has gone to great lengths to keep you from touching your instance directly, forcing you to always use the workflowinstance class to manipulate your workflow from the outside. They have done this to make sure the integrity of the system is maintained. It is however, a major pain in the ass. ;-)
I have been looking into solving a serious deficiency of windows workflow foundation: updating current workflows in your database. Microsoft does not have a good story on that one, and the WF architecture seems to be designed to make it as hard as possible to actually pull off! One approach to do this, would be to get to your instance, use reflection to get to private and public fields and create your new type, setting those fields as you go. Therefor, I have been looking into getting to my persisted instance.

Please be very careful with the following technique. Do not use it lightly, because it does allow you to do things that will break the integrity. It _will_ get you into trouble, if you do not watch out.


We are going to fetch a persisted workflow, but do not want the workflow runtime interfering. So:

Database db = DatabaseFactory.CreateDatabase("##Your database##");
byte[] act = (byte[])db.ExecuteScalar(CommandType.Text,
"select state from InstanceState where uidInstanceID = '## the guid you are interested in ##'");

This will directly query your database for some guid. (I am using Entlib 3.0 here).
The byte array returned is formatted with the binaryformatter. To be able to serialize activities, the WF-team does not use the Serializable attribute, but uses surrogates. A surrogate is a class that has intimate knowledge on how to serialize a certain type. Looking at the class ActivitySurrogate, you will notice that they have quite a bit going on there. Most important seems to be the private sealed class ActivitySurrogateRef, which defines fields like these:

[NonSerialized]
private Activity cachedActivity;
[NonSerialized]
private Activity cachedDefinitionActivity;
[OptionalField]
private EventHandler disposed;
private string id = string.Empty;
[NonSerialized]
private int lastPosition;
[OptionalField]
private object memberData;
[OptionalField]
private object[] memberDatas;
[OptionalField]
private string[] memberNames;
[OptionalField]
private string rulesMarkup;
[OptionalField]
private Type type;
[OptionalField]
private string workflowChanges;
[OptionalField]
private Guid workflowChangeVersion = Guid.Empty;
[OptionalField]
private string workflowMarkup;

So, it is clear our binaryformatter is going to have to reuse this logic. Thankfully, we can use the ActivitySurrogateSelector. This selector has logic that allows it to select the correct surrogate for each type. We set it like this:

BinaryFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = ActivitySurrogateSelector.Default;

Now it is time to deserialize our byte array. However, it is zipped!! So, let's unzip it:

MemoryStream stream = new MemoryStream(act);
stream.Position = 0;
using (GZipStream stream2 = new GZipStream(stream, CompressionMode.Decompress, true))
{ // here we can finally deserialize.  }

Inside that using-statement, we will deserialize. Here is the line that does the magic:

T activity = (T)Activity.Load(stream2, workflowRuntime.CreateWorkflow(typeof(T)).GetWorkflowDefinition(), formatter);

The 'T' is the type of your workflow.
I am using the static Load functionality of Activity to load. We first pass in our unzipped stream (stream2) and then we have to pass the workflowdefinition of the type we are deserializing. Finally, we are using our own formatter, with the activity surrogate attached.

Obviously, the use of the workflowruntime to get to the workflowdefinition is not pretty. Ugly even. However, I have not been able to circumvent it. Passing a null, will result in serializationexceptions.
I will update this post if I find a better way. Please leave a comment if you know of one!

I can imagine that it is useful to be able to get to your instance directly. For now, the only way to get to it, seems to be when they have to release it to the persistence store.

I hope this helps someone!

Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):