I’m currently working on a client-server application with a .NET 2.0 WinForms GUI. We’re using the standard WebMethod RPC stuff to bounce things back and forward between the server and client, with .NET taking care of serializing everything into XML and back again and NHibernate handling persistence. So in theory, the only work should be designing our domain and our forms, binding the domain objects to the forms, defining some services, writing some Hibernate mapping files, and the occasional bit of business logic when things get dull.

Sadly it’s not to be. Coming from a LAMP / J2EE background, I’ve always been sceptical of the Evil Empire. However in the back of my mind I assumed that there must be something in it what with all the RAD integrated environment drag’n’drop hoopla with which sneering Microsoft weenies assault us poor UNIX kids. One early warning sign came when dealing with distributed transactions. Deep in the OS lurks the MSDTC stuff, which can be cajoled into action with the simple application of the TransactionOption propery in the WebMethod attribute. When things inevitably go wrong, I poke around the filing system in search of a log file. Sure enough we find one… in a binary format. There’s a batch file that promises to convert it into plain text, but luckily it depends on an executable which is only included in the Windows Driver Development Kit. 123Mb of download and installation later, and I discover that the transaction rolled back at the request of the client. Thanks.

Anyway that’s not the real problem. That comes with the limitations imposed by the combination of WebMethod and serialization. When saving from the GUI, we’d like to send an object graph via a WebMethod to have it persisted (if you’re already clutching your head, you can skip to the end). The first problem is that the standard serialization is unable to handle bidirectional associations, such that (for example) a badger keeper has a set of badgers, and each badger has a reference to its badger keeper. When it tries to deserialize the object it dies, saying it has found a circular reference. Furthermore, the standard XML serialization works on properties rather than (private) fields, so you can’t use immutable objects. This can be fixed by adding the IXmlSerializable attribute and using XStream or something similar to do the serialization yourself. However, WebMethod then fails to handle deserializing objects into fields defined with abstract types, saying it can’t instantiate an abstract class.

Another obstacle crops up if you try to serialize any kind of graph except a tree. For example your badgers might eat crumpets, and hence when modelling them you would give them a set of crumpets. It’s convenient for me, as a badger keeper, to have a reference to the same set of crumpets without having to ask the badger for them. However, when two objects both have a reference to a third object in this way, serialization again puts a fly in my crumpets by producing two copies of the third object. One of these copies is referred to by the first object, the other by the second. When Hibernate tries to save the graph, it croaks complaining that it has two references to the same entity. If the third object is a new one (we’ve bought a new crumpet and want to add it to our crumpet store), and the second of the referring objects (the badger) has cascade set to none in its Hibernate mapping (so that I have responsibility for moving around crumpets rather than the badger), you’ll find that Hibernate is unable to persist the graph. It dies because it has a reference to a transient object which will not be saved – the same object that the first object refers to, but a new instance with a new reference created by deserialization.

The most recent problem we’ve encountered occurs due to the client server model we’ve employed, in which objects are created on the client side and then persisted. One such object has a timestamp field which is required to be accurate for auditing purposes. However because it’s created on the client side, users who have their clock set wrongly or who maliciously alter it will generate spurious timestamps. These fields should be initialized on the server side.

None of these issues are show-stoppers, but working around them requires careful thought and in some cases some vigorous refactoring. More than anything, it demonstrates that you can’t just stick some WebMethod attributes on your service methods and expect to have an instant remote service layer. Furthermore, not all these problems are just dumb technical goof-ups on the part of Microsoft – they’re genuine modelling problems that can’t be fixed with clever coding. In particular, trying to serialize an object graph doesn’t just impose the irritating constraint that it must be a directed tree. It also causes problems with locking, synchronization and the problem of how to apply a security policy that varies for different nodes in the graph.

These considerations suggest using a single web service which takes a set of commands, perhaps one for each distinct part of the graph we want to persist. The set could be executed in a single transaction. This frees us from some of these issues, at the cost of incurring further complexity in our services layer: logic that turns a graph into a set of commands and then interprets them on the server side. It does have the advantage of significantly simplifying the schema of our web service by making it generic and weakly typed. We could then handle issues such as preserving backwards compatibility more simply, since they would not involve changes to this schema. Because we’re only using a single service entry point, it becomes possible to easily factor out properties common to all requests, such as authentication. We also get the benefit that we only have to serialize the parts of the graph we actually want to save rather than the whole lot, although this is not a primary consideration just yet. Oh and another thing: we get REST buzzword bingo points.
We’ll be evaluating this approach and some others over the course of the next week, but if anybody who has got this far has any experience in this area, your advice would be much appreciated.

  • http://www.geekswithblogs.com/optikal Dylan

    I’m trying to solve a very similar problem. My Web Service returns some custom business objects to the client. These business objects potentially represent a large object graph. We want to implement lazy-loading in our business objects, so rather than being forced to populate the whole graph, we just populate the one object, and when the consumer tries to access a member that isn’t loaded, the business object will be able to call out to the web service to retrieve the necessary data.

    I implement this lazy-loading code in the property get for the business objects. They check to see if the member has already been loaded, if so it returns it, if not it calls out to the web service to load it.

    The problem is, when the web service tries to serialize my business object, it iterates through all the public properties calling Get on each one. This would cause the lazy-loader code to kick into action and fill in the whole object graph, which is not what I want.

    If I mark that property as NonSerializable then it won’t get serialized at all, even if it is already loaded. Which is also not what I want (if it’s loaded already, I don’t want to lose the data, it should be serialized and sent down the pipe).

    I had hoped I could use some kind of events such as the ones talked about here: http://msdn.microsoft.com/msdnmag/issues/04/10/AdvancedSerialization/#S3 And put a little bit of code before the serialization that would “turn off” the lazy loader. Then I could have some code execute on the client after deserialization that turned it back on. Unfortunately, these attributes appear to be for use with the BinaryFormatter only, and the “stuff” uses the XML Serializer.

    The two solutions we’ve thought about is to write our own custom serializer that would be used. I don’t know how much work this is, or how to indicate to the ASMX plumbing which serializer it should use, but I assume it’s not trivial.

    The other solution, which is very “hacky”, but I’m leaning towards right now, is to add some code to the lazy-loader which retrieves the call stack and uses that to determine if it’s being called by the serializer, and if so do not lazy-load.

    If you’ve made any progress on your problem, or have any advice for me I’d love to hear.

    Cheers,
    Dylan

  • Dave Clemmer

    I’ve run into the exact same problem regarding the need to turn off lazy loading when serialization occurs, trying the “hacky” approach so far.

    // ——————————————————————————
    /// This property returns whether or not the entity can be lazy loaded.
    // ——————————————————————————
    [XmlIgnore]
    public virtual bool IsLazyLoadable
    {
    get
    {
    // the hack to check the calling assembly is done to prevent the serializer from lazy loading everything
    // TODO: look for a cleaner solution
    return IsLoaded == true && System.Reflection.Assembly.GetCallingAssembly().ManifestModule.Name.Contains(“Unknown”) == false && System.Reflection.Assembly.GetCallingAssembly().ManifestModule.FullyQualifiedName.ToLower().Contains(“webservice”) == false;
    }
    }