views:

376

answers:

4

I'm trying to load a tree of objects via XML serialisation, and at the moment it will load the objects in, and create the tree quite happily. My issue revolves around the fact that these classes support a level of auditing. What I'd like to be able to do is call some method on each object after it has finished being loaded.

For the sake of argument, assume I have a fairly generic object tree with differing classes at different levels, like:

 <Customer name="Foo Bar Inc.">
   <Office IsHq="True">
     <Street>123 Any Street</Street>
     <Town name="Anytown">
       <State name="Anystate">
         <Country name="My Country" />
       </State>
     </Town>
   </Office>
   <Office IsHq="False">
     <Street>456 High Street</Street>
     <Town name="Anycity">
       <State name="Anystate">
         <Country name="My Country" />
       </State>
     </Town>
   </Office>
 </Customer>

Is there any way using the default serialisers (In the similar way that you can create methods like ShouldSerializeFoo) to determine when loading has finished for each object?

EDIT: I should point out that the obvious case of exposing something akin to an OnLoaded() method that I could call after deserialising, strikes me as being a "bad thing to do".

EDIT2: For the sake of discussion this is my current hack "approach", which works for the basic level, but the child City node still thinks it needs to be saved with changes (in the real world the object model is a lot more complex, but this will at least compile, without the need for full source)

public class Office
{
    [XmlAttribute("IsHq")] public bool IsHeadquarters { get; set; }
    [XmlElement()] public string Street { get; set; }
    [XmlElement()] public Town Town { get; set; }

    protected virtual void OnLoaded() {}

    public static OfficeCollection Search()
    {
        OfficeCollection retval = new OfficeCollection();
        string xmlString = @"<Office IsHq='True'>
 <Street>123 Any Street</Street>
 <Town name='Anytown'>
   <State name='Anystate'>
     <Country name='My Country' />
   </State>
 </Town>

";

        XmlSerializer xs = new XmlSerializer(retval.GetType());
        XmlReader xr = new XmlTextReader(xmlString);
        retval = (OfficeCollection)xs.Deserialize(xr);

        foreach (Office thisOffice in retval)
        {
            thisOffice.OnLoaded();
        }

        return retval;
    }
}
+1  A: 

A toughie, since XmlSerializer doesn't support serialization callback events. Is there any way you could use DataContractSerializer? That does, but doesn't allow attributes (like @name above).

Otherwise; you could implement IXmlSerializable, but that is lots of work, and very error-prone.

Otherwise - perhaps checking the caller via the stack, but that is very brittle, and smells ripe.

Marc Gravell
+3  A: 

Hmmm... it's still not pretty but you could refactor your deserialization logic into a dedicated class which could notify the deserialized object that it originated from XML before returning it to the caller.

Update: I think this should be fairly easy to do without straying too far from the patterns laid by the framework... you'd just need to ensure that you use the CustomXmlSerializer. Classes that need this notification just need to implement IXmlDeserializationCallback

using System.Xml.Serialization;

namespace Custom.Xml.Serialization
{
    public interface IXmlDeserializationCallback
    {
        void OnXmlDeserialization(object sender);
    }

    public class CustomXmlSerializer : XmlSerializer
    {
        protected override object Deserialize(XmlSerializationReader reader)
        {
            var result = base.Deserialize(reader);

            var deserializedCallback = result as IXmlDeserializationCallback;
            if (deserializedCallback != null)
            {
                deserializedCallback.OnXmlDeserialization(this);
            }

            return result;
        }
    }
}
STW
I think this option makes the most sense. Post-process the objects after deserialization.
mackenir
Playing around with it I still think it's a little cludgy, but if the class handling the Deserialization uses generics and notifies the deserialized objects via an interface then it's a little better and not too bad to code against.
STW
That's effectively my best option "at the moment". I'm struggling with an elegant option to pass that message down the tree - exposing an `OnLoaded()` type of method seems "wrong"
Rowland Shaw
Would the updated suggestion look to see if the interface is implemented on the differing layers in the tree? For instance, Say I care about it for Office nodes and State nodes, but not Town nodes?
Rowland Shaw
@Rowland: Unfortunately not; it will call it on the root type being deserialized but it doesn't appear to work on nested types.
STW
I cheated a little, and used `internal virtual OnXmlDeserialized()` on a base class, and for the moment, override to iterate through the collections on each object. Less than elegant, but at least it works.
Rowland Shaw
I'm trying to get this to work and the Deserialize override never gets called. Am I missing some other part to this?
Anna Lear
A: 

There is an attribute called "OnSerialized" which you can apply to a method that will be called after serialization is over.

Here is the MSDN Link, http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializedattribute.aspx

[OnSerializedAttribute()]
internal void RunThisMethod(StreamingContext context)
{ 
    // this method will be called after serialization .. 
}
Akash Kava
This doesn't get called for XML Serialisation though...
Rowland Shaw
Sorry !! MSDN didnt mention that so I posted here.. I guess you are left with writing your own method after deserialization happens in code.
Akash Kava
Don't feel bad, it's an easy trap to fall into. Unless you know to search for somethink like OnDeserialized XmlSerializer it's hard to find this little piece of critical information
STW
Perfect example of where Google falls down. Reading the docs will tell you that the callbacks and `[Serializable]` have nothing to do with XML Serialization. Yet Google will display search hits for both of them, together on the same page, as though they were related.
John Saunders
A: 

Re: the CustomXmlSerializer. This is a great idea, but in my code the Deserialize(XmlSerializationReader reader) method is never called.

I am using the following to call the CustomXmlSerializer:

using (MemoryStream ms = new MemoryStream())
{
     CustomXmlSerializer xs = new CustomXmlSerializer(obj.GetType());
     xs.Serialize(ms, obj);
     ms.Flush();
     ms.Position = 0;
     result = (MockSerializableObject)xs.Deserialize(ms);
}

Any ideas?

andrewb