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

Solution 1:

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;
        }
    }
}

Solution 2:

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));
                }
            }
        }
    }
}

Solution 3:

I tried the solution provided by abatishchev but as pointed out by the comments below his answer, the Deserialize method in the custom serializer never seems to get called.

I was able to get this working by overloading all the different Deserialize overloads I would need so that it would always call the custom method.

protected object Deserialize(System.IO.StringReader reader)
{
    var result = base.Deserialize(reader);

    CallBack(result);

    return result;
}

protected object Deserialize(System.IO.TextReader reader)
{
    var result = base.Deserialize(reader);

    CallBack(result);

    return result;
}

protected object Deserialize(System.Xml.XmlReader reader)
{
    var result = base.Deserialize(reader);

    CallBack(result);

    return result;
}

protected object Deserialize(System.IO.Stream stream)
{
    var result = base.Deserialize(stream);

    CallBack(result);

    return result;
}

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

This way I actually see the Deserialize method getting called.

Solution 4:

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.

Solution 5:

After wasting some time with first answer, I adopted code from HotN's post, except for CheckForDeserializationCallbacks:

private static void ProcessOnDeserialize(object _result) {
  var type = _result != null ? _result.GetType() : null;
  var methods = type != null ? type.GetMethods().Where(_ => _.GetCustomAttributes(true).Any(_m => _m is OnDeserializedAttribute)) : null;
  if (methods != null) {
    foreach (var mi in methods) {
      mi.Invoke(_result, null);
    }
  }
  var properties = type != null ? type.GetProperties().Where(_ => _.GetCustomAttributes(true).Any(_m => _m is XmlElementAttribute || _m is XmlAttributeAttribute)) : null;
  if (properties != null) {
    foreach (var prop in properties) {
      var obj = prop.GetValue(_result, null);
      var enumeration = obj as IEnumerable;
      if (obj is IEnumerable) {
        foreach (var item in enumeration) {
          ProcessOnDeserialize(item);
        }
      } else {
        ProcessOnDeserialize(obj);
      }
    }
  }
}

This allows using of standard [OnDeserialized].

UPD. Updated post for recursive walk on object tree.