Click here to Skip to main content
15,885,216 members
Articles / Desktop Programming / WPF

Catel - Part 4 of n: Unit testing with Catel

Rate me:
Please Sign up or sign in to vote.
4.55/5 (10 votes)
28 Jan 2011CPOL11 min read 48.9K   572   11  
This article explains how to write unit tests for MVVM using Catel.
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializationHelper.cs" company="Catel development team">
//   Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
//   Class that makes serialization much easier and safer.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using Catel.Properties;
using Catel.Reflection;
using log4net;

#if SILVERLIGHT
using System.IO.IsolatedStorage;
#else
using System.IO;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
#endif

namespace Catel.Runtime.Serialization
{
#if SILVERLIGHT
    /// <summary>
    /// Class that makes serialization much easier and safer.
    /// </summary>
    public static class SerializationHelper
    {
        #region Variables
        /// <summary>
        /// The <see cref="ILog">log</see> object.
        /// </summary>
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Cache for the <see cref="DataContractSerializer"/> per name.
        /// </summary>
        private static readonly Dictionary<string, DataContractSerializer> _serializers = new Dictionary<string, DataContractSerializer>();
        #endregion

        #region Methods
        /// <summary>
        /// Gets the Data Contract serializer for a specific type. This method caches serializers so the
        /// performance can be improved when a serializer is used more than once.
        /// </summary>
        /// <param name="type">The type to get the xml serializer for.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <returns><see cref="DataContractSerializer"/> for the given type.</returns>
        public static DataContractSerializer GetDataContractSerializer(Type type, string xmlName)
        {
            return GetDataContractSerializer(type, xmlName, true);
        }

        /// <summary>
        /// Gets the Data Contract serializer for a specific type. This method caches serializers so the
        /// performance can be improved when a serializer is used more than once.
        /// </summary>
        /// <param name="type">The type to get the xml serializer for.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <param name="loadFromCache">if set to <c>true</c>, the serializer is retrieved from the cache if possible.</param>
        /// <returns>
        /// 	<see cref="DataContractSerializer"/> for the given type.
        /// </returns>
        public static DataContractSerializer GetDataContractSerializer(Type type, string xmlName, bool loadFromCache)
        {
            return GetDataContractSerializer(type, xmlName, null, loadFromCache);
        }

        /// <summary>
        /// Gets the Data Contract serializer for a specific type. This method caches serializers so the
        /// performance can be improved when a serializer is used more than once.
        /// </summary>
        /// <param name="type">The type to get the xml serializer for.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <param name="obj">The object to create the serializer for. When the object is not <c>null</c>, the types that are
        /// a child object of this object are added to the known types of the serializer.</param>
        /// <returns>
        /// 	<see cref="DataContractSerializer"/> for the given type.
        /// </returns>
        public static DataContractSerializer GetDataContractSerializer(Type type, string xmlName, object obj)
        {
            return GetDataContractSerializer(type, xmlName, obj, true);
        }

        /// <summary>
        /// Gets the Data Contract serializer for a specific type. This method caches serializers so the
        /// performance can be improved when a serializer is used more than once.
        /// </summary>
        /// <param name="type">The type to get the xml serializer for.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <param name="obj">The object to create the serializer for. When the object is not <c>null</c>, the types that are
        /// a child object of this object are added to the known types of the serializer.</param>
        /// <param name="loadFromCache">if set to <c>true</c>, the serializer is retrieved from the cache if possible.</param>
        /// <returns>
        /// 	<see cref="DataContractSerializer"/> for the given type.
        /// </returns>
        public static DataContractSerializer GetDataContractSerializer(Type type, string xmlName, object obj, bool loadFromCache)
        {
            string key = string.Empty;

            if (loadFromCache)
            {
                key = string.Format("{0}|{1}", type.Name, xmlName);
                if (_serializers.ContainsKey(key))
                {
                    return _serializers[key];
                }
            }

            Log.Debug(TraceMessages.GettingKnownTypesForXmlSerialization, type.Name);

            Type[] knownTypes = GetKnownTypesForInstance(obj);

            Log.Debug(TraceMessages.FoundKnownTypesForXmlSerialization, knownTypes.Length);

            DataContractSerializer xmlSerializer = new DataContractSerializer(type, xmlName, string.Empty, knownTypes);

            if (!string.IsNullOrEmpty(key))
            {
                _serializers.Add(key, xmlSerializer);
            }

            return xmlSerializer;
        }

