Jon Flanders' Blog

WF and Serialization Part One

Tuesday, May 23, 2006 5:42:25 PM (GMT Daylight Time, UTC+01:00)

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 ).

WF   #    Comments [4]   Tracked by:
"Workflow Serialization - what you need to know about persistence" (David Gristw... [Trackback]
"Doug's Connected Applications Blog Report / June 3" (Clemens Vasters: Enterpris... [Trackback]
http://friends.newtelligence.net/clemensv/PermaLink,guid,6ce2b8fe-93fd-421c-a685... [Pingback]
"Tech Blog Report" (Gustavo Echeverry's Blog) [Trackback]
http://blogs.devdeo.com/gustavo.echeverry/PermaLink,guid,3c7a980d-0262-498f-acbe... [Pingback]
"WF Serialization Part One and a half" (Jon Flanders' Blog) [Trackback]
http://www.masteringbiztalk.com/blogs/jon/PermaLink,guid,4e0f556a-2546-4aac-bda9... [Pingback]
"My Gripes with Windows Workflow" (K. Scott Allen) [Trackback]
http://odetocode.com/Blogs/scott/archive/2006/08/17/5647.aspx [Pingback]
http://www.winterdom.com/weblog/2006/08/22/WorkflowComplexityPart2.aspx [Pingback]
"Workflow Complexity, Part 2" (Commonality) [Trackback]

Tuesday, May 23, 2006 9:16:31 PM (GMT Daylight Time, UTC+01:00)
Looks like this is solving all the problems that BizTalk already solves when it comes to persisting flows. Whomever says that WF is a replacement for BizTalk is seriously misguided.

Can't wait to see BizTalk in its new WF clothes though! Would love to be able to change a BizTalk orchestration without having to go through the build/deploy cycle.
McGeeky
Tuesday, May 23, 2006 10:21:57 PM (GMT Daylight Time, UTC+01:00)
Excellent info Jon. One thing that rather baffles me about WF is stuff like this: Sure, the runtime is pretty extensible, and yes, they support you changing most of the built-in services (such as creating your own persistence service that uses something different from serialization), but all of this comes at a cost, and one that, quite frankly, I'm not sure is all that wise: There is almost *no* built-in mechanism to check that you *don't* make mistakes like this during design-time.

While yeah, you might be able to work around stuff like this in your own workflows, what about you depending on third party activities that with unknown internal implementations? You really can't trust them at all (meaning a rather larger set of tests). What about the holy grail of business users defining workflows from sets of custom, business-specific activities? impossible if they need to face simple yet highly annoying things like this?
Tuesday, May 23, 2006 10:26:20 PM (GMT Daylight Time, UTC+01:00)
Tomas - I agree with you *except* they have also given us the validtion hooks to make sure this doesn't happpen. If you are building that holy grail kind of application you are going to be controlling (tightly) the activities that can be added to your workflow.

But I agree - certainly because of the extensibility there are more points at which you can really get bitten (and luckily in all of them I have found you can get yourself back out of trouble pretty easily).
Wednesday, May 24, 2006 5:16:13 AM (GMT Daylight Time, UTC+01:00)
Jon,

Ohh, I don't mean the validation hooks are not there, as you say they are. My point was: how many people will first of all figure they *need* to use those hooks, what code to write and remember to run them on each activity/workflow they use? It sure as hell is not intuitive, and my guess is most people will find out about it the hard way (i.e. by loosing data/time in production), which will only make it all the more frustraring. It's not so much a technical issue per se, but rather somewhat of a User (developer) Experience thing.
All comments require the approval of the site owner before being displayed.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview