// --------------------------------------------------------------------------------------------------------------------
// <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<T>"/> 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
}
}