        /// <summary>
        /// Gets the known types for a specific object instance.
        /// </summary>
        /// <param name="obj">The object to retrieve the known types for.</param>
        /// <returns>
        /// Array of <see cref="Type"/> that are found in the object instance.
        /// </returns>
        private static Type[] GetKnownTypesForInstance(object obj)
        {
            return GetKnownTypesForInstance(obj, new List<Type>());
        }

        /// <summary>
        /// Gets the known types for a specific object instance.
        /// </summary>
        /// <param name="obj">The object to retrieve the known types for.</param>
        /// <param name="knownTypeList">The known type list.</param>
        /// <returns>
        /// Array of <see cref="Type"/> that are found in the object instance.
        /// </returns>
        /// <remarks>
        /// This code originally can be found at:
        /// http://geekswithblogs.net/SoftwareDoneRight/archive/2008/01/17/xmlserialization---solving-the-quottype-not-statically-knownquot-exception.aspx.
        /// </remarks>
        private static Type[] GetKnownTypesForInstance(object obj, List<Type> knownTypeList)
        {
            if (obj == null)
            {
                return knownTypeList.ToArray();
            }

            Type objectType = obj.GetType();
            GetKnownTypes(objectType, knownTypeList);

            if (objectType == typeof(List<KeyValuePair<string, object>>))
            {
                foreach (var keyValuePair in ((List<KeyValuePair<string, object>>)obj))
                {
                    GetKnownTypesForInstance(keyValuePair.Value, knownTypeList);
                }
            }
            else if (!objectType.FullName.StartsWith("System."))
            {
                FieldInfo[] fields = objectType.GetFields(BindingFlags.Instance | BindingFlags.Public);
                foreach (FieldInfo field in fields)
                {
                    try
                    {
                        object value = field.GetValue(obj);
                        GetKnownTypes(value == null ? field.FieldType : value.GetType(), knownTypeList);
                    }
                    catch (Exception)
                    {
                        Log.Warn(TraceMessages.FailedToGetFieldValue, field.Name, objectType.Name);
                    }
                }

                PropertyInfo[] properties = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
                foreach (PropertyInfo property in properties)
                {
                    try
                    {
                        object value = property.GetValue(obj, null);
                        GetKnownTypes(value == null ? property.PropertyType : value.GetType(), knownTypeList);
                    }
                    catch (Exception)
                    {
                        Log.Warn(TraceMessages.FailedToGetPropertyValue, property.Name, objectType.Name);
                    }
                }
            }

            return knownTypeList.ToArray();
        }

        /// <summary>
        /// Gets the known types inside the specific type.
        /// </summary>
        /// <param name="type">The type to retrieve the known types for.</param>
        /// <param name="knownTypeList">The known type list.</param>
        /// <returns>
        /// Array of <see cref="Type"/> that are found in the object type.
        /// </returns>
        /// <remarks>
        /// This code originally can be found at:
        /// http://geekswithblogs.net/SoftwareDoneRight/archive/2008/01/17/xmlserialization---solving-the-quottype-not-statically-knownquot-exception.aspx.
        /// </remarks>
        private static Type[] GetKnownTypes(Type type, List<Type> knownTypeList)
        {
            if (type == null)
            {
                return knownTypeList.ToArray();
            }

            // Ignore .NET or existing types
            if (type.FullName.StartsWith("System.") || knownTypeList.Contains(type))
            {
                return knownTypeList.ToArray();
            }

            knownTypeList.Add(type);

            // Fields
            FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (FieldInfo field in fields)
            {
                GetKnownTypes(field.FieldType, knownTypeList);
            }

            // Properties
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (PropertyInfo property in properties)
            {
                GetKnownTypes(property.PropertyType, knownTypeList);
            }

            // If this isn't the base type, check that as well
            if (type.BaseType != null)
            {
                GetKnownTypes(type.BaseType, knownTypeList);
            }

            return knownTypeList.ToArray();
        }

