Friday, August 7, 2009

XML Serialization and Deserialization made easy.

In my previous post I described the problem with passing a local object as a webmethod argument. I briefly mentioned there're 2 ways to resolve the problem. Well here's the other one.

Using the following extension method, you can now get its XMLDocument representation by typing ".Serialize()"

Getting the same object back is extremely easy as well, simply typing ".Deserialize(OriginalSample)" OriginalSample being an object returned when calling the default constructor the object's default constructor (which you must have for .NET XML Serialization anyways)

This would also help in the webmethod call, local to proxy class conversion problem by serializing the object to XMLDocument and then deserializing into the desired proxy type. For example: "WebService.WebMethod(ObjectA.Clone(Proxy.ObjectA));"

This is the code you need:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Xml;

namespace XMLSerializerHelperClasses
{
/// <summary>
/// This extension helps to easily and intuitively serialize objects to xml and deserialize back to object.
/// </summary>
public static class Extension
{
/// <summary>
/// XML Serilize the given object.
/// </summary>
/// <param name="O">The object to be serialized</param>
/// <returns>An xml representation of the serialized object.</returns>
public static string Serialize(this object O)
{
//Initial preparation for the object serialization. We instantiate the serializer, the memory stream and the text (xml) writer.
XmlSerializer Serializer = GetSerializer(O);


using (MemoryStream MemoryStream = new MemoryStream())
{
//Creates a unicode textwriter to the memory stream.
XmlTextWriter TextWriter = new XmlTextWriter(MemoryStream, Encoding.Unicode);

//Serialize the object into the memory stream using the textwriter.
Serializer.Serialize(TextWriter, O);

//Returns the string representation of the object.
//NOTE: The prefix of the string is some unrecognized character, and must be trimmed for proper functioning.
return Encoding.Unicode.GetString(MemoryStream.ToArray(), 0, (int)MemoryStream.Length).Substring(1);
}
}

/// <summary>
/// XML Serilize the given object.
/// </summary>
/// <param name="O">The object to be serialized</param>
/// <param name="Namespace">The namespace of the object, for SOA</param>
/// <returns>An xml representation of the serialized object.</returns>
public static string Serialize(this object O, string Namespace)
{
//Initial preparation for the object serialization. We instantiate the serializer, the memory stream and the text (xml) writer.
XmlSerializer Serializer = new XmlSerializer(O.GetType(), Namespace);


using (MemoryStream MemoryStream = new MemoryStream())
{
//Creates a unicode textwriter to the memory stream.
XmlTextWriter TextWriter = new XmlTextWriter(MemoryStream, Encoding.Unicode);

//Serialize the object into the memory stream using the textwriter.
Serializer.Serialize(TextWriter, O);

//Returns the string representation of the object.
//NOTE: The prefix of the string is some unrecognized character, and must be trimmed for proper functioning.
return Encoding.Unicode.GetString(MemoryStream.ToArray(), 0, (int)MemoryStream.Length).Substring(1);
}
}


/// <summary>
/// XML Serilize the given object.
/// </summary>
/// <param name="O">The object to be deserialized</param>
/// <param name="TargetObject">The target object for deserialization.</param>
/// <returns>An object representation of the xml serialized object.</returns>
public static T DeSerialize<T>(this string O, T TargetObject)
{
//Initial preparation for the object serialization. We instantiate the serializer, the memory stream and the text (xml) writer.
XmlSerializer Serializer = GetSerializer(TargetObject);

using (MemoryStream MemoryStream = new MemoryStream())
{
//Converts string to stream.
XmlTextWriter TextWriter = new XmlTextWriter(MemoryStream, Encoding.Unicode);
TextWriter.WriteRaw(O);
TextWriter.Flush();
MemoryStream.Seek(0, 0);
StreamReader Reader = new StreamReader(MemoryStream, Encoding.Unicode);
//DeSerialize the unicode reader.
return (T)Serializer.Deserialize(Reader);
}
}


/// <summary>
/// XML Serilize the given object.
/// </summary>
/// <param name="O">The object to be deserialized</param>
/// <param name="TargetObject">The target object for deserialization.</param>
/// <param name="Namespace">The target object's namespace, usually "http://tempury.org"</param>
/// <returns>An object representation of the xml serialized object.</returns>
public static T DeSerialize<T>(this string O, T TargetObject, string Namespace)
{
//Initial preparation for the object serialization. We instantiate the serializer, the memory stream and the text (xml) writer.
XmlSerializer Serializer = new XmlSerializer(TargetObject.GetType(), Namespace);

using (MemoryStream MemoryStream = new MemoryStream())
{
//Converts string to stream.
XmlTextWriter TextWriter = new XmlTextWriter(MemoryStream, Encoding.Unicode);
TextWriter.WriteRaw(O);
TextWriter.Flush();
MemoryStream.Seek(0, 0);
StreamReader Reader = new StreamReader(MemoryStream, Encoding.Unicode);
//DeSerialize the unicode reader.
return (T)Serializer.Deserialize(Reader);
}
}

/// <summary>
///
/// </summary>
/// <param name="O"></param>
/// <returns></returns>
private static XmlSerializer GetSerializer(Object O)
{
//Should not return null.
XmlSerializer Serializer = null;
Serializer = new XmlSerializer(O.GetType());
return Serializer;
}


/// <summary>
/// Clones an object using serialization - deserialization technique.
/// NOTE: All limitations pertaining to xml serialization/deserialization applies here, objects must have default constructor and public members.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="Object"></param>
/// <returns></returns>
public static T Clone<T>(this T Object)
{
return Object.Serialize().DeSerialize(Object);
}
}
}

