Click here to Skip to main content
15,881,757 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.8K   572   11  
This article explains how to write unit tests for MVVM using Catel.
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SavableDataObjectBase.cs" company="Catel development team">
//   Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
//   Abstract class that makes the <see cref="DataObjectBase{T}" /> serializable.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using Catel.Properties;
using Catel.Runtime.Serialization;
using log4net;

#if !SILVERLIGHT
using System.Runtime.Serialization.Formatters.Binary;
#endif

namespace Catel.Data
{
    /// <summary>
    /// Abstract class that makes the <see cref="DataObjectBase{T}"/> serializable.
    /// </summary>
    /// <typeparam name="T">Type that the class should hold (same as the defined type).</typeparam>
#if SILVERLIGHT
    [DataContract]
#else
    [Serializable]
#endif
    public abstract class SavableDataObjectBase<T> : DataObjectBase<T>, ISavableDataObjectBase where T : class
    {
        #region Constructor & destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="SavableDataObjectBase{T}"/> class.
        /// </summary>
        protected SavableDataObjectBase()
        {
            if (ContainsNonSerializableMembers)
            {
            	throw new NotSupportedException(Exceptions.NonSerializablePropertiesNotSupported);
            }
        }

#if !SILVERLIGHT
        /// <summary>
        /// Initializes a new instance of the <see cref="SavableDataObjectBase&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="info">SerializationInfo object, null if this is the first time construction.</param>
        /// <param name="context">StreamingContext object, simple pass a default new StreamingContext() if this is the first time construction.</param>
        /// <remarks>
        /// Call this method, even when constructing the object for the first time (thus not deserializing).
        /// </remarks>
        protected SavableDataObjectBase(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
#endif
        #endregion

        #region Properties
        /// <summary>
        /// Gets the bytes of the current binary serialized data object.
        /// </summary>
        /// <value>The bytes.</value>
        [Browsable(false)]
        [XmlIgnore]
        public byte[] Bytes { get { return ToByteArray(); } }
        #endregion

        #region Methods
        /// <summary>
        /// Serializes the object to and xml object.
        /// </summary>
        /// <returns><see cref="XDocument"/> containing the serialized data.</returns>
        public XDocument ToXml()
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                Save(memoryStream, SerializationMode.Xml);

                memoryStream.Position = 0L;

                using (XmlReader xmlReader = XmlReader.Create(memoryStream))
                {
                    return (XDocument)XNode.ReadFrom(xmlReader);
                }
            }
        }

        /// <summary>
        /// Serializes the object to a byte array.
        /// </summary>
        /// <returns>Byte array containing the serialized data.</returns>
        public byte[] ToByteArray()
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
#if SILVERLIGHT
                Save(memoryStream, SerializationMode.Xml);
#else
                Save(memoryStream, SerializationMode.Binary);
#endif