        /// <summary>
        /// Serializes the XML.
        /// </summary>
        /// <param name="isolatedStorageFileStream">The isolated storage file stream.</param>
        /// <param name="obj">The object.</param>
        /// <returns>
        /// 	<c>true</c> if the object is serialized to xml successfully; otherwise <c>false</c>.
        /// </returns>
        public static bool SerializeXml(IsolatedStorageFileStream isolatedStorageFileStream, object obj)
        {
            bool succeeded = false;

            try
            {
                if (obj != null)
                {
                    DataContractSerializer serializer = new DataContractSerializer(obj.GetType());
                    serializer.WriteObject(isolatedStorageFileStream, obj);

                    succeeded = true;
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, TraceMessages.FailedToSerializeObject);
            }

            return succeeded;
        }

        /// <summary>
        /// Deserializes the specified file name to an object.
        /// </summary>
        /// <typeparam name="T">Type of the object that is contained in the file.</typeparam>
        /// <param name="isolatedStorageFileStream">The isolated storage file stream.</param>
        /// <returns>
        /// Deserialized type or <c>null</c> if not successful.
        /// </returns>
        public static T DeserializeXml<T>(IsolatedStorageFileStream isolatedStorageFileStream) where T : class, new()
        {
            T result = null;

            try
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(T));
                result = (T)serializer.ReadObject(isolatedStorageFileStream);
            }
            catch (Exception ex)
            {
                Log.Error(ex, TraceMessages.FailedToDeserializeObject);
            }

            if (result == null)
            {
                result = new T();
            }

            return result;
        }
        #endregion
    }
