Monthly Archives: May 2006

WF and Serialization Part One

This is part one of a two part post (because I know it is going to be too long to be comfortable as a single post) on Workflow and Serialization.

As a starting point, the WF Runtime is built to handle serialization of Activities.  The most common form of serialization in WF is persistence.  If you are running workflows inside of WF and you add a WorkflowPersistenceService to the Runtime, the Runtime will use this service whenever it determines that the workflow should persist.  This enables some interesting scenarios like load-balancing, where a workflow can be persisted on one machine and then be brought back to life on another machine (as in an ASP.NET web farm).

Persistence of Workflows is fairly determinilistic, they persist when:

1) The host calls WorkflowInstance.Unload or TryUnload (i.e. the host wants them to persist).

2) The end of a TransactionScopeActivity or an Activity that has the PersistOnClose attribute on it.

3) When the workflow idles (assuming the WorkflowPersistenceService tells the runtime to persist for that Activity type).

4) When an ActivityExecutionContext closes (sometimes, more on this in Part Two – thanks to Dharma http://www.dharmashukla.com/ for catching this).

Now – one of the issues people have been running into with WF and persistence is that when you first start designing your WF system, you generally don’t add a WorkflowPersistenceService at the beginning.  The issue that can trip you up here is that if you are adding fields or properties to your Workflow you want to think about Serialization upfront.  In fact, the parameters that you can pass to WorkflowRuntime.CreateWorkflow are not serialized (unlike parameters you pass when you do ExternalDataExchangeService related communication), so it is fairly easy to add a property to your workflow that isn’t serializable and your workflow works fine, up until the time you add a WorkflowPersistenceService.  When you add the WorkflowPersistenceService you will get a serialization exception when your workflow tries to persist.  This of course is problematic.

So what can you do to deal with objects your workflow might need to have as internal state which aren’t serializable if you need to use the WorkflowPersistenceService.

So the choices branch here.  If you wrote the type yourself, you can always add the Serializable attribute (or implement ISerializable) to your type.  This certainly solves the problem, assuming that this type can be serialized, which it might not be depending on the fields that this type has.

Another option is to add the NonSerialized attribute to any fields (either on the Workflow or your child types) that you can live without.  This option works whether or not you wrote the type the fields represent.

Now – if you didn’t write the type in question that isn’t serializable *and* you need to keep that object around during serialization (that is you need the state this object has and can’t easily re-create it after de-serialization), you have another option (this options is actually used by the WorkflowRuntime to serialize Activity – which if you look at the definition doesn’t have the Serializable attribute or implement the ISerializable interface).

The option relates to the .NET runtime’s serialization system. You can find information about this system by reading this article  by Jeff Richter.  By registering a SerializationSurrogate, you can allow an non-serializable type to be serialized and deserialized, which of course would solve the problem at hand.

I won’t go into the technical details as much as show you how to register your own ISerializationSurrogate to integrate with WF.  Let’s assume your workflow has a non-serializable type as a property – let’s assume something simple like a Point (of course this type could easily be serializable – let’s assume we didn’t write it and the author didn’t consider this issue).

public class Point
{
public Point(int px,int py)
{

x = px;
y = py;
}
public int x;
public int y;
}

 

So assume our workflow has a proprerty of this type, of course everything will work fine until we add the WorkflowPersistenceService, at which time of course we’ll get a serialization exception.

To fix this problem we can create a SurrogateSelector, that when chained with the WF Runtime’s SurrogateSelector can be used to serialize the Point type for us.  So the first this we need to do is to write the SurrogateSelector and the SerializationSurrogate.  Here is a simple implementation for Point:

public class PointSurrogateSelector : SurrogateSelector
{
public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
{
if (type == typeof(Point))
{
selector = this;
return new PointSerializationSurrogate();
}
return base.GetSurrogate(type, context, out selector);
}
}
public class PointSerializationSurrogate : ISerializationSurrogate
{
[Serializable]
public class PointRef : IObjectReference
{
#region IObjectReference Members

public object GetRealObject(StreamingContext context)
{
Point p = new Point(x, y);
return p;
}
int x;
int y;

#endregion
}
#region ISerializationSurrogate Members

public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
Point p = obj as Point;
if (p == null)
{
throw new ArgumentException(“Invalid type”);

}
info.AddValue(“x”, p.x);
info.AddValue(“y”, p.y);
info.SetType(typeof(PointRef));
}

public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
throw new Exception(“The method or operation is not implemented.”);
}

#endregion
}

Next we need to chain this SurrogateSelector with the ActivitySurrogateSelector.  This can be done anywhere, but needs to be done before this workflow will be serialized, so I prefer to add this code in my Hosting layer before I start the WorkflowRuntime (although it can be added after the WorkflowRuntime starts as it isn’t a WorkflowService  – it just hooks into the .NET serialization infrastructure).  Here is the line of code that does that:

ActivitySurrogateSelector.Default.ChainSelector(new PointSurrogateSelector());

So here we are – at the end of part one, and we can now make nonserializable types serializable by registering a SurrogateSelector and SerializationSurrogate.  In Part Two I will talk about stategies you can use to reduce the size of your serialized workflows, as well as another related way to solve this problem (which was suggested to me by a student in my WF class last week – Faisal Alamgir from EDS ).