                return memoryStream.ToArray();
            }
        }

        #region Saving
        /// <summary>
        /// Saves the object to a file using the default formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that will contain the serialized data of this object.</param>
        public void Save(string fileName)
        {
            Save(fileName, Mode);
        }

        /// <summary>
        /// Saves the object to a file using a specific formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that will contain the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param>
        public void Save(string fileName, SerializationMode mode)
        {
            FileInfo fileInfo = new FileInfo(fileName);
            if (!Directory.Exists(fileInfo.DirectoryName))
            {
                Directory.CreateDirectory(fileInfo.DirectoryName);
            }

            using (Stream stream = new FileStream(fileName, FileMode.Create))
            {
                Save(stream, mode);
            }
        }

        /// <summary>
        /// Saves the object to a stream using the default formatting.
        /// </summary>
        /// <param name="stream">Stream that will contain the serialized data of this object.</param>
        public void Save(Stream stream)
        {
            Save(stream, Mode);
        }

        /// <summary>
        /// Saves the object to a stream using a specific formatting.
        /// </summary>
        /// <param name="stream">Stream that will contain the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param>
        public void Save(Stream stream, SerializationMode mode)
        {
            string stopwatchName = string.Format(CultureInfo.CurrentCulture, TraceMessages.SavingObject, GetType().Name, mode);
            Log.StartStopwatchTrace(stopwatchName);

            switch (mode)
            {
#if SILVERLIGHT
                case SerializationMode.JSON:
                    break;
#else
                case SerializationMode.Binary:
                    BinaryFormatter binaryFormatter = SerializationHelper.GetBinarySerializer(false);
                    binaryFormatter.Serialize(stream, this);
                    break;
#endif

                case SerializationMode.Xml:
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.OmitXmlDeclaration = false;
                    settings.Indent = true;

                    using (XmlWriter xmlWriter = XmlWriter.Create(stream, settings))
                    {
                        xmlWriter.WriteStartElement(GetType().Name);
                        ((IXmlSerializable)this).WriteXml(xmlWriter);
                        xmlWriter.WriteEndElement();
                    }

                    break;
            }

            Log.StopStopwatchTrace(stopwatchName);

            ClearIsDirtyOnAllChilds();
        }
        #endregion

        #region Loading
        /// <summary>
        /// Loads the object from a file using binary formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that contains the serialized data of this object.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(string fileName)
        {
            return Load<T>(fileName, false);
        }

        /// <summary>
        /// Loads the object from a file using binary formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that contains the serialized data of this object.</param>
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(string fileName, bool enableRedirects)
        {
            return Load<T>(fileName, enableRedirects);
        }

        /// <summary>
        /// Loads the object from a file using a specific formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that contains the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(string fileName, SerializationMode mode)
        {
            return Load<T>(fileName, mode);
        }

        /// <summary>
        /// Loads the object from a file using a specific formatting.
        /// </summary>
        /// <param name="fileName">Filename of the file that contains the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param>
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(string fileName, SerializationMode mode, bool enableRedirects)
        {
            return Load<T>(fileName, mode, enableRedirects);
        }

        /// <summary>
        /// Loads the object from an XmlDocument object.
        /// </summary>
        /// <param name="xmlDocument">The XML document.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(XDocument xmlDocument)
        {
            return Load<T>(xmlDocument);
        }

        /// <summary>
        /// Loads the object from a stream using binary formatting.
        /// </summary>
        /// <param name="bytes">The byte array.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(byte[] bytes)
        {
            return Load<T>(bytes);
        }

        /// <summary>
        /// Loads the object from a stream.
        /// </summary>
        /// <param name="bytes">The byte array.</param>
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>
        /// Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.
        /// </returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(byte[] bytes, bool enableRedirects)
        {
            return Load<T>(bytes, enableRedirects);
        }

        /// <summary>
        /// Loads the object from a stream using binary formatting.
        /// </summary>
        /// <param name="stream">Stream that contains the serialized data of this object.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(Stream stream)
        {
            return Load<T>(stream);
        }

        /// <summary>
        /// Loads the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(Stream stream, bool enableRedirects)
        {
            return Load<T>(stream, enableRedirects);
        }

        /// <summary>
        /// Loads the object from a stream using a specific formatting.
        /// </summary>
        /// <param name="stream">Stream that contains the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param> 
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        public static T Load(Stream stream, SerializationMode mode)
        {
            return Load<T>(stream, mode);
        }

        /// <summary>
        /// Loads the object from a stream using a specific formatting.
        /// </summary>
        /// <param name="stream">Stream that contains the serialized data of this object.</param>
        /// <param name="mode"><see cref="SerializationMode"/> to use.</param> 
        /// <param name="enableRedirects">if set to <c>true</c>, redirects will be enabled.</param>
        /// <returns>Deserialized instance of the object. If the deserialization fails, <c>null</c> is returned.</returns>
        /// <remarks>
        /// When enableRedirects is enabled, loading will take more time. Only set
        /// the parameter to <c>true</c> when the deserialization without redirects fails.
        /// </remarks>
        public static T Load(Stream stream, SerializationMode mode, bool enableRedirects)
        {
            return Load<T>(stream, mode, enableRedirects);
        }
        #endregion
        #endregion

        #region IXmlSerializable Members
        /// <summary>
        /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
        {
            // As requested by the documentation, we return null
            return null;
        }

        /// <summary>
        /// Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized.</param>
        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            if (reader.IsEmptyElement)
            {
            	return;
            }

            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreComments = true;
            settings.IgnoreProcessingInstructions = true;
            settings.IgnoreWhitespace = true;
            XmlReader newReader = XmlReader.Create(reader, settings);

            if (!newReader.Read())
            {
            	return;
            }

            newReader.MoveToContent();

            XNode objectNode = XNode.ReadFrom(newReader);

            if (objectNode is XContainer)
            {
                foreach (XNode node in ((XContainer)objectNode).Nodes())
                {
                    if (node is XElement)
                    {
                        XElement element = (XElement)node;
                        string propertyName = MapXmlNameToPropertyName(element.Name.LocalName);

                        object value = GetObjectFromXmlNode(node, propertyName, element.Name.LocalName);
                        if (value != null)
                        {
                            _propertyValues[propertyName] = value;
                        }
                    }
                }
            }

            FinishDeserialization();
        }

        /// <summary>
        /// Gets the object from XML node.
        /// </summary>
        /// <param name="node">The node from which to get the object.</param>
        /// <param name="propertyName">Name of the property as known in the class.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <returns>Object or <c>null</c>.</returns>
        private object GetObjectFromXmlNode(XNode node, string propertyName, string xmlName)
        {
            object value = null;

            if (IsPropertyRegistered(propertyName))
            {
                try
                {
#if SILVERLIGHT
                    DataContractSerializer serializer = SerializationHelper.GetDataContractSerializer(GetPropertyData(propertyName).Type, xmlName);
                    value = serializer.ReadObject(node.CreateReader());
#else
                    XmlSerializer serializer = SerializationHelper.GetXmlSerializer(GetPropertyData(propertyName).Type, xmlName);
                    value = serializer.Deserialize(node.CreateReader());
#endif
                }
                catch (Exception)
                {
                    Log.Error(TraceMessages.FailedToDeserializeProperty, propertyName, xmlName);
                }
            }

            return value;
        }

        /// <summary>
        /// Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized.</param>
        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            foreach (KeyValuePair<string, object> propertyValue in _propertyValues)
            {
                PropertyInfo propertyInfo = GetPropertyInfo(propertyValue.Key);
                if (propertyInfo == null)
                {
                	continue;
                }

                XmlIgnoreAttribute xmlIgnoreAttribute;
                if (AttributeHelper.TryGetAttribute(propertyInfo, out xmlIgnoreAttribute))
                {
                	continue;
                }

                if (propertyValue.Value == null)
                {
                	continue;
                }

                string serializationPropertyName = MapPropertyNameToXmlName(propertyValue.Key);

#if SILVERLIGHT
                // Create serializer
                DataContractSerializer serializer = SerializationHelper.GetDataContractSerializer(propertyValue.Value.GetType(), serializationPropertyName);

                // Serialize
                serializer.WriteObject(writer, propertyValue.Value);
#else
                XmlSerializer serializer = SerializationHelper.GetXmlSerializer(propertyValue.Value.GetType(), serializationPropertyName);

                XmlSerializerNamespaces blankNamespaces = new XmlSerializerNamespaces();
                blankNamespaces.Add(string.Empty, string.Empty);

                serializer.Serialize(writer, propertyValue.Value, blankNamespaces);
#endif
            }
        }

        /// <summary>
        /// Maps the name of the XML element to a property name.
        /// </summary>
        /// <param name="xmlName">Name of the XML element.</param>
        /// <returns>Name of the property that represents the xml value.</returns>
        private string MapXmlNameToPropertyName(string xmlName)
        {
            return PropertyDataManager.MapXmlNameToPropertyName(GetType(), xmlName);
        }

        /// <summary>
        /// Maps the name of the property name to an XML name.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>Name of the XML element that represents the property value.</returns>
        private string MapPropertyNameToXmlName(string propertyName)
        {
            return PropertyDataManager.MapPropertyNameToXmlName(GetType(), propertyName);
        }
        #endregion
    }
}

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