ASPX Web Method: Same Class, Different Domains

When you try to pass an object as a parameter to a Web Service. For simplicity sake, call this object, object A. At the same time you also have object A referenced from a local binary.

As you try to do the following: WebServiceX.RunWebMethod(A); it bombed. Well... as it turns A and A are different since they come from a different domain. The first A is a proxy object, created when you reference the webservice in Visual Studio, the other A is the actual object whose binary exists locally. C# being strongly typed throws an exception.

The only way to get around this restriction is to make deep copy of object A via. reflection, or... XML serialization and deserialization. In this example I'll cover the first method, via. reflection.

So now after implementing the following code snippet, you want to run the webservice this way: WebServiceX.RunWebMethod((AnotherDomain.A) A.CopyTo(AnotherDomain.A))


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ReflectionHelpers
{
public static class DeepCopyUtils
{


        /// <summary>
        /// We can't use generic with copy to but this wrapper allows it to wrap the logic around in a way that'll allow for type casting.
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TTarget"></typeparam>
        /// <param name="Source"></param>
        /// <param name="Target"></param>
        /// <returns></returns>
        public static TTarget CopyTo<TSource, TTarget>(this TSource Source, TTarget Target)
        {
            return (TTarget) Source.CopyTo_Base(Target);
        }

        /// <summary>
        /// Make a deep copy of source to target type of object.
        /// NOTE: Unfortunately the 2nd argument cannot be generic as that would cause problems when creating linq anonymous type.
        /// </summary>
        /// <param name="Source">The source</param>
        /// <param name="Target">The target object.</param>
        /// <returns>An object of type Target.</returns>
        private static Object CopyTo_Base(this Object Source, Object Target)
        {
            //The requisite null case scenerio. 
            if (Source == null)
                return null; //If the source is null, then target will have a null value also, simple.
            else if (Target == null && Source != null)
                Target = Source; //If the target is null, but source is not null, then we want to  return the shallow copy of the target, by letting this function falls through.


            Type BType = Target.GetType();
            Type AType = Source.GetType();
            //Members or properties.
            MemberInfo[] BProperties = ((MemberInfo[])BType.GetProperties()).Union((MemberInfo[])BType.GetFields()).ToArray();
            MemberInfo[] AProperties = ((MemberInfo[])AType.GetProperties()).Union((MemberInfo[])AType.GetFields()).ToArray();
            
            //Creates a new target object.
            Object Result;
            if (Target.GetType().GetConstructor(new Type[] { }) != null)
                Result = Target.GetType().GetConstructor(new Type[] { }).Invoke(new Object[0]);            //The requisite null case scenerio. 
            else //No default constructor, it may be a semi-primitive eg. string or array, so we can do nothing more
                return Source.Clone();
            

            //Find all similar property names between Source and Target, put them to a list, along with the content of the Source.
            var x = from b in BProperties
                    join a in AProperties 
                    on new{
                        Name = b.Name,
                        TypeName = b.GetMemberType().Name
                    }
                    equals
                    new {
                        Name = a.Name,
                        TypeName = a.GetMemberType().Name
                    }
                    select new
                    {                        
                        //The name of the property.
                        ID = a.Name,
                        //3 cases for content: 1) Array, 2) Struct Type and 3) Value Type.
                        Content = a.GetType().IsArray ? ((System.Array)a.GetValue(Source)).CopyTo((System.Array)b.GetValue(Target)) :
                                    !a.GetType().IsValueType ? a.GetValue(Source).CopyTo(b.GetValue(Target)) :
                                    a.GetValue(Source)
                                  
                    };


            Dictionary<string, object> MergeResult = new Dictionary<string, object>();

            foreach (var y in x)
            {
                MergeResult[y.ID] = y.Content;
            }

            //Copy if the same name exists in the dictionary as in the result.
            foreach (var z in ((MemberInfo[]) Result.GetType().GetProperties()).Union((MemberInfo[]) Result.GetType().GetFields()))
            {
                if (MergeResult.Keys.Contains(z.Name))
                {
                    if(z is PropertyInfo)
                        ((PropertyInfo) z).SetValue(Result, MergeResult[z.Name], null);
                    else if (z is FieldInfo)
                        ((FieldInfo)z).SetValue(Result, MergeResult[z.Name]);
                }
            }

            return Result;
        }
/// <summary> /// Retrieves the value of a member, if the member is of type property or field. /// NOTE: Only works for field and properties, all else returns null. /// </summary> /// <param name="Member"></param> /// <returns></returns> public static object GetValue(this MemberInfo Member, object ObjectInstance) { if (Member.MemberType == MemberTypes.Property) return ((PropertyInfo)Member).GetValue(ObjectInstance, new object[] { }); else if (Member.MemberType == MemberTypes.Field) return ((FieldInfo)Member).GetValue(ObjectInstance); return null; } /// <summary> /// Return the property type of a field, properties or method (return type). /// eg. int a = 0; will return "int". /// </summary> /// <param name="Member">The name of the member to look for.</param> /// <returns>The type of the field/property or method.</returns> public static Type GetMemberType(this MemberInfo Member) { if (Member.MemberType == MemberTypes.Property) return ((PropertyInfo)Member).PropertyType; else if (Member.MemberType == MemberTypes.Field) return ((FieldInfo)Member).FieldType; else if (Member.MemberType == MemberTypes.Method) return ((MethodInfo)Member).ReturnType; return null; } /// <summary> /// Shallow copy one array to the next. /// </summary> /// <param name="Source"></param> /// <param name="Target"></param> /// <returns></returns> public static System.Array CopyTo(this System.Array Source, System.Array Target) { //The requisite null case scenerio. if (Source == null) return null; //If the source is null, then target will have a null value also, simple. else if (Target == null && Source != null) Target = Source; //If the target is null, but source is not null, then we want to return the shallow copy of the target, by letting this function falls through. LinkedList<object> CopyResult = new LinkedList<object>(); for (int i = 0; i < Min(Source.GetLength(0), Target.GetLength(0)); i++) { CopyResult.AddLast(Source.GetValue(i).CopyTo(Target.GetValue(i))); } return (System.Array)CopyResult.ToArray(); } public static int Min(int a, int b) { if (a <= b) return a; else return b; } } }