#else
    /// <summary>
    /// Class that makes serialization much easier and safer.
    /// </summary>
    public static class SerializationHelper
    {
    #region Variables
        /// <summary>
        /// The <see cref="ILog">log</see> object.
        /// </summary>
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Cache for the <see cref="XmlSerializer"/> per name.
        /// </summary>
        private static readonly Dictionary<string, XmlSerializer> _serializers = new Dictionary<string, XmlSerializer>();
    #endregion

    #region Methods
        /// <summary>
        /// Gets the XML serializer for a specific type. This method caches serializers so the
        /// performance can be improved when a serializer is used more than once.
        /// </summary>
        /// <param name="type">The type to get the xml serializer for.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <returns><see cref="XmlSerializer"/> for the given type.</returns>
        public static XmlSerializer GetXmlSerializer(Type type, string xmlName)
        {
            string key = string.Format("{0}|{1}", type.Name, xmlName);

            if (_serializers.ContainsKey(key))
            {
            	return _serializers[key];
            }

            XmlSerializer xmlSerializer = new XmlSerializer(type, new XmlRootAttribute(xmlName));

            _serializers.Add(key, xmlSerializer);

            return xmlSerializer;
        }

        /// <summary>
        /// Gets the <see cref="BinaryFormatter"/> for binary (de)serialization.
        /// </summary>
        /// <param name="supportRedirects">if set to <c>true</c>, redirects of types are supported. This is substantially slower.</param>
        /// <returns><see cref="BinaryFormatter"/> with the requested options.</returns>
        public static BinaryFormatter GetBinarySerializer(bool supportRedirects)
        {
            Log.Debug(TraceMessages.CreatingBinarySerializer);

            BinaryFormatter formatter = new BinaryFormatter();
            formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
            formatter.FilterLevel = TypeFilterLevel.Full;
            formatter.TypeFormat = FormatterTypeStyle.TypesWhenNeeded;

            if (supportRedirects)
            {
                formatter.Binder = new RedirectDeserializationBinder();
            }

            Log.Debug(TraceMessages.CreatedBinarySerializer);

            return formatter;
        }

        /// <summary>
        /// Retrieves a string from a SerializationInfo object.
        /// </summary>
        /// <param name="info">SerializationInfo object.</param>
        /// <param name="name">Name of the value to retrieve.</param>
        /// <param name="defaultValue">Default value when value does not exist.</param>
        /// <returns>String value.</returns>
        public static string GetString(SerializationInfo info, string name, string defaultValue)
        {
            return GetObject(info, name, defaultValue);
        }

        /// <summary>
        /// Retrieves an integer from a SerializationInfo object.
        /// </summary>
        /// <param name="info">SerializationInfo object</param>
        /// <param name="name">Name of the value to retrieve.</param>
        /// <param name="defaultValue">Default value when value does not exist.</param>
        /// <returns>Integer value.</returns>
        public static int GetInt(SerializationInfo info, string name, int defaultValue)
        {
            return GetObject(info, name, defaultValue);
        }

        /// <summary>
        /// Retrieves a boolean from a SerializationInfo object.
        /// </summary>
        /// <param name="info">SerializationInfo object.</param>
        /// <param name="name">Name of the value to retrieve.</param>
        /// <param name="defaultValue">Default value when value does not exist.</param>
        /// <returns>Boolean value.</returns>
        public static bool GetBool(SerializationInfo info, string name, bool defaultValue)
        {
            return GetObject(info, name, defaultValue);
        }

        /// <summary>
        /// Retrieves an object from a SerializationInfo object.
        /// </summary>
        /// <typeparam name="T">Type of the value to read from the serialization information.</typeparam>
        /// <param name="info">SerializationInfo object.</param>
        /// <param name="name">Name of the value to retrieve.</param>
        /// <param name="defaultValue">Default value when value does not exist.</param>
        /// <returns>object value.</returns>
        public static T GetObject<T>(SerializationInfo info, string name, T defaultValue)
        {
            Type type = typeof(T);
            object value = GetObject(info, name, type, defaultValue);
            return ((value != null) && (value is T)) ? (T)value : defaultValue;
        }

        /// <summary>
        /// Retrieves an object from a SerializationInfo object.
        /// </summary>
        /// <param name="info">SerializationInfo object.</param>
        /// <param name="name">Name of the value to retrieve.</param>
        /// <param name="type">Type of the object to retrieve.</param>
        /// <param name="defaultValue">Default value when value does not exist.</param>
        /// <returns>object value.</returns>
        public static object GetObject(SerializationInfo info, string name, Type type, object defaultValue)
        {
            try
            {
                object obj = info.GetValue(name, type);
                return obj ?? defaultValue;
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// Serializes the XML.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="obj">The object.</param>
        /// <returns><c>true</c> if the object is serialized to xml successfully; otherwise <c>false</c>.</returns>
        public static bool SerializeXml(string fileName, object obj)
        {
            // Failed by default
            bool succeeded = false;

            try
            {
                if (obj != null)
                {
                    Directory.CreateDirectory(IO.Path.GetParentDirectory(fileName));

                    using (FileStream fs = File.Create(fileName))
                    {
                        XmlSerializer xs = new XmlSerializer(obj.GetType());
                        xs.Serialize(fs, obj);

                        succeeded = true;
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, TraceMessages.FailedToSerializeObject);
            }

            return succeeded;
        }

        /// <summary>
        /// Deserializes the specified file name to an object.
        /// </summary>
        /// <typeparam name="T">Type of the object that is contained in the file.</typeparam>
        /// <param name="fileName">Name of the file.</param>
        /// <returns>Deserialized type or <c>null</c> if not successful.</returns>
        public static T DeserializeXml<T>(string fileName) where T : class, new()
        {
            return DeserializeXml<T>(fileName, string.Empty);
        }

        /// <summary>
        /// Deserializes the specified file name to an object. If the object cannot be serialized by the
        /// full path, the relative path is used as a last resort.
        /// </summary>
        /// <typeparam name="T">Type of the object that is contained in the file.</typeparam>
        /// <param name="fullFileName">Full name of the file.</param>
        /// <param name="relativeFileName">Name of the relative file.</param>
        /// <returns>
        /// Deserialized type or <c>null</c> if not successful.
        /// </returns>
        public static T DeserializeXml<T>(string fullFileName, string relativeFileName) where T : class, new()
        {
            T result = null;

            try
            {
                Stream stream = File.Open(fullFileName, FileMode.Open, FileAccess.Read);

                XmlSerializer xs = new XmlSerializer(typeof(T));

                result = (T)xs.Deserialize(stream);

                stream.Dispose();
            }
            catch (Exception ex)
            {
                Log.Error(ex, TraceMessages.FailedToDeserializeObject);
            }

            return result ?? new T();
        }
    #endregion
    }
#endif
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions