Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

Catel - Part 4 of n: Unit testing with Catel

, 28 Jan 2011
This article explains how to write unit tests for MVVM using Catel.
Catel-04_01-unittesting.zip
src
Catel.Articles.04 - Unit testing
Models
Properties
Settings.settings
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
ViewModels
Windows
Catel.Articles.04 - Unit testing.Test
Models
Properties
UI
ViewModels
Catel.Articles.Base
Data
Attributes
Properties
Settings.settings
Resources
Images
CatenaLogic.png
Preview.png
Run.png
ShowCode.png
UI
Controls
Helpers
ViewModels
Windows
Catel.Core
Attributes
ClassDiagrams
DataObjectBase.cd
SavableDataObjectBase.cd
Collections
Helpers
ComponentModel
Data
Attributes
Exceptions
Interfaces
Diagnostics
Extensions
Helpers
Exceptions
Helpers
IO
Exceptions
IoC
LLBLGen
Log4net
Appender
Extensions
Helpers
MVVM
Commands
Interfaces
Exceptions
Services
EventArgs
Exceptions
Interfaces
ViewModels
Attributes
Interfaces
Properties
Reflection
Exceptions
Extensions
Helpers
Runtime
Serialization
Attributes
Helpers
Security
Cryptography
Helpers
Catel.Examples.Models
Properties
Catel.Examples.PersonApplication
Properties
Settings.settings
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
ViewModels
Windows
Catel.Examples.Silverlight
Properties
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
Pages
ViewModels
Windows
Catel.Examples.Silverlight.Web
Catel.Examples.Silverlight.Web.csproj.user
ClientBin
Properties
Catel.FxCop
Catel.Silverlight
Diagnostics
log4net
Core
MVVM
Commands
Services
ViewModels
Properties
Catel.Core
Catel.Windows
Reflection
Themes
Generic
Assets
Old
Windows
Controls
Data
Converters
Helpers
Helpers
Catel.Silverlight.Test
Properties
Catel.Silverlight.Test.Web
Catel.Silverlight.Test.Web.csproj.user
ClientBin
Properties
Catel.snk
Catel.Templates.WpfApplication
Properties
Settings.settings
UI
Controls
ViewModels
Windows
Catel.Templates.WpfItemTemplates
Properties
UI
Controls
ViewModels
Windows
Catel.Test
Collections
Convert
Data
Helpers
IO
MVVM
UI
ViewModels
Properties
Reflection
Runtime
Serialization
Security
Cryptography
Test References
Catel.Windows.accessor
Windows
Data
Converters
Catel.vsmdi
Catel.Windows
ClassDiagrams
ViewModelBase.cd
Collections
Extensions
Helpers
MVVM
Commands
Services
Test
UI
ViewModels
Properties
Settings.settings
Resources
Images
Add.png
ClearOutput.png
Edit.png
Error.png
Loading.gif
Preview.png
Remove.png
Save.png
TipOfTheDay.png
Warning.png
Themes
Aero
ExpressionDark
Assets
Generic
Assets
Controls
Jetpack
Assets
background.png
Old
SunnyOrange
Assets
Windows
Controls
Extensions
LinkLabel
StackGrid
Data
Converters
Helpers
Documents
Extensions
Extensions
Helpers
Input
Markup
Media
Effects
EmptyEffect
EmptyEffect.fx
EmptyEffect.ps
EmptyEffect.fx
GrayscaleEffect
GrayscaleEffect.fx
GrayscaleEffect.ps
Extensions
Imaging
Extensions
Windows
DataWindow
TipOfTheDay
Local.testsettings
Settings.StyleCop
TraceAndTestImpact.testsettings
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DataObjectBase.cs" company="Catel development team">
//   Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
//   Enumeration containing all the available serialization modes for the <see cref="DataObjectBase{T}" /> class.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

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

#if SILVERLIGHT
#else
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;
#endif

namespace Catel.Data
{
    #region Enums
    /// <summary>
    /// Enumeration containing all the available serialization modes for the <see cref="DataObjectBase{T}"/> class.
    /// </summary>
    public enum SerializationMode
    {
#if SILVERLIGHT
        /// <summary>
        /// Serialize using the <see cref="DataContractJsonSerializer" />.
        /// </summary>
        JSON,

        /// <summary>
        /// Serialize using the <see cref="DataContractSerializer" />.
        /// </summary>
#else
        /// <summary>
        /// Serialize using the <see cref="BinaryFormatter"/>.
        /// </summary>
        Binary,

        /// <summary>
        /// Serialize using the <see cref="XmlSerializer"/>.
        /// </summary>
#endif
        Xml
    }
    #endregion

