Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you find out when you've been loaded via XML Serialization?

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>
                    </Office>";

        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;
    }
}
like image 379
Rowland Shaw Avatar asked Aug 12 '09 14:08

Rowland Shaw


People also ask

What is XML serialization?

XML serialization is the process of converting XML data from its representation in the XQuery and XPath data model, which is the hierarchical format it has in a Db2® database, to the serialized string format that it has in an application.

What is XML serialization in Java?

Serialization of Java Objects to XML can be done using XMLEncoder, XMLDecoder. Java Object Serialization feature was introduced in JDK 1.1. Serialization transforms a Java object or graph of Java object into an array of bytes which can be stored in a file or transmitted over a network.

What is XML serialization and deserialization?

Serialization is a process by which an object's state is transformed in some serial data format, such as XML or binary format. Deserialization, on the other hand, is used to convert the byte of data, such as XML or binary data, to object type.

Is a Namespsace for XML serialization?

XML namespaces provide a way to qualify the names of XML elements and attributes in XML documents. A qualified name consists of a prefix and a local name, separated by a colon. The prefix functions only as a placeholder; it is mapped to a URI that specifies a namespace.


2 Answers

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;
        }
    }
}
like image 99
STW Avatar answered Oct 02 '22 18:10

STW


The accepted solution didn't quite work for me. The overridden Deserialize() method never got called. I believe this is because that method is not public and is therefore called by one (or more) of the public Deserialize() methods, but not all of them.

Here's an implementation that works by method hiding and makes use of the existing IDeserializationCallback interface so any deserialization using non-xml methods can still trigger the OnDeserialization() method of that interface. It also uses reflection to traverse child properties to see if they also implement IDeserializationCallback and calls them accordingly.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;

namespace Xml.Serialization
{
    class XmlCallbackSerializer : XmlSerializer
    {
        public XmlCallbackSerializer(Type type) : base(type)
        {
        }

        public XmlCallbackSerializer(XmlTypeMapping xmlTypeMapping) : base(xmlTypeMapping)
        {
        }

        public XmlCallbackSerializer(Type type, string defaultNamespace) : base(type, defaultNamespace)
        {
        }

        public XmlCallbackSerializer(Type type, Type[] extraTypes) : base(type, extraTypes)
        {
        }

        public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides) : base(type, overrides)
        {
        }

        public XmlCallbackSerializer(Type type, XmlRootAttribute root) : base(type, root)
        {
        }

        public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes,
            XmlRootAttribute root, string defaultNamespace) : base(type, overrides, extraTypes, root, defaultNamespace)
        {
        }

        public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes,
            XmlRootAttribute root, string defaultNamespace, string location)
            : base(type, overrides, extraTypes, root, defaultNamespace, location)
        {
        }

        public new object Deserialize(Stream stream)
        {
            var result = base.Deserialize(stream);

            CheckForDeserializationCallbacks(result);

            return result;
        }

        public new object Deserialize(TextReader textReader)
        {
            var result = base.Deserialize(textReader);

            CheckForDeserializationCallbacks(result);

            return result;
        }

        public new object Deserialize(XmlReader xmlReader)
        {
            var result = base.Deserialize(xmlReader);

            CheckForDeserializationCallbacks(result);

            return result;
        }

        public new object Deserialize(XmlSerializationReader reader)
        {
            var result = base.Deserialize(reader);

            CheckForDeserializationCallbacks(result);

            return result;
        }

        public new object Deserialize(XmlReader xmlReader, string encodingStyle)
        {
            var result = base.Deserialize(xmlReader, encodingStyle);

            CheckForDeserializationCallbacks(result);

            return result;
        }

        public new object Deserialize(XmlReader xmlReader, XmlDeserializationEvents events)
        {
            var result = base.Deserialize(xmlReader, events);

            CheckForDeserializationCallbacks(result);

            return result;
        }

        public new object Deserialize(XmlReader xmlReader, string encodingStyle, XmlDeserializationEvents events)
        {
            var result = base.Deserialize(xmlReader, encodingStyle, events);

            CheckForDeserializationCallbacks(result);

            return result;
        }

        private void CheckForDeserializationCallbacks(object deserializedObject)
        {
            var deserializationCallback = deserializedObject as IDeserializationCallback;

            if (deserializationCallback != null)
            {
                deserializationCallback.OnDeserialization(this);
            }

            var properties = deserializedObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (var propertyInfo in properties)
            {
                if (propertyInfo.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null)
                {
                    var collection = propertyInfo.GetValue(deserializedObject) as IEnumerable;

                    if (collection != null)
                    {
                        foreach (var item in collection)
                        {
                            CheckForDeserializationCallbacks(item);
                        }
                    }
                }
                else
                {
                    CheckForDeserializationCallbacks(propertyInfo.GetValue(deserializedObject));
                }
            }
        }
    }
}
like image 22
HotN Avatar answered Oct 02 '22 18:10

HotN