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