    #region Delegates & event args
    /// <summary>
    /// Property changed event args that are used when a property has changed. The event arguments contains both
    /// the original sender as the current sender of the event.
    /// </summary>
    public class PropertyChangedWithSenderEventArgs : PropertyChangedEventArgs
    {
        #region Constructor & destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyChangedWithSenderEventArgs"/> class.
        /// </summary>
        /// <param name="originalSender">The original sender.</param>
        /// <param name="latestSender">The latest sender.</param>
        /// <param name="eventArgs">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        public PropertyChangedWithSenderEventArgs(object originalSender, object latestSender, PropertyChangedEventArgs eventArgs)
            : this(originalSender, latestSender, eventArgs.PropertyName)
        {
            // Call overload
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyChangedWithSenderEventArgs"/> class.
        /// </summary>
        /// <param name="originalSender">The original sender.</param>
        /// <param name="latestSender">The latest sender.</param>
        /// <param name="propertyName">Name of the property.</param>
        public PropertyChangedWithSenderEventArgs(object originalSender, object latestSender, string propertyName)
            : base(propertyName)
        {
            // Store values
            OriginalSender = originalSender;
            LatestSender = latestSender;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the original sender.
        /// </summary>
        /// <value>The original sender.</value>
        public object OriginalSender { get; private set; }

        /// <summary>
        /// Gets the latest sender.
        /// </summary>
        /// <value>The latest sender.</value>
        public object LatestSender { get; private set; }
        #endregion
    }
    #endregion

    /// <summary>
    /// Abstract class that serves as a base class for serializable objects.
    /// </summary>
#if SILVERLIGHT
    [DataContract]
#else
    [Serializable]
#endif
    public abstract class DataObjectBase : IDataObjectBase
    {
        #region Internal classes
        /// <summary>
        /// Class containing backup information.
        /// </summary>
        private class BackupData
        {
            #region Constants
            /// <summary>
            /// The name of the <see cref="DataObjectBase.IsDirty"/> property.
            /// </summary>
            private const string IsDirty = "IsDirty";
            #endregion

            #region Variables
            /// <summary>
            /// The <see cref="DataObjectBase"/> object that this backup is created for.
            /// </summary>
            private readonly DataObjectBase _object;

            /// <summary>
            /// Backup of the property values.
            /// </summary>
            private Dictionary<string, object> _propertyValuesBackup;

            /// <summary>
            /// Backup of the object values.
            /// </summary>
            private Dictionary<string, object> _objectValuesBackup;
            #endregion

            #region Constructor & destructor
            /// <summary>
            /// Initializes a new instance of the <see cref="DataObjectBase.BackupData"/> class.
            /// </summary>
            /// <param name="obj">Object to backup.</param>
            public BackupData(DataObjectBase obj)
            {
                _object = obj;

                CreateBackup();
            }
            #endregion

            #region Properties
            #endregion

            #region Methods
            /// <summary>
            /// Creates a backup of the object property values.
            /// </summary>
            private void CreateBackup()
            {
                using (MemoryStream stream = new MemoryStream())
                {
#if SILVERLIGHT
                    // Xml backup, create serializer without using the cache since the dictionary is used for every object, and
                    // we need a "this" object specific dictionary.
                    var objectToSerialize = _object.ConvertDictionaryToListAndExcludeNonSerializableObjects(_object._propertyValues);
                    DataContractSerializer serializer = SerializationHelper.GetDataContractSerializer(objectToSerialize.GetType(), 
                        "backup", objectToSerialize, false);
                    serializer.WriteObject(stream, objectToSerialize);

                    try
                    {
                        stream.Position = 0L;

                        _propertyValuesBackup = _object.ConvertListToDictionary((List<KeyValuePair<string, object>>)serializer.ReadObject(stream));
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex, TraceMessages.FailedToDeserializeDataForBackup);
                    }
#else
                    BinaryFormatter serializer = SerializationHelper.GetBinarySerializer(false);
                    serializer.Serialize(stream, _object.ConvertDictionaryToListAndExcludeNonSerializableObjects(_object._propertyValues));

                    try
                    {
                        stream.Position = 0L;

                        _propertyValuesBackup = _object.ConvertListToDictionary((List<KeyValuePair<string, object>>)serializer.Deserialize(stream));
                    }
                    catch (Exception ex)
                    {
                        Log.Warn(ex, TraceMessages.FailedToDeserializeDataForBackupTryingWithRedirects);

                        stream.Position = 0L;

                        BinaryFormatter binaryFormatterWithRedirects = SerializationHelper.GetBinarySerializer(true);
                        _propertyValuesBackup = _object.ConvertListToDictionary((List<KeyValuePair<string, object>>)binaryFormatterWithRedirects.Deserialize(stream));
                    }
#endif
                }

                _objectValuesBackup = new Dictionary<string, object>();
                _objectValuesBackup.Add(IsDirty, _object.IsDirty);
            }

            /// <summary>
            /// Restores the backup to the object.
            /// </summary>
            public void RestoreBackup()
            {
                foreach (KeyValuePair<string, object> propertyValue in _propertyValuesBackup)
                {
                    // Set value so the PropertyChanged event is invoked
                    _object.SetValue(propertyValue.Key, propertyValue.Value);
                }

                _object.IsDirty = (bool)_objectValuesBackup[IsDirty];
            }
            #endregion
        }
        #endregion

        #region Constants
        /// <summary>
        /// The name of the <see cref="IDataWarningInfo.Warning"/> property.
        /// </summary>
        private const string WarningMessageProperty = "IDataWarningInfo.Warning";

        /// <summary>
        /// The name of the <see cref="IDataErrorInfo.Error"/> property.
        /// </summary>
        private const string ErrorMessageProperty = "IDataErrorInfo.Error";

        /// <summary>
        /// The constant that represents the uncomputed hash code.
        /// </summary>
        private const int UncomputedHashCode = -1;
        #endregion

        #region Variables
        /// <summary>
        /// The <see cref="ILog">log</see> object.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        protected static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Dictionary of initialized types.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private static readonly Dictionary<Type, bool> _initializedTypes = new Dictionary<Type, bool>();

        /// <summary>
        /// Lock object for the <see cref="_initializedTypes"/> field.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private static readonly object _initializedTypesLock = new object();

        /// <summary>
        /// List of types not to dispose automatically.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private readonly List<string> _typesNotToDispose = new List<string>();

        /// <summary>
        /// The property values.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        internal readonly Dictionary<string, object> _propertyValues = new Dictionary<string, object>();

        /// <summary>
        /// Lock object for the <see cref="_propertyValues"/> field.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        internal readonly object _propertyValuesLock = new object();

        /// <summary>
        /// The backup of the current object if any backup is initiated.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private BackupData _backup;

        /// <summary>
        /// Dictionary of field warnings per property.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private Dictionary<string, string> _fieldWarnings = new Dictionary<string, string>();

        /// <summary>
        /// List of business warnings.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private List<string> _businessWarnings = new List<string>();

        /// <summary>
        /// Dictionary of field errors per property.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private Dictionary<string, string> _fieldErrors = new Dictionary<string, string>();

        /// <summary>
        /// List of business errors.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private List<string> _businessErrors = new List<string>();

        /// <summary>
        /// Lock object to make sure that multiple validations at the same time are not allowed.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private readonly object _validationLock = new object();

#if !SILVERLIGHT
        /// <summary>
        /// The <see cref="SerializationInfo"/> that is retrieved and will be used for deserialization.
        /// </summary>
        [field: NonSerialized]
        private readonly SerializationInfo _serializationInfo;
#endif

        /// <summary>
        /// The parent object of the current object.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        private IParent _parent;
        #endregion

        #region Constructor & destructor
        /// <summary>
        /// Initializes static members of the <see cref="DataObjectBase"/> class.
        /// </summary>
        static DataObjectBase()
        {
            PropertyDataManager = new PropertyDataManager();
        }

#if SILVERLIGHT
        /// <summary>
        /// Initializes a new instance of the <see cref="DataObjectBase"/> class.
        /// </summary>
        protected DataObjectBase()
        {
            // Creating the object
            OnInitializing();

            // Initialize
            Initialize();

            // Finish initialization
            FinishInitializationAfterConstructionOrDeserialization();

            // Object created successfully
            OnInitialized();
        }
#else
        /// <summary>
        /// Initializes a new instance of the <see cref="DataObjectBase"/> class.
        /// </summary>
        protected DataObjectBase()
            : this(null, new StreamingContext())
        {
            // Do not write anything in this constructor. Use the Initialize method or the
            // OnInitializing or OnInitialized methods instead.
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataObjectBase"/> class.
        /// <para />
        /// Only constructor for the DataObjectBase.
        /// </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 DataObjectBase(SerializationInfo info, StreamingContext context)
        {
            OnInitializing();

            Initialize();

            // Make sure this is not a first time call
            if (info == null)
            {
                FinishInitializationAfterConstructionOrDeserialization();
            }
            else
            {
                _serializationInfo = info;

                List<KeyValuePair<string, object>> properties = (List<KeyValuePair<string, object>>)SerializationHelper.GetObject(info,
                    "Properties", typeof(List<KeyValuePair<string, object>>), new List<KeyValuePair<string, object>>());

                GetDataFromSerializationInfoInternal(_serializationInfo);

                DeserializationSucceeded = (properties != null) && (properties.Count > 0);
            }

            OnInitialized();
        }
#endif

        /// <summary>
        /// Finalizes an instance of the <see cref="DataObjectBase"/> class.
        /// <para />
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="DataObjectBase"/> is reclaimed by garbage collection.
        /// </summary>
        ~DataObjectBase()
        {
            // Call Dispose with false. Since we're in the destructor call, the managed resources will be disposed of anyways
            Dispose(false);
        }
        #endregion

        #region Events
#if !SILVERLIGHT
        /// <summary>
        /// Occurs when a property of this object is changing.
        /// </summary>
        [field: NonSerialized]
        public event PropertyChangingEventHandler PropertyChanging;
#endif

        /// <summary>
        /// Occurs when a property of this object has changed.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Occurs when the object is validating.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        public event EventHandler Validating;

        /// <summary>
        /// Occurs when the object is about the validate the fields.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        protected event EventHandler ValidatingFields;

        /// <summary>
        /// Occurs when the object has validated the fields.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        protected event EventHandler ValidatedFields;

        /// <summary>
        /// Occurs when the object is about the validate the business rules.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        protected event EventHandler ValidatingBusinessRules;

        /// <summary>
        /// Occurs when the object has validated the business rules.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        protected event EventHandler ValidatedBusinessRules;

        /// <summary>
        /// Occurs when the object is validated.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        public event EventHandler Validated;

        /// <summary>
        /// Occurs when the object is initialized.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        public event EventHandler Initialized;

        /// <summary>
        /// Occurs when the object is uninitialized.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        public event EventHandler Uninitialized;

        /// <summary>
        /// Occurs when the object is deserialized.
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        public event EventHandler Deserialized;
        #endregion

        #region Operators
        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="firstObject">The first object.</param>
        /// <param name="secondObject">The second object.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(DataObjectBase firstObject, DataObjectBase secondObject)
        {
            if (ReferenceEquals(firstObject, secondObject))
            {
                return true;
            }

            if (((object)firstObject == null) || ((object)secondObject == null))
            {
                return false;
            }

            foreach (KeyValuePair<string, object> propertyValue in firstObject._propertyValues)
            {
                // Only check if this is not an internal data object base property
                if (!firstObject.IsDataObjectBaseProperty(propertyValue.Key))
                {
                    object valueA = propertyValue.Value;
                    if (!secondObject.IsPropertyRegistered(propertyValue.Key))
                    {
                        return false;
                    }

                    object valueB = secondObject.GetValue(propertyValue.Key);

                    if (!ReferenceEquals(valueA, valueB))
                    {
                        if ((valueA == null) || (valueB == null))
                        {
                            return false;
                        }

                        // Is this an IEnumerable (but not a string)?
                        if ((valueA is IEnumerable) && !(valueA is string))
                        {
                            // Yes, loop all sub items and check them
                            if (!CollectionHelper.IsEqualTo((IEnumerable)valueA, (IEnumerable)valueB))
                            {
                                return false;
                            }
                        }
                        else
                        {
                            // No, check objects via equals method
                            if (!valueA.Equals(valueB))
                            {
                                return false;
                            }
                        }
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="firstObject">The first object.</param>
        /// <param name="secondObject">The second object.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(DataObjectBase firstObject, DataObjectBase secondObject)
        {
            return !(firstObject == secondObject);
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the property data manager that manages the properties of this object.
        /// </summary>
        /// <value>The property data manager.</value>
        [Browsable(false)]
        [XmlIgnore]
        internal static PropertyDataManager PropertyDataManager { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether this object is subscribed to all childs.
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        private bool SubscribedToEvents { get; set; }

        /// <summary>
        /// Gets a value indicating whether this object is currently initializing.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this object is currently initializing; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        [XmlIgnore]
        protected bool IsInitializing { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this object is initialized.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this object is initialized; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        [XmlIgnore]
        protected bool IsInitialized { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether the deserialized data is available, which means that
        /// OnDeserialized is invoked.
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        private bool IsDeserializedDataAvailable { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the object is fully deserialized.
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        private bool IsDeserialized { get; set; }

        /// <summary>
        /// Gets a value indicating whether the object is currently validating. During validation, no validation will be invoked.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the object is validating; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        [XmlIgnore]
        protected bool IsValidating { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether this object is validated or not.
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        private bool IsValidated { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is disposed.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is disposed; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        [XmlIgnore]
        private bool IsDisposed { get; set; }

        /// <summary>
        /// Gets a value indicating whether this instance contains non-serializable members.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance contains non-serializable members; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        [XmlIgnore]
        protected bool ContainsNonSerializableMembers { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether this object should always invoke the <see cref="PropertyChanged"/> event,
        /// even when the actual value of a property has not changed.
        /// <para />
        /// Enabling this property is useful when using this class in a WPF environment.
        /// </summary>
        /// <remarks>
        /// By default, this property is true. Disable it to gain a very, very small performance improvement but
        /// to loose stable WPF compatibility.
        /// </remarks>
        [Browsable(false)]
        protected bool AlwaysInvokeNotifyChanged { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this object should handle (thus invoke the specific events) when
        /// a property of collection value has changed.
        /// </summary>
        [Browsable(false)]
        protected bool HandlePropertyAndCollectionChanges { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the validation should be suspended. A call to <see cref="Validate()"/> will be returned immediately.
        /// </summary>
        /// <value><c>true</c> if validation should be suspended; otherwise, <c>false</c>.</value>
        [Browsable(false)]
        protected bool SuspendValidation { get; set; }

        /// <summary>
        /// Gets the collection of type names that should be disposed automatically.
        /// </summary>
        /// <value>The names of the types not to dispose.</value>
        [Browsable(false)]
        [XmlIgnore]
        protected List<string> TypesNotToDispose { get { return _typesNotToDispose; } }

        /// <summary>
        /// Gets or sets a value indicating whether child objects that implement the <see cref="IDisposable"/> interface are
        /// disposed automatically when this object is being disposed.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if child objects that implement the <see cref="IDisposable"/> interface are disposed automatically 
        ///     when this object is being disposed; otherwise, <c>false</c>.
        /// </value>
        protected bool AutomaticallyDisposeChildObjectsOnDispose { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this object should automatically validate itself when a property value
        /// has changed.
        /// </summary>
        [Browsable(false)]
        protected bool AutomaticallyValidateOnPropertyChanged { get; set; }

        /// <summary>
        /// Gets the parent.
        /// </summary>
        /// <value>The parent.</value>
        [Browsable(false)]
        [XmlIgnore]
        IParent IParent.Parent { get { return _parent; } }

        /// <summary>
        /// Gets the name of the object. By default, this is the hash code of all the properties combined.
        /// </summary>
        /// <value>The name of the key.</value>
        [Browsable(false)]
        [XmlIgnore]
        public virtual string KeyName
        {
            get { return GetHashCode().ToString(); }
        }

        /// <summary>
        /// Gets the <see cref="SerializationMode"/> of this object.
        /// </summary>
        /// <value>The serialization mode.</value>
        [Browsable(false)]
        [XmlIgnore]
        public SerializationMode Mode { get; private set; }

        /// <summary>
        /// Gets a value indicating whether the object is currently in an edit session, started by the <see cref="BeginEdit"/> method.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is currently in an edit session; otherwise, <c>false</c>.
        /// </value>
        public bool IsInEditSession
        {
            get { return _backup != null; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this object is dirty (contains unsaved data).
        /// </summary>
        /// <value><c>true</c> if this instance is dirty; otherwise, <c>false</c>.</value>
        [Browsable(false)]
        [XmlIgnore]
        public bool IsDirty
        {
            get { return GetValue<bool>(IsDirtyProperty); }
            protected set { SetValue(IsDirtyProperty, value); }
        }

        /// <summary>
        /// Register the IsDirty property so it is known in the class.
        /// </summary>
        protected static readonly PropertyData IsDirtyProperty = RegisterProperty("IsDirty", typeof(bool), false, false, null, false, true);

        /// <summary>
        /// Gets or sets a value indicating whether this object is currently read-only. When the object is read-only, values can only be read, not set.
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool IsReadOnly
        {
            get { return GetValue<bool>(IsReadOnlyProperty); }
            protected set { SetValue(IsReadOnlyProperty, value); }
        }

        /// <summary>
        /// Register the IsReadOnly property so it is known in the class.
        /// </summary>
        protected static readonly PropertyData IsReadOnlyProperty = RegisterProperty("IsReadOnly", typeof(bool), false, false,
            (sender, e) => ((DataObjectBase)sender).OnPropertyChanged("IsEditable"), false, true);

        /// <summary>
        /// Gets a value indicating whether this object is editable. This is the opposite of the <see cref="IsReadOnly"/> property.
        /// </summary>
        /// <value><c>true</c> if this object is editable; otherwise, <c>false</c>.</value>
        [Browsable(false)]
        [XmlIgnore]
        public bool IsEditable
        {
            get { return !IsReadOnly; }
        }

        /// <summary>
        /// Gets a value indicating whether this object contains any field or business warnings.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has warnings; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        [XmlIgnore]
        public bool HasWarnings
        {
            get { return (FieldWarningCount + BusinessRuleWarningCount) > 0; }
        }

        /// <summary>
        /// Gets the number of field warnings.
        /// </summary>
        /// <value>The field warning count.</value>
        [Browsable(false)]
        [XmlIgnore]
        public int FieldWarningCount
        {
            get
            {
                if (!IsValidated)
                {
                    Validate();
                }

                return _fieldWarnings.Count;
            }
        }

        /// <summary>
        /// Gets the number of business rule warnings.
        /// </summary>
        /// <value>The business rule warning count.</value>
        [Browsable(false)]
        [XmlIgnore]
        public int BusinessRuleWarningCount
        {
            get
            {
                if (!IsValidated)
                {
                    Validate();
                }

                return _businessWarnings.Count;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this object contains any field or business errors.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has errors; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        [XmlIgnore]
        public bool HasErrors
        {
            get { return (FieldErrorCount + BusinessRuleErrorCount) > 0; }
        }

        /// <summary>
        /// Gets the number of field errors.
        /// </summary>
        /// <value>The field error count.</value>
        [Browsable(false)]
        [XmlIgnore]
        public int FieldErrorCount
        {
            get
            {
                if (!IsValidated)
                {
                    Validate();
                }

                return _fieldErrors.Count;
            }
        }

        /// <summary>
        /// Gets the number of business rule errors.
        /// </summary>
        /// <value>The business rule error count.</value>
        [Browsable(false)]
        [XmlIgnore]
        public int BusinessRuleErrorCount
        {
            get
            {
                if (!IsValidated)
                {
                    Validate();
                }

                return _businessErrors.Count;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the deserialization has succeeded. If automatic deserialization fails, the object
        /// should try to deserialize manually.
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        protected bool DeserializationSucceeded { get; private set; }
        #endregion

        #region Methods
        /// <summary>
        /// Called when the object is being initialized.
        /// </summary>
        protected virtual void OnInitializing()
        {
            IsInitializing = true;
        }

        /// <summary>
        /// Called when the object is initialized.
        /// </summary>
        protected virtual void OnInitialized()
        {
            IsInitializing = false;
            IsInitialized = true;

            if (Initialized != null)
            {
                Initialized(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when the object is uninitializing.
        /// </summary>
        protected virtual void OnUninitializing() { }

        /// <summary>
        /// Called when the object is uninitialized.
        /// </summary>
        protected virtual void OnUninitialized()
        {
            if (Uninitialized != null)
            {
                Uninitialized(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when the object is deserialized.
        /// </summary>
        protected virtual void OnDeserialized()
        {
            if (Deserialized != null)
            {
                Deserialized(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Initializes the object by setting default values.
        /// </summary>
        private void Initialize()
        {
            DeserializationSucceeded = false;
            HandlePropertyAndCollectionChanges = true;
            AlwaysInvokeNotifyChanged = true;
            AutomaticallyDisposeChildObjectsOnDispose = true;
            AutomaticallyValidateOnPropertyChanged = true;
#if SILVERLIGHT
            Mode = SerializationMode.Xml;
#else
            Mode = SerializationMode.Binary;
#endif

            InitializeProperties();
        }

        /// <summary>
        /// Finishes the deserialization (both binary and xml)
        /// </summary>
        internal void FinishDeserialization()
        {
            Log.Debug(TraceMessages.FinishedDeserializationOfObject, GetType().Name);

            // Data is now considered deserialized
            IsDeserialized = true;

            try
            {
                FinishInitializationAfterConstructionOrDeserialization();
            }
            catch (Exception)
            {
                Log.Warn(TraceMessages.FailedToSubscribeToEventsInOnDeserializedMethod);
            }

            IsDirty = false;

            OnDeserialized();
        }

        /// <summary>
        /// Finishes the initialization after construction or deserialization.
        /// </summary>
        private void FinishInitializationAfterConstructionOrDeserialization()
        {
            foreach (KeyValuePair<string, PropertyData> propertyData in PropertyDataManager.GetProperties(GetType()))
            {
                if (propertyData.Value.SetParent)
                {
                    if (_propertyValues[propertyData.Key] is DataObjectBase)
                    {
                        ((DataObjectBase)_propertyValues[propertyData.Key]).SetParent(this);
                    }
                    else if (_propertyValues[propertyData.Key] is IEnumerable)
                    {
                        foreach (object obj in (IEnumerable)_propertyValues[propertyData.Key])
                        {
                            if (obj is DataObjectBase)
                            {
                                ((DataObjectBase)obj).SetParent(this);
                            }
                        }
                    }
                }
            }

            SubscribeAllObjectsToNotifyChangedEvents();
        }

        /// <summary>
        /// Sets the new parent of this object.
        /// </summary>
        /// <param name="parent">The new parent.</param>
        protected void SetParent(IParent parent)
        {
            _parent = parent;

            OnPropertyChanged("Parent");
        }

        /// <summary>
        /// Sets the value of a specific property.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="value">Value of the property</param>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        protected void SetValue(string name, object value)
        {
            SetValue(name, value, true);
        }

        /// <summary>
        /// Sets the value of a specific property.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="value">Value of the property</param>
        /// <param name="notifyOnChange">If <c>true</c>, the <see cref="PropertyChanged"/> event will be invoked.</param>
        /// <exception cref="PropertyNotNullableException">When the property is not nullable, but <paramref name="value"/> is <c>null</c>.</exception>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception> 
        private void SetValue(string name, object value, bool notifyOnChange)
        {
            // Is the object currently read-only (and aren't we changing that)?
            if ((string.Compare(name, IsReadOnlyProperty.Name) != 0) && IsReadOnly)
            {
                Log.Warn(TraceMessages.CannotSetPropertyObjectIsReadonly, name);

                return;
            }

            PropertyData property = GetPropertyData(name);
            if ((value == null) && !TypeHelper.IsTypeNullable(property.Type))
            {
                throw new PropertyNotNullableException(name, GetType());
            }

            if ((value != null) && (!property.Type.IsAssignableFrom(value.GetType())))
            {
                throw new InvalidPropertyValueException(name, property.Type, value.GetType());
            }

            lock (_propertyValuesLock)
            {
                bool notify = false;

                if (notifyOnChange)
                {
#if !SILVERLIGHT
                    OnPropertyChanging(name);
#endif
                }

                object oldValue = GetValue(name);

                if (!TypeHelper.AreObjectsEqual(oldValue, value))
                {
                    _propertyValues[name] = value;

                    HandleObjectEventsSubscription(oldValue, value);

                    IsValidated = false;
                    notify = true;
                }

                if (notifyOnChange && (AlwaysInvokeNotifyChanged || notify))
                {
                    OnPropertyChanged(name);
                }
            }
        }

        /// <summary>
        /// Sets the value of a specific property.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <param name="value">Value of the property</param>
        /// <exception cref="ArgumentNullException">When <paramref name="property"/> is <c>null</c>.</exception>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        protected void SetValue(PropertyData property, object value)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }

            SetValue(property.Name, value);
        }

        /// <summary>
        /// Gets the value of a specific property.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <returns>Object value of the property</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        protected object GetValue(string name)
        {
            if (!IsPropertyRegistered(name))
            {
                throw new PropertyNotRegisteredException(name, GetType());
            }

            lock (_propertyValuesLock)
            {
                return _propertyValues[name];
            }
        }

        /// <summary>
        /// Gets the typed value of a specific property.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="name">Name of the property.</param>
        /// <returns>Object value of the property</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        protected TValue GetValue<TValue>(string name)
        {
            object obj = GetValue(name);

            return ((obj != null) && (obj is TValue)) ? (TValue)obj : default(TValue);
        }

        /// <summary>
        /// Gets the value of a specific property.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <returns>Object value of the property</returns>
        /// <exception cref="ArgumentNullException">When <paramref name="property"/> is <c>null</c>.</exception>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        protected object GetValue(PropertyData property)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }

            return GetValue(property.Name);
        }

        /// <summary>
        /// Gets the typed value of a specific property.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <returns>Object value of the property</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        protected TValue GetValue<TValue>(PropertyData property)
        {
            object obj = GetValue(property);

            return ((obj != null) && (obj is TValue)) ? (TValue)obj : default(TValue);
        }

        /// <summary>
        /// Returns the default value of a specific property.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <returns>Default value of the property.</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        public object GetDefaultValue(string name)
        {
            return GetPropertyData(name).GetDefaultValue();
        }

        /// <summary>
        /// Returns the default value of a specific property.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <returns>Default value of the property.</returns>
        /// <exception cref="ArgumentNullException">When <paramref name="property"/> is <c>null</c>.</exception>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        public object GetDefaultValue(PropertyData property)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }

            return GetDefaultValue(property.Name);
        }

        /// <summary>
        /// Returns the typed default value of a specific property.
        /// </summary>
        /// <typeparam name="TValue">The type of the 1.</typeparam>
        /// <param name="name">Name of the property.</param>
        /// <returns>Default value of the property.</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        public TValue GetDefaultValue<TValue>(string name)
        {
            object obj = GetDefaultValue(name);

            return ((obj != null) && (obj is TValue)) ? (TValue)obj : default(TValue);
        }

        /// <summary>
        /// Returns the typed default value of a specific property.
        /// </summary>
        /// <typeparam name="TValue">The type of the 1.</typeparam>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <returns>Default value of the property.</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        public TValue GetDefaultValue<TValue>(PropertyData property)
        {
            object obj = GetDefaultValue(property);

            return ((obj != null) && (obj is TValue)) ? (TValue)obj : default(TValue);
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        /// 	<c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            // ReSharper disable RedundantCast

            if ((object)obj == null)
            {
                return false;
            }

            // ReSharper restore RedundantCast

            if (!(obj is DataObjectBase))
            {
                return false;
            }

            // ReSharper disable RedundantCast

            return (DataObjectBase)this == (DataObjectBase)obj;

            // ReSharper restore RedundantCast
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        // ReSharper disable RedundantOverridenMember

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return base.ToString();
        }

        // ReSharper restore RedundantOverridenMember

        /// <summary>
        /// Clears the <see cref="IsDirty"/> on all childs.
        /// </summary>
        protected void ClearIsDirtyOnAllChilds()
        {
            ClearIsDirtyOnAllChilds(this);
        }

        /// <summary>
        /// Clears the <see cref="IsDirty"/> on all childs.
        /// </summary>
        /// <param name="obj">The object.</param>
        private static void ClearIsDirtyOnAllChilds(object obj)
        {
            if (obj is DataObjectBase)
            {
                ((DataObjectBase)obj).IsDirty = false;

                Dictionary<string, PropertyData> properties = PropertyDataManager.GetProperties(obj.GetType());
                foreach (KeyValuePair<string, PropertyData> property in properties)
                {
                    object value = ((DataObjectBase)obj).GetValue(property.Value);

                    ClearIsDirtyOnAllChilds(value);
                }
            }
            else if (obj is IEnumerable)
            {
                foreach (object childItem in (IEnumerable)obj)
                {
                    if (childItem is DataObjectBase)
                    {
                        ClearIsDirtyOnAllChilds(childItem);
                    }
                }
            }
        }
        #endregion

        #region Property handling
        /// <summary>
        /// Registers a property that will be automatically handled by this object with <c>null</c> as default value.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        protected static PropertyData RegisterProperty(string name, Type type)
        {
            return RegisterProperty(name, type, type.IsValueType ? Activator.CreateInstance(type) : null);
        }

        /// <summary>
        /// Registers a property that will be automatically handled by this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="defaultValue">Default value of the property.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        protected static PropertyData RegisterProperty(string name, Type type, object defaultValue)
        {
            return RegisterProperty(name, type, defaultValue, false, null);
        }

        /// <summary>
        /// Registers a property that will be automatically handled by this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="defaultValue">Default value of the property.</param>
        /// <param name="setParent">if set to <c>true</c>, the parent of the property will be set.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        protected static PropertyData RegisterProperty(string name, Type type, object defaultValue, bool setParent)
        {
            return RegisterProperty(name, type, defaultValue, setParent, null);
        }

        /// <summary>
        /// Registers a property that will be automatically handled by this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="defaultValue">Default value of the property.</param>
        /// <param name="setParent">if set to <c>true</c>, the parent of the property will be set.</param>
        /// <param name="includeInSerialization">if set to <c>true</c>, the property should be included in the serialization.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        protected static PropertyData RegisterProperty(string name, Type type, object defaultValue, bool setParent, bool includeInSerialization)
        {
            return RegisterProperty(name, type, defaultValue, setParent, null, includeInSerialization);
        }

        /// <summary>
        /// Registers a property that will be automatically handled by this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="propertyChangedEventHandler">The property changed event handler.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        protected static PropertyData RegisterProperty(string name, Type type, EventHandler<PropertyChangedWithSenderEventArgs> propertyChangedEventHandler)
        {
            return RegisterProperty(name, type, type.IsValueType ? Activator.CreateInstance(type) : null, false, propertyChangedEventHandler);
        }

        /// <summary>
        /// Registers a property that will be automatically handled by this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="defaultValue">Default value of the property.</param>
        /// <param name="propertyChangedEventHandler">The property changed event handler.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        protected static PropertyData RegisterProperty(string name, Type type, object defaultValue, EventHandler<PropertyChangedWithSenderEventArgs> propertyChangedEventHandler)
        {
            return RegisterProperty(name, type, defaultValue, false, propertyChangedEventHandler);
        }

        /// <summary>
        /// Registers a property that will be automatically handled by this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="defaultValue">Default value of the property.</param>
        /// <param name="setParent">if set to <c>true</c>, the parent of the property will be set.</param>
        /// <param name="propertyChangedEventHandler">The property changed event handler.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        protected static PropertyData RegisterProperty(string name, Type type, object defaultValue, bool setParent, EventHandler<PropertyChangedWithSenderEventArgs> propertyChangedEventHandler)
        {
            return RegisterProperty(name, type, defaultValue, setParent, propertyChangedEventHandler, true);
        }

        /// <summary>
        /// Registers a property that will be automatically handled by this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="defaultValue">Default value of the property.</param>
        /// <param name="setParent">if set to <c>true</c>, the parent of the property will be set.</param>
        /// <param name="propertyChangedEventHandler">The property changed event handler.</param>
        /// <param name="includeInSerialization">if set to <c>true</c>, the property should be included in the serialization.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        protected static PropertyData RegisterProperty(string name, Type type, object defaultValue, bool setParent, EventHandler<PropertyChangedWithSenderEventArgs> propertyChangedEventHandler, bool includeInSerialization)
        {
            return RegisterProperty(name, type, defaultValue, setParent, propertyChangedEventHandler, includeInSerialization, false);
        }

        /// <summary>
        /// Registers a property that will be automatically handled by this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="defaultValue">Default value of the property.</param>
        /// <param name="setParent">if set to <c>true</c>, the parent of the property will be set.</param>
        /// <param name="propertyChangedEventHandler">The property changed event handler.</param>
        /// <param name="includeInSerialization">if set to <c>true</c>, the property should be included in the serialization.</param>
        /// <param name="isDataObjectBaseProperty">if set to <c>true</c>, the property is declared by the <see cref="DataObjectBase"/>.</param>
        /// <returns>
        /// 	<see cref="PropertyData"/> containing the property information.
        /// </returns>
        private static PropertyData RegisterProperty(string name, Type type, object defaultValue, bool setParent, EventHandler<PropertyChangedWithSenderEventArgs> propertyChangedEventHandler,
            bool includeInSerialization, bool isDataObjectBaseProperty)
        {
#if SILVERLIGHT
            // Create the property (and in silverlight, assume all properties are serializable)
            PropertyData property = new PropertyData(name, type, defaultValue, setParent, propertyChangedEventHandler, true, includeInSerialization, isDataObjectBaseProperty);
#else
            PropertyData property = new PropertyData(name, type, defaultValue, setParent, propertyChangedEventHandler, type.IsSerializable, includeInSerialization, isDataObjectBaseProperty);
#endif

            return property;
        }

        /// <summary>
        /// Initializes all the properties for this object.
        /// </summary>
        private void InitializeProperties()
        {
            Type type = GetType();

            List<PropertyData> registeredPropertyData = new List<PropertyData>();

            // Fields
            List<FieldInfo> fields = new List<FieldInfo>();
            fields.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy));
            foreach (FieldInfo field in fields)
            {
                if (field.FieldType == typeof(PropertyData))
                {
                    PropertyData propertyValue = (field.IsStatic ? field.GetValue(null) : field.GetValue(this)) as PropertyData;
                    if (propertyValue != null)
                    {
                        registeredPropertyData.Add(propertyValue);
                    }
                }
            }

            // Properties
            List<PropertyInfo> properties = new List<PropertyInfo>();
            properties.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy));
            foreach (PropertyInfo property in properties)
            {
                if (property.PropertyType == typeof(PropertyData))
                {
                    PropertyData propertyValue = property.GetValue(null, null) as PropertyData;
                    if (propertyValue != null)
                    {
                        registeredPropertyData.Add(propertyValue);
                    }
                }
            }

            foreach (PropertyData propertyData in registeredPropertyData)
            {
                if (!propertyData.IsSerializable)
                {
                    object[] allowNonSerializableMembersAttributes = type.GetCustomAttributes(typeof(AllowNonSerializableMembersAttribute), true);
                    if (allowNonSerializableMembersAttributes.Length == 0)
                    {
                        throw new InvalidPropertyException(propertyData.Name);
                    }

                    ContainsNonSerializableMembers = true;
                }

                InitializeProperty(propertyData);
            }

            lock (_initializedTypesLock)
            {
                if (!_initializedTypes.ContainsKey(type))
                {
                    _initializedTypes.Add(type, true);
                }
            }
        }

        /// <summary>
        /// Initializes a specific property for this object.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <exception cref="InvalidPropertyException">When the name of the property is invalid.</exception>
        /// <exception cref="PropertyAlreadyRegisteredException">When the property is already registered.</exception>
        private void InitializeProperty(PropertyData property)
        {
            InitializeProperty(property.Name, property.Type, property.GetDefaultValue(), property.SetParent, property.PropertyChangedEventHandler,
                property.IsSerializable, property.IncludeInSerialization, property.IsDataObjectBaseProperty);
        }

        /// <summary>
        /// Initializes a specific property for this object.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <param name="type">Type of the property.</param>
        /// <param name="defaultValue">Default value of the property.</param>
        /// <param name="setParent">if set to <c>true</c>, the parent of the property will be set.</param>
        /// <param name="propertyChangedEventHandler">The property changed event handler.</param>
        /// <param name="isSerializable">if set to <c>true</c>, the property is serializable.</param>
        /// <param name="includeInSerialization">if set to <c>true</c>, the property should be included in the serialization.</param>
        /// <param name="isDataObjectBaseProperty">if set to <c>true</c>, the property is declared by the <see cref="DataObjectBase"/>.</param>
        /// <exception cref="InvalidPropertyException">When the name of the property is invalid.</exception>
        /// <exception cref="PropertyAlreadyRegisteredException">When the property is already registered.</exception>
        private void InitializeProperty(string name, Type type, object defaultValue, bool setParent, EventHandler<PropertyChangedWithSenderEventArgs> propertyChangedEventHandler,
            bool isSerializable, bool includeInSerialization, bool isDataObjectBaseProperty)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new InvalidPropertyException(name);
            }

            Type objectType = GetType();
            if ((defaultValue == null) && !TypeHelper.IsTypeNullable(type))
            {
                throw new PropertyNotNullableException(name, objectType);
            }

            lock (_initializedTypesLock)
            {
                if (!_initializedTypes.ContainsKey(objectType) || !_initializedTypes[objectType])
                {
                    if (IsPropertyRegistered(name))
                    {
                        // This type already registered the property, but maybe this is a derived type
                        // As long as the name, type and default value are the same, this is allowed
                        PropertyData propertyData = GetPropertyData(name);
                        if ((propertyData.Name != name) ||
                            (propertyData.Type != type) ||
                            ((propertyData.GetDefaultValue() == null) && (defaultValue != null)) ||
                            ((propertyData.GetDefaultValue() != null) && (!propertyData.GetDefaultValue().Equals(defaultValue))))
                        {
                            throw new PropertyAlreadyRegisteredException(name, GetType());
                        }
                    }
                    else
                    {
                        PropertyData propertyData = new PropertyData(name, type, defaultValue, setParent, propertyChangedEventHandler,
                            isSerializable, includeInSerialization, isDataObjectBaseProperty);
                        PropertyDataManager.RegisterProperty(objectType, name, propertyData);
                    }
                }
            }

            lock (_propertyValuesLock)
            {
                _propertyValues.Add(name, defaultValue);
            }
        }

        /// <summary>
        /// Determines whether the specified property is a property declared by the <see cref="DataObjectBase"/> itself.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>
        /// 	<c>true</c> if the specified property is a property declared by the <see cref="DataObjectBase"/> itself; otherwise, <c>false</c>.
        /// </returns>
        protected bool IsDataObjectBaseProperty(string name)
        {
            if (!IsPropertyRegistered(name))
            {
                return false;
            }

            return GetPropertyData(name).IsDataObjectBaseProperty;
        }

        /// <summary>
        /// Returns whether a specific property is registered.
        /// </summary>
        /// <param name="name">Name of the property.</param>
        /// <returns>True if the property is registered, otherwise false.</returns>
        protected bool IsPropertyRegistered(string name)
        {
            return PropertyDataManager.IsPropertyRegistered(GetType(), name);
        }

        /// <summary>
        /// Gets the <see cref="PropertyData"/> for the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The <see cref="PropertyData"/>.</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        protected PropertyData GetPropertyData(string name)
        {
            return PropertyDataManager.GetPropertyData(GetType(), name);
        }

        /// <summary>
        /// Gets the <see cref="PropertyInfo"/> for the specified property.
        /// </summary>
        /// <param name="property">The property.</param>
        /// <returns><see cref="PropertyInfo"/> or <c>null</c> if no property info is found.</returns>
        protected PropertyInfo GetPropertyInfo(PropertyData property)
        {
            return GetPropertyInfo(property.Name);
        }

        /// <summary>
        /// Gets the <see cref="PropertyInfo"/> for the specified property.
        /// </summary>
        /// <param name="property">The name of the property.</param>
        /// <returns><see cref="PropertyInfo"/> or <c>null</c> if no property info is found.</returns>
        protected PropertyInfo GetPropertyInfo(string property)
        {
            return TypeHelper.GetPropertyInfo(GetType(), property);
        }

        /// <summary>
        /// Returns the type of a specific property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>Type of the property.</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        public Type GetPropertyType(string name)
        {
            return GetPropertyData(name).Type;
        }

        /// <summary>
        /// Returns the type of a specific property.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <returns>Type of the property.</returns>
        /// <exception cref="PropertyNotRegisteredException">When the property is not registered.</exception>
        public Type GetPropertyType(PropertyData property)
        {
            return GetPropertyType(property.Name);
        }
        #endregion

        #region INotifyPropertyChanging Members
#if !SILVERLIGHT
        /// <summary>
        /// Invoked when a property value is changing.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangingEventArgs"/> instance containing the event data.</param>
        private void OnPropertyChanging(object sender, PropertyChangingEventArgs e)
        {
            if (PropertyChanging != null)
            {
                PropertyChanging(sender, e);
            }
        }

        /// <summary>
        /// Invoked when a property value is changing.
        /// </summary>
        /// <param name="propertyName">Name of the property that is changing.</param>
        protected virtual void OnPropertyChanging(string propertyName)
        {
            OnPropertyChanging(this, new PropertyChangingEventArgs(propertyName));
        }
#endif
        #endregion

        #region INotifyPropertyChanged Members
        /// <summary>
        /// Subscribes all objects to notify changed events.
        /// </summary>
        public void SubscribeAllObjectsToNotifyChangedEvents()
        {
            if (_propertyValues == null)
            {
                return;
            }

            if (SubscribedToEvents)
            {
                return;
            }

            lock (_propertyValuesLock)
            {
                foreach (KeyValuePair<string, object> property in _propertyValues)
                {
                    SubscribeNotifyChangedEvents(property.Value);
                }
            }

            SubscribedToEvents = true;
        }

        /// <summary>
        /// Unsubscribes all objects from notify changed.
        /// </summary>
        private void UnsubscribeAllObjectsFromNotifyChanged()
        {
            if (_propertyValues == null)
            {
                return;
            }

            lock (_propertyValuesLock)
            {
                foreach (KeyValuePair<string, object> property in _propertyValues)
                {
                    UnsubscribeNotifyChangedEvents(property.Value);
                }
            }

            SubscribedToEvents = false;
        }

        /// <summary>
        /// Subscribes to events of child objects.
        /// </summary>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        private void HandleObjectEventsSubscription(object oldValue, object newValue)
        {
            UnsubscribeNotifyChangedEvents(oldValue);

            SubscribeNotifyChangedEvents(newValue);
        }

        /// <summary>
        /// Unsubscribes from the notify changed events.
        /// </summary>
        /// <param name="value">The object to unsubscribe from.</param>
        private void UnsubscribeNotifyChangedEvents(object value)
        {
            if (value != null)
            {
                INotifyPropertyChanged propertyChangedValue = value as INotifyPropertyChanged;
                if (propertyChangedValue != null)
                {
                    propertyChangedValue.PropertyChanged -= OnPropertyChanged;
                }

                INotifyCollectionChanged collectionChangedValue = value as INotifyCollectionChanged;
                if (collectionChangedValue != null)
                {
                    collectionChangedValue.CollectionChanged -= OnCollectionChanged;

                    foreach (object child in (IEnumerable)value)
                    {
                        if (child is INotifyPropertyChanged)
                        {
                            ((INotifyPropertyChanged)child).PropertyChanged -= OnPropertyChanged;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Subscribes to the notify changed events.
        /// </summary>
        /// <param name="value">The object to subscribe to.</param>
        private void SubscribeNotifyChangedEvents(object value)
        {
            if (value != null)
            {
                INotifyPropertyChanged propertyChangedValue = value as INotifyPropertyChanged;
                if (propertyChangedValue != null)
                {
                    propertyChangedValue.PropertyChanged += OnPropertyChanged;
                }

                INotifyCollectionChanged collectionChangedValue = value as INotifyCollectionChanged;
                if (collectionChangedValue != null)
                {
                    collectionChangedValue.CollectionChanged += OnCollectionChanged;

                    foreach (object child in (IEnumerable)value)
                    {
                        if (child is INotifyPropertyChanged)
                        {
                            ((INotifyPropertyChanged)child).PropertyChanged += OnPropertyChanged;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Invokes the property changed for all registered properties.
        /// </summary>
        /// <remarks>
        /// Using this method does not set the <see cref="IsDirty"/> property to <c>true</c>, nor will
        /// it cause the object to validate itself automatically, even when the <see cref="AutomaticallyValidateOnPropertyChanged"/>
        /// is set to <c>true</c>.
        /// </remarks>
        internal void InvokePropertyChangedForAllRegisteredProperties()
        {
            foreach (var propertyData in PropertyDataManager.GetProperties(GetType()))
            {
                OnPropertyChanged(this, new PropertyChangedEventArgs(propertyData.Key), false);
            }
        }

        /// <summary>
        /// Invoked when a property value has changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChanged(sender, e, true);
        }

        /// <summary>
        /// Invoked when a property value has changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        /// <param name="setDirtyAndAllowAutomaticValidation">if set to <c>true</c>, the <see cref="IsDirty"/> property is set and automatic validation is allowed.</param>
        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e, bool setDirtyAndAllowAutomaticValidation)
        {
            // If this is an internal data object base property, just leave
            if (IsDataObjectBaseProperty(e.PropertyName))
            {
                if (Equals(sender))
                {
                    return;
                }

                // Maybe this is a child object informing us that it's not dirty any longer
                if ((sender is DataObjectBase) && (e.PropertyName == IsDirtyProperty.Name) && ((bool)((DataObjectBase)sender).GetValue(e.PropertyName) == false))
                {
                    return;
                }
            }

            if (HandlePropertyAndCollectionChanges)
            {
                if (IsPropertyRegistered(e.PropertyName))
                {
                    PropertyData propertyData = GetPropertyData(e.PropertyName);

                    if (ReferenceEquals(this, sender) && (propertyData.PropertyChangedEventHandler != null))
                    {
                        propertyData.PropertyChangedEventHandler(this, new PropertyChangedWithSenderEventArgs(sender, this, e));
                    }
                }

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, e);
                }
            }

            // Are we not validating or is this a warning or error message?
            if (setDirtyAndAllowAutomaticValidation && !IsValidating &&
                (string.Compare(e.PropertyName, WarningMessageProperty) != 0) &&
                (string.Compare(e.PropertyName, ErrorMessageProperty) != 0))
            {
                IsDirty = true;
                IsValidated = false;
            }

            if (AutomaticallyValidateOnPropertyChanged && setDirtyAndAllowAutomaticValidation)
            {
                Validate();
            }
        }

        /// <summary>
        /// Invoked when a property value has changed.
        /// </summary>
        /// <param name="propertyName">Name of the property that has changed.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Invoked when a collection value has changed.
        /// </summary>
        /// <param name="sender">The object that contains the changed collection value.</param>
        /// <param name="e"><see cref="NotifyCollectionChangedEventArgs"/> containing all information about the changed collection.</param>
        protected virtual void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null)
            {
                foreach (object item in e.OldItems)
                {
                    if (item is INotifyPropertyChanged)
                    {
                        ((INotifyPropertyChanged)item).PropertyChanged -= OnPropertyChanged;
                    }
                }
            }

            if (e.NewItems != null)
            {
                foreach (object item in e.NewItems)
                {
                    if (item is INotifyPropertyChanged)
                    {
                        ((INotifyPropertyChanged)item).PropertyChanged += OnPropertyChanged;
                    }
                }
            }

            IsDirty = true;
            IsValidated = false;

            if (!HandlePropertyAndCollectionChanges)
            {
                return;
            }

            lock (_propertyValuesLock)
            {
                foreach (KeyValuePair<string, object> property in _propertyValues)
                {
                    if ((property.Value != null) && (property.Value == sender))
                    {
                        OnPropertyChanged(property.Key);

                        return;
                    }
                }
            }
        }
        #endregion

        #region Validation
        /// <summary>
        /// Validates the field values of this object. Override this method to enable
        /// validation of field values.
        /// </summary>
        protected virtual void ValidateFields()
        { }

        /// <summary>
        /// Validates the business rules of this object. Override this method to enable
        /// validation of business rules.
        /// </summary>
        protected virtual void ValidateBusinessRules()
        { }

        /// <summary>
        /// Called when the object is validating.
        /// </summary>
        [CoverageExclude(ExcludeReason.MethodWillOnlyBeCoveredInProductionScenario)]
        protected virtual void OnValidating()
        {
            if (Validating != null)
            {
                Validating(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when the object is validating the fields.
        /// </summary>
        [CoverageExclude(ExcludeReason.MethodWillOnlyBeCoveredInProductionScenario)]
        protected virtual void OnValidatingFields()
        {
            if (ValidatingFields != null)
            {
                ValidatingFields(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when the object has validated the fields.
        /// </summary>
        [CoverageExclude(ExcludeReason.MethodWillOnlyBeCoveredInProductionScenario)]
        protected virtual void OnValidatedFields()
        {
            if (ValidatedFields != null)
            {
                ValidatedFields(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when the object is validating the business rules.
        /// </summary>
        [CoverageExclude(ExcludeReason.MethodWillOnlyBeCoveredInProductionScenario)]
        protected virtual void OnValidatingBusinessRules()
        {
            if (ValidatingBusinessRules != null)
            {
                ValidatingBusinessRules(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when the object has validated the business rules.
        /// </summary>
        [CoverageExclude(ExcludeReason.MethodWillOnlyBeCoveredInProductionScenario)]
        protected virtual void OnValidatedBusinessRules()
        {
            if (ValidatedBusinessRules != null)
            {
                ValidatedBusinessRules(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when the object is validated.
        /// </summary>
        [CoverageExclude(ExcludeReason.MethodWillOnlyBeCoveredInProductionScenario)]
        protected virtual void OnValidated()
        {
            if (Validated != null)
            {
                Validated(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Validates the current object for field and business rule errors.
        /// </summary>
        /// <remarks>
        /// To check wether this object contains any errors, use the <see cref="HasErrors"/> property.
        /// <para />
        /// This method does not force validation. This means that when the object is already validated,
        /// and no properties have been changed, no validation actually occurs since there is no reason
        /// for any values to have changed.
        /// </remarks>
        public void Validate()
        {
            Validate(false);
        }

        /// <summary>
        /// Validates the current object for field and business rule errors.
        /// </summary>
        /// <param name="force">if set to <c>true</c>, a validation is forced.</param>
        /// <remarks>
        /// To check wether this object contains any errors, use the <see cref="HasErrors"/> property.
        /// </remarks>
        public void Validate(bool force)
        {
            Validate(force, true);
        }

        /// <summary>
        /// Validates the current object for field and business rule errors.
        /// </summary>
        /// <param name="force">if set to <c>true</c>, a validation is forced (even if the object knows it is already validated).</param>
        /// <param name="notifyChangedPropertiesOnly">if set to <c>true</c> only the properties for which the warnings or errors have been changed 
        /// will be updated via <see cref="INotifyPropertyChanged.PropertyChanged"/>; otherwise all the properties that
        /// had warnings or errors but not anymore and properties still containing warnings or errors will be updated.</param>
        /// <remarks>
        /// To check wether this object contains any errors, use the <see cref="HasErrors"/> property.
        /// </remarks>
        public void Validate(bool force, bool notifyChangedPropertiesOnly)
        {
            if (SuspendValidation)
            {
                return;
            }

            if (IsValidating)
            {
                return;
            }

            IsValidating = true;

            OnValidating();

            if (!IsValidated || force)
            {
                lock (_validationLock)
                {
                    bool hasErrors = HasErrors;

                    #region Fields
                    IDictionary<string, string> previousFieldWarnings = _fieldWarnings;
                    IDictionary<string, string> previousFieldErrors = _fieldErrors;

                    _fieldWarnings = new Dictionary<string, string>();
                    _fieldErrors = new Dictionary<string, string>();

                    OnValidatingFields();
                    ValidateFields();
                    OnValidatedFields();

                    #region Track warning and error entries to determine what fields should be marked as changed to ensure WPF UI updates
                    IDictionary<string, bool> changedFields = new Dictionary<string, bool>();
                    if (previousFieldWarnings != null)
                    {
                        foreach (string propertyName in previousFieldWarnings.Keys)
                        {
                            changedFields.Add(propertyName, !notifyChangedPropertiesOnly || !_fieldWarnings.ContainsKey(propertyName) || !string.Equals(_fieldWarnings[propertyName], previousFieldWarnings[propertyName], StringComparison.Ordinal));
                        }
                    }

                    if (previousFieldErrors != null)
                    {
                        foreach (string propertyName in previousFieldErrors.Keys)
                        {
                            changedFields.Add(propertyName, !notifyChangedPropertiesOnly || !_fieldErrors.ContainsKey(propertyName) || !string.Equals(_fieldErrors[propertyName], previousFieldErrors[propertyName], StringComparison.Ordinal));
                        }
                    }

                    foreach (string propertyName in _fieldWarnings.Keys)
                    {
                        // Was not in previousCollection, new warning thus also changed
                        if (!changedFields.ContainsKey(propertyName))
                        {
                            changedFields.Add(propertyName, true);
                        }
                    }

                    foreach (string propertyName in _fieldErrors.Keys)
                    {
                        // Was not in previousCollection, new error thus also changed
                        if (!changedFields.ContainsKey(propertyName))
                        {
                            changedFields.Add(propertyName, true);
                        }
                    }
                    #endregion

                    foreach (string propertyName in changedFields.Keys)
                    {
                        if (changedFields[propertyName])
                        {
                            OnPropertyChanged(propertyName);
                        }
                    }
                    #endregion

                    #region Business rules
                    int previousBusinessWarningsCount = _businessWarnings.Count;
                    int previousBusinessErrorsCount = _businessErrors.Count;

                    _businessWarnings = new List<string>();
                    _businessErrors = new List<string>();

                    OnValidatingBusinessRules();
                    ValidateBusinessRules();
                    OnValidatedBusinessRules();

                    IsValidated = true;

                    if ((_businessWarnings.Count > 0) || (previousBusinessWarningsCount > 0))
                    {
                        OnPropertyChanged(WarningMessageProperty);
                    }

                    if ((_businessErrors.Count > 0) || (previousBusinessErrorsCount > 0))
                    {
                        OnPropertyChanged(ErrorMessageProperty);
                    }
                    #endregion

                    if (HasErrors != hasErrors)
                    {
                        OnPropertyChanged("HasErrors");
                    }
                }
            }

            OnValidated();

            IsValidating = false;
        }

        /// <summary>
        /// Gets the list messages.
        /// </summary>
        /// <param name="fields">The field warnings or errors.</param>
        /// <param name="business">The business warnings or errors.</param>
        /// <returns>String representing the output of all items in the fields an business object.</returns>
        /// <remarks>
        /// This method is used to create a message string for field warnings or errors and business warnings
        /// or errors. Just pass the right dictionary and list to this method.
        /// </remarks>
        private static string GetListMessages(Dictionary<string, string> fields, IEnumerable<string> business)
        {
            StringBuilder messageBuilder = new StringBuilder();

            foreach (KeyValuePair<string, string> field in fields)
            {
                messageBuilder.AppendLine(string.Format("* {0}", field.Value));
            }

            foreach (string businessItem in business)
            {
                messageBuilder.AppendLine(string.Format("* {0}", businessItem));
            }

            return messageBuilder.ToString();
        }

        #region IDataWarningInfo Members
        /// <summary>
        /// Gets the current warning.
        /// </summary>
        string IDataWarningInfo.Warning
        {
            get
            {
                string warning = string.Empty;

                if (!IsValidated)
                {
                    Validate();
                }

                if ((_businessWarnings != null) && (_businessWarnings.Count > 0))
                {
                    warning = _businessWarnings[0];
                }

                return warning;
            }
        }

        /// <summary>
        /// Gets a warning for a specific column.
        /// </summary>
        /// <param name="columnName">Column name.</param>
        /// <returns>Warning.</returns>
        string IDataWarningInfo.this[string columnName]
        {
            get
            {
                string warning = string.Empty;

                if (!IsValidated)
                {
                    Validate();
                }

                if ((_fieldWarnings != null) && _fieldWarnings.ContainsKey(columnName))
                {
                    warning = _fieldWarnings[columnName];
                }

                return warning;
            }
        }

        /// <summary>
        /// Sets a specific field warning.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <param name="warning">Warning message.</param>
        protected void SetFieldWarning(PropertyData property, string warning)
        {
            SetFieldWarning(property.Name, warning);
        }

        /// <summary>
        /// Sets a specific field warning.
        /// </summary>
        /// <param name="property">Name of the property.</param>
        /// <param name="warning">Warning message.</param>
        protected void SetFieldWarning(string property, string warning)
        {
            if (string.IsNullOrEmpty(warning))
            {
                return;
            }

            _fieldWarnings[property] = warning;
        }

        /// <summary>
        /// Sets a specific field warning.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <param name="warningFormat">The warning format.</param>
        /// <param name="args">The formatting arguments.</param>
        protected void SetFieldWarning(PropertyData property, string warningFormat, params object[] args)
        {
            SetFieldWarning(property.Name, string.Format(warningFormat, args));
        }

        /// <summary>
        /// Sets a specific business rule warning.
        /// </summary>
        /// <param name="warning">Warning message</param>
        protected void SetBusinessRuleWarning(string warning)
        {
            if (string.IsNullOrEmpty(warning))
            {
                return;
            }

            if (_businessWarnings.Contains(warning))
            {
                return;
            }

            _businessWarnings.Add(warning);
        }

        /// <summary>
        /// Sets a specific business rule warning.
        /// </summary>
        /// <param name="warningFormat">The warning format.</param>
        /// <param name="args">The formatting arguments.</param>
        protected void SetBusinessRuleWarning(string warningFormat, params object[] args)
        {
            SetBusinessRuleWarning(string.Format(warningFormat, args));
        }

        /// <summary>
        /// Returns a message that contains all the current warnings and automatically determines the name of the object.
        /// </summary>
        /// <returns>
        /// Warning string or empty in case of no warnings.
        /// </returns>
        public string GetWarningMessage()
        {
            return GetWarningMessage(null);
        }

        /// <summary>
        /// Returns a message that contains all the current warnings.
        /// </summary>
        /// <param name="userFriendlyObjectName">Name of the user friendly object.</param>
        /// <returns>
        /// Warning string or empty in case of no warnings.
        /// </returns>
        public string GetWarningMessage(string userFriendlyObjectName)
        {
            if (!HasWarnings)
            {
                return string.Empty;
            }

            if (string.IsNullOrEmpty(userFriendlyObjectName))
            {
                // Use the real entity name (stupid developer that passes a useless value)
                userFriendlyObjectName = GetType().Name;
            }

            StringBuilder messageBuilder = new StringBuilder();
            messageBuilder.AppendLine(string.Format(CultureInfo.CurrentUICulture, Resources.WarningsFound, userFriendlyObjectName));
            messageBuilder.Append(GetListMessages(_fieldWarnings, _businessWarnings));

            return messageBuilder.ToString();
        }
        #endregion

        #region IDataErrorInfo Members
        /// <summary>
        /// Gets the current error.
        /// </summary>
        string IDataErrorInfo.Error
        {
            get
            {
                string error = string.Empty;

                if (!IsValidated)
                {
                    Validate();
                }

                if ((_businessErrors != null) && (_businessErrors.Count > 0))
                {
                    error = _businessErrors[0];
                }

                return error;
            }
        }

        /// <summary>
        /// Gets an error for a specific column.
        /// </summary>
        /// <param name="columnName">Column name.</param>
        /// <returns>Error.</returns>
        string IDataErrorInfo.this[string columnName]
        {
            get
            {
                string error = string.Empty;

                if (!IsValidated)
                {
                    Validate();
                }

                if ((_fieldErrors != null) && _fieldErrors.ContainsKey(columnName))
                {
                    error = _fieldErrors[columnName];
                }

                return error;
            }
        }

        /// <summary>
        /// Sets a specific field error.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <param name="errorFormat">The error format.</param>
        /// <param name="args">The formatting arguments.</param>
        protected void SetFieldError(PropertyData property, string errorFormat, params object[] args)
        {
            SetFieldError(property.Name, string.Format(errorFormat, args));
        }

        /// <summary>
        /// Sets a specific field error.
        /// </summary>
        /// <param name="property"><see cref="PropertyData"/> of the property.</param>
        /// <param name="error">Error message.</param>
        protected void SetFieldError(PropertyData property, string error)
        {
            SetFieldError(property.Name, error);
        }

        /// <summary>
        /// Sets a specific field error.
        /// </summary>
        /// <param name="property">Name of the property.</param>
        /// <param name="errorFormat">The error format.</param>
        /// <param name="args">The formatting arguments.</param>
        protected void SetFieldError(string property, string errorFormat, params object[] args)
        {
            SetFieldError(property, string.Format(errorFormat, args));
        }

        /// <summary>
        /// Sets a specific field error.
        /// </summary>
        /// <param name="property">Name of the property.</param>
        /// <param name="error">Error message.</param>
        protected void SetFieldError(string property, string error)
        {
            if (string.IsNullOrEmpty(error))
            {
                return;
            }

            _fieldErrors[property] = error;
        }

        /// <summary>
        /// Sets a specific business rule error.
        /// </summary>
        /// <param name="errorFormat">The error format.</param>
        /// <param name="args">The formatting arguments.</param>
        protected void SetBusinessRuleError(string errorFormat, params object[] args)
        {
            SetBusinessRuleError(string.Format(errorFormat, args));
        }

        /// <summary>
        /// Sets a specific business rule error.
        /// </summary>
        /// <param name="error">Error message</param>
        protected void SetBusinessRuleError(string error)
        {
            if (string.IsNullOrEmpty(error))
            {
                return;
            }

            if (_businessErrors.Contains(error))
            {
                return;
            }

            _businessErrors.Add(error);
        }

        /// <summary>
        /// Returns a message that contains all the current errors and automatically determines the name of the object.
        /// </summary>
        /// <returns>
        /// Error string or empty in case of no errors.
        /// </returns>
        public string GetErrorMessage()
        {
            return GetErrorMessage(null);
        }

        /// <summary>
        /// Returns a message that contains all the current errors.
        /// </summary>
        /// <param name="userFriendlyObjectName">Name of the user friendly object.</param>
        /// <returns>
        /// Error string or empty in case of no errors.
        /// </returns>
        public string GetErrorMessage(string userFriendlyObjectName)
        {
            if (!HasErrors)
            {
                return string.Empty;
            }

            if (string.IsNullOrEmpty(userFriendlyObjectName))
            {
                // Use the real entity name (stupid developer that passes a useless value)
                userFriendlyObjectName = GetType().Name;
            }

            StringBuilder messageBuilder = new StringBuilder();
            messageBuilder.AppendLine(string.Format(CultureInfo.CurrentUICulture, Resources.ErrorsFound, userFriendlyObjectName));
            messageBuilder.Append(GetListMessages(_fieldErrors, _businessErrors));

            return messageBuilder.ToString();
        }
        #endregion
        #endregion

        #region IDisposable Members
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposeManagedResources"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposeManagedResources)
        {
            if (!IsDisposed)
            {
                if (disposeManagedResources)
                {
                    OnUninitializing();

                    UnsubscribeAllObjectsFromNotifyChanged();

                    if (AutomaticallyDisposeChildObjectsOnDispose)
                    {
                        foreach (KeyValuePair<string, object> propertyValue in _propertyValues)
                        {
                            IDisposable disposable = propertyValue.Value as IDisposable;
                            if (disposable != null)
                            {
                                if (!_typesNotToDispose.Contains(disposable.GetType().Name))
                                {
                                    disposable.Dispose();
                                }
                                else
                                {
                                    Log.Debug(TraceMessages.PropertyNotDisposedSinceItIsInIgnoreList, propertyValue.Key, disposable.GetType().Name);
                                }
                            }
                        }
                    }

                    OnUninitialized();

                    // Disabled by Geert van Horrik on 2010-11-14 because this caused some unwanted behavior in WPF because the
                    // event manager was removed from the events (now it's up to the user to unsubscribe events correctly)
                    // Disconnect all event subscribers, this includes Uninitialized-event, so this must be the last action.
                    ////ForcedClearEvents();

                    IsDisposed = true;
                }
                else if (!IsDisposed)
                {
                    string message = string.Format(TraceMessages.ObjectNotDisposedCorrectly, GetType().Name);

                    Debug.Assert(true, message);

                    Log.Warn(message);
                }
            }
        }

        /// <summary>
        /// Clear all DataObjectBase events.
        /// </summary>
        private void ForcedClearEvents()
        {
#if !SILVERLIGHT
            if (PropertyChanging != null)
            {
                foreach (PropertyChangingEventHandler subscriber in PropertyChanging.GetInvocationList())
                {
                    PropertyChanging -= subscriber;
                }
            }
#endif

            if (PropertyChanged != null)
            {
                foreach (PropertyChangedEventHandler subscriber in PropertyChanged.GetInvocationList())
                {
                    PropertyChanged -= subscriber;
                }
            }

            ClearEventHandler(Validating);
            ClearEventHandler(ValidatingFields);
            ClearEventHandler(ValidatedFields);
            ClearEventHandler(ValidatingBusinessRules);
            ClearEventHandler(ValidatedBusinessRules);
            ClearEventHandler(Validated);
            ClearEventHandler(Initialized);
            ClearEventHandler(Uninitialized);
            ClearEventHandler(Deserialized);
        }

        /// <summary>
        /// Force a disconnect for all subscribers to the event handler.
        /// </summary>
        /// <param name="eventHandler">The event handler.</param>
        private static void ClearEventHandler(EventHandler eventHandler)
        {
            if (eventHandler != null)
            {
                foreach (EventHandler subscriber in eventHandler.GetInvocationList())
                {
                    eventHandler -= subscriber;
                }
            }
        }
        #endregion

        #region ICloneable Members
        /// <summary>
        /// Clones the current object.
        /// </summary>
        /// <returns>Clone of the object or <c>null</c> if unsuccessful.</returns>
        public object Clone()
        {
            // First, try without redirects (why would we even need them, it costs a lot of performance)
            object clone = Clone(false);
            if (clone != null)
            {
                return clone;
            }

            // If we got here, cloning without redirects failed, try again with redirects as a last resort
            clone = Clone(true);
            return clone;
        }

        /// <summary>
        /// Clones the current object with the option to enable redirects.
        /// </summary>
        /// <param name="enableRedirects">if set to <c>true</c>, enable supports for redirects.</param>
        /// <returns>
        /// Clone of the object or <c>null</c> if unsuccessful.
        /// </returns>
        private object Clone(bool enableRedirects)
        {
            try
            {
#if SILVERLIGHT
                MemoryStream stream = new MemoryStream();
                DataContractSerializer serializer = SerializationHelper.GetDataContractSerializer(GetType(), string.Empty);

                serializer.WriteObject(stream, this);

                stream.Position = 0L;

                object clone = serializer.ReadObject(stream);

                stream.Close();

                return clone;
#else
                MemoryStream stream = new MemoryStream();
                BinaryFormatter serializer = SerializationHelper.GetBinarySerializer(enableRedirects);

                serializer.Serialize(stream, this);

                stream.Position = 0L;

                object clone = serializer.Deserialize(stream);

                stream.Close();

                return clone;
#endif
            }
            catch (Exception ex)
            {
                Log.Error(ex);

                return null;
            }
        }
        #endregion

        #region IEditableObject Members
        /// <summary>
        /// Begins an edit on an object.
        /// </summary>
        public void BeginEdit()
        {
            if (_backup != null)
            {
                throw new InvalidOperationException(Exceptions.BeginEditCannotBeInvokedTwice);
            }

            _backup = new BackupData(this);
        }

        /// <summary>
        /// Discards changes since the last <see cref="IEditableObject.BeginEdit()"/> call.
        /// </summary>
        public void CancelEdit()
        {
            if (_backup == null)
            {
                return;
            }

            _backup.RestoreBackup();
            _backup = null;
        }

        /// <summary>
        /// Pushes changes since the last <see cref="IEditableObject.BeginEdit()"/> call.
        /// </summary>
        public void EndEdit()
        {
            _backup = null;
        }
        #endregion

        #region Serialization
        #region Loading
        /// <summary>
        /// Loads the object from a file using binary formatting.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(string fileName) where T : class
        {
            return Load<T>(fileName, false);
        }

        /// <summary>
        /// Loads the object from a file using binary formatting.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(string fileName, bool enableRedirects) where T : class
        {
#if SILVERLIGHT
            return Load<T>(fileName, SerializationMode.Xml, enableRedirects);
#else
            return Load<T>(fileName, SerializationMode.Binary, enableRedirects);
#endif
        }

        /// <summary>
        /// Loads the object from a file using a specific formatting.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(string fileName, SerializationMode mode) where T : class
        {
            return Load<T>(fileName, mode, false);
        }

        /// <summary>
        /// Loads the object from a file using a specific formatting.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(string fileName, SerializationMode mode, bool enableRedirects) where T : class
        {
            using (Stream stream = new FileStream(fileName, FileMode.Open))
            {
                return Load<T>(stream, mode, enableRedirects);
            }
        }

        /// <summary>
        /// Loads the object from an XmlDocument object.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(XDocument xmlDocument) where T : class
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (XmlWriter writer = XmlWriter.Create(memoryStream))
                {
                    xmlDocument.Save(writer);
                }

                memoryStream.Position = 0L;

                return Load<T>(memoryStream, SerializationMode.Xml, false);
            }
        }

        /// <summary>
        /// Loads the object from a stream using binary formatting.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(byte[] bytes) where T : class
        {
            return Load<T>(bytes, false);
        }

        /// <summary>
        /// Loads the object from a stream.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(byte[] bytes, bool enableRedirects) where T : class
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                memoryStream.Write(bytes, 0, bytes.Length);

                memoryStream.Position = 0L;

#if SILVERLIGHT
                return Load<T>(memoryStream, SerializationMode.Xml, enableRedirects);
#else
                return Load<T>(memoryStream, SerializationMode.Binary, enableRedirects);
#endif
            }
        }

        /// <summary>
        /// Loads the object from a stream using binary formatting.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(Stream stream) where T : class
        {
            return Load<T>(stream, false);
        }

        /// <summary>
        /// Loads the specified stream.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(Stream stream, bool enableRedirects) where T : class
        {
#if SILVERLIGHT
            return Load<T>(stream, SerializationMode.Xml, enableRedirects);
#else
            return Load<T>(stream, SerializationMode.Binary, enableRedirects);
#endif
        }

        /// <summary>
        /// Loads the object from a stream using a specific formatting.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(Stream stream, SerializationMode mode) where T : class
        {
            return Load<T>(stream, mode, false);
        }

        /// <summary>
        /// Loads the object from a stream using a specific formatting.
        /// </summary>
        /// <typeparam name="T">Type of the object that should be loaded.</typeparam>
        /// <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<T>(Stream stream, SerializationMode mode, bool enableRedirects) where T : class
        {
            object result = null;

            string stopwatchName = string.Format(CultureInfo.CurrentCulture, TraceMessages.LoadingObject, typeof(T).Name, mode);
            Log.StartStopwatchTrace(stopwatchName);

            switch (mode)
            {
#if SILVERLIGHT
                case SerializationMode.JSON:
                    // TODO: Add support for JSON
                    break;
#else
                case SerializationMode.Binary:
                    try
                    {
                        BinaryFormatter binaryFormatter = SerializationHelper.GetBinarySerializer(enableRedirects);

                        Log.Debug(TraceMessages.ResettingStreamPositionToZero);

                        stream.Position = 0L;

                        Log.Debug(TraceMessages.ResettedStreamPositionToZero);

                        Log.Debug(TraceMessages.DeserializingBinaryStream, typeof(T).Name);

                        result = binaryFormatter.Deserialize(stream);

                        Log.Debug(TraceMessages.DeserializedBinaryStream);
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex, TraceMessages.FailedToDeserializeBinaryObject);
                    }

                    break;
#endif

                case SerializationMode.Xml:
                    using (XmlReader xmlReader = XmlReader.Create(stream))
                    {
#if SILVERLIGHT
                        result = typeof(T).InvokeMember(null, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null, null, new object[] { });
#else
                        result = Activator.CreateInstance(typeof(T), true);
#endif

                        IXmlSerializable xmlSerializable = (IXmlSerializable)result;
                        xmlSerializable.ReadXml(xmlReader);
                    }

                    break;
            }

            Log.StopStopwatchTrace(stopwatchName);

            if (result is DataObjectBase)
            {
                ((DataObjectBase)result).Mode = mode;
            }

            return (T)result;
        }
        #endregion

#if !SILVERLIGHT
        /// <summary>
        /// Retrieves the actual data from the serialization info.
        /// </summary>
        /// <param name="info">The <see cref="SerializationInfo"/> to get the data from.</param>
        /// <remarks>
        /// This method is called from the OnDeserialized method, thus all child objects
        /// are serialized and available at the time this method is called.
        /// <para />
        /// Only use this method to support older serialization techniques. When using this class
        /// for new objects, all serialization is handled automatically.
        /// </remarks>
        protected virtual void GetDataFromSerializationInfo(SerializationInfo info)
        {
            // Not implemented by default
        }

        /// <summary>
        /// Retrieves the actual data from the serialization info for the properties registered
        /// on this object.
        /// </summary>
        /// <param name="info">The <see cref="SerializationInfo"/> to get the data from.</param>
        protected void GetDataFromSerializationInfoInternal(SerializationInfo info)
        {
            if (info == null)
            {
                Log.Warn(TraceMessages.SerializationInfoIsNull);
                return;
            }

            if (!IsDeserializedDataAvailable)
            {
                return;
            }

            if (IsDeserialized)
            {
                Log.Debug(TraceMessages.ObjectAlreadyDeserialized, GetType().Name);
                return;
            }

            try
            {
                List<KeyValuePair<string, object>> properties = (List<KeyValuePair<string, object>>)SerializationHelper.GetObject(info,
                    "Properties", typeof(List<KeyValuePair<string, object>>), new List<KeyValuePair<string, object>>());
                foreach (KeyValuePair<string, object> property in properties)
                {
                    if (IsPropertyRegistered(property.Key))
                    {
                        if (property.Value != null)
                        {
                            if ((property.Value is ICollection) && (property.Value is IDeserializationCallback))
                            {
                                IDeserializationCallback propertyDeserializationCallback = property.Value as IDeserializationCallback;

                                // Call it since collections need this call to contain valid items
                                propertyDeserializationCallback.OnDeserialization(this);
                            }
                        }

                        lock (_propertyValuesLock)
                        {
                            _propertyValues[property.Key] = property.Value;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, TraceMessages.ErrorDeserializingObject, GetType().Name);
            }

            // Allow developers to support backwards compatibility
            GetDataFromSerializationInfo(info);

            FinishDeserialization();
        }

        /// <summary>
        /// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object.
        /// </summary>
        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data.</param>
        /// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization.</param>
        /// <exception cref="T:System.Security.SecurityException">
        /// The caller does not have the required permission.
        /// </exception>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            List<KeyValuePair<string, object>> properties;

            lock (_propertyValuesLock)
            {
                properties = ConvertDictionaryToListAndExcludeNonSerializableObjects(_propertyValues);
            }

            info.AddValue("Properties", properties, typeof(List<KeyValuePair<string, object>>));
        }

        /// <summary>
        /// Invoked when the deserialization of the object graph is complete.
        /// </summary>
        /// <param name="context">The <see cref="StreamingContext"/>..</param>
        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            Log.Debug(TraceMessages.ReceivedOnDeserialized, GetType().Name);

            IsDeserializedDataAvailable = true;

            GetDataFromSerializationInfoInternal(_serializationInfo);
        }

        /// <summary>
        /// Invoked when the deserialization of the object graph is complete.
        /// </summary>
        /// <param name="sender">The object that has started deserializing.</param>
        public void OnDeserialization(object sender)
        {
            Log.Debug(TraceMessages.ReceivedOnDeserialization, GetType().Name);

            try
            {
                lock (_propertyValuesLock)
                {
                    foreach (KeyValuePair<string, object> property in _propertyValues)
                    {
                        CallOnDeserializationCallback(property.Value);

                        ICollection collection = property.Value as ICollection;
                        if (collection != null)
                        {
                            foreach (object item in collection)
                            {
                                CallOnDeserializationCallback(item);
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                Log.Warn(TraceMessages.FailedToCallOnDeserializationForChildObjects);
            }
        }

        /// <summary>
        /// Calls the <see cref="IDeserializationCallback.OnDeserialization"/> method on the object if possible.
        /// </summary>
        /// <param name="obj">The object that has finished deserializing.</param>
        private void CallOnDeserializationCallback(object obj)
        {
            if (obj == null)
            {
                return;
            }

            IDeserializationCallback propertyCallback = obj as IDeserializationCallback;
            if (propertyCallback != null)
            {
                propertyCallback.OnDeserialization(this);
            }
        }
#endif

        /// <summary>
        /// Converts a dictionary to a list for serialization purposes.
        /// </summary>
        /// <param name="dictionary">Dictionary to convert.</param>
        /// <returns>List that contains all the values of the dictionary.</returns>
        /// <remarks>
        /// This method is required because Dictionary can't be serialized.
        /// </remarks>
        private List<KeyValuePair<string, object>> ConvertDictionaryToListAndExcludeNonSerializableObjects(Dictionary<string, object> dictionary)
        {
            List<KeyValuePair<string, object>> listToSerialize = new List<KeyValuePair<string, object>>();

            foreach (KeyValuePair<string, object> dictionaryItem in dictionary)
            {
                PropertyData propertyData = GetPropertyData(dictionaryItem.Key);
                if (!propertyData.IsSerializable)
                {
                    Log.Warn(TraceMessages.PropertyNotSerializableSoExcludedFromSerialization, propertyData.Name);

                    continue;
                }

                if (!propertyData.IncludeInSerialization)
                {
                    Log.Debug(TraceMessages.PropertyFlaggedToBeExcludedFromSerialization, propertyData.Name);

                    continue;
                }

                Log.Debug(TraceMessages.AddingPropertyToListOfObjectsToSerialize, propertyData.Name);

                listToSerialize.Add(dictionaryItem);
            }

            return listToSerialize;
        }

        /// <summary>
        /// Converts a list to a dictionary for serialization purposes.
        /// </summary>
        /// <param name="list">List to convert.</param>
        /// <returns>Dictionary that contains all the values of the list.</returns>
        private Dictionary<string, object> ConvertListToDictionary(IEnumerable<KeyValuePair<string, object>> list)
        {
            Dictionary<string, object> result = new Dictionary<string, object>();

            foreach (KeyValuePair<string, object> listItem in list)
            {
                if (IsPropertyRegistered(listItem.Key))
                {
#if !SILVERLIGHT
                    if (listItem.Value != null)
                    {
                        if ((listItem.Value is ICollection) && (listItem.Value is IDeserializationCallback))
                        {
                            IDeserializationCallback propertyDeserializationCallback = listItem.Value as IDeserializationCallback;

                            propertyDeserializationCallback.OnDeserialization(this);
                        }
                    }
#endif

                    // Store the value (since deserialized values always override default values)
                    result[listItem.Key] = listItem.Value;
                }
            }

            return result;
        }
        #endregion
    }

    /// <summary>
    /// Abstract class that serves as a base class for serializable objects.
    /// </summary>
    /// <typeparam name="TDataObject">Type that the class should hold (same as the defined type).</typeparam>
#if SILVERLIGHT
    [DataContract]
#else
    [Serializable]
#endif
    public abstract class DataObjectBase<TDataObject> : DataObjectBase, IDataObjectBase<TDataObject> where TDataObject : class
    {
        #region Constructor & destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="DataObjectBase{TDataObject}"/> class.
        /// </summary>
        protected DataObjectBase()
        {
        }

#if !SILVERLIGHT
        /// <summary>
        /// Initializes a new instance of the <see cref="DataObjectBase&lt;TDataObject&gt;"/> class.
        /// </summary>
        /// <para />
        /// Only constructor for the DataObjectBase.
        /// <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 DataObjectBase(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
#endif
        #endregion

        #region Properties
        #endregion

        #region Methods
        #endregion

        #region IEquatable<T> Members
        /// <summary>
        /// Checks whether this object equals another object of the same type.
        /// </summary>
        /// <param name="other">The other object.</param>
        /// <returns><c>true</c> if the objects are equal; otherwise <c>false</c></returns>
        public bool Equals(TDataObject other)
        {
            return this == other;
        }
        #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)

Share

About the Author

Geert van Horrik
Software Developer CatenaLogic
Netherlands Netherlands
I am Geert van Horrik, and I have studied Computer Science in the Netherlands.
 
I love to write software using .NET (especially the combination of WPF and C#). I am also the lead developer of Catel, an open-source application development framework for WPF, Silverlight, WP7 and WinRT with the focus on MVVM.
 
I have my own company since January 1st 2007, called CatenaLogic. This company develops commercial and non-commercial software.
 
To download (or buy) applications I have written, visit my website: http://www.catenalogic.com
Follow on   Twitter

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 28 Jan 2011
Article Copyright 2011 by Geert van Horrik
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid