none
Another XML serialization problem [DataContractSerializer] RRS feed

  • Question

  • I am becoming increasingly frustrated with .Net serialization, I thought that Microsoft had finally dealt with the issue with the BinaryFormatter but it does not work in the UWP so I tried to find another solution to save private class members etc and the best that I encountered was the DataContractSerializer but it had several issues that I needed to work around because it failed - here is one that appears to be a show stopper for me.

    Desrialization of the FooCollection class using the DataContractSerializer fails to serialize the two string properties, as usual the BinaryFormatter works.

    The example is a Console app referencing System.Runtime.Serialization

    using System;
    using System.Runtime.Serialization;
    
    namespace AnotherDataContractIssue
    {
        [Serializable]
        [CollectionDataContract(IsReference = true)]
        public sealed class FooCollection : ClassCollection<Foo>
        {
            #region Construction
    
            public FooCollection()
            {
                this._guid = Guid.NewGuid();
            }
    
            public FooCollection(FooCollection copy)
            {
                if (null == copy)
                    throw new ArgumentNullException(nameof(copy));
    
                this._guid = Guid.NewGuid();
                this._name = copy._name;
                this._comment = copy._comment;
    
                this.Capacity = copy.Capacity;
                foreach (Foo Foo in copy)
                    Add(Foo.Clone());
            }
    
            #endregion Construction
    
            #region Fields
    
            [DataMember]
            private Guid _guid;
            [DataMember]
            private string _name;
            [DataMember]
            private string _comment;
    
            #endregion Fields
    
            #region FooCollection
    
            public Guid Key
            {
                get { return this._guid; }
            }
    
            public string Name
            {
                get { return this._name; }
                set
                {
                    Verify(value);
                    this._name = value;
                }
            }
    
            public string Comment
            {
                get { return this._comment; }
                set { this._comment = value; }
            }
    
            public static void Verify(string name)
            {
                if (string.IsNullOrEmpty(name))
                    throw new ArgumentOutOfRangeException("name", 
                        "name null or empty!");
    
                char nextChar = name[0];
                if (!char.IsLetter(nextChar) &&
                    nextChar != '_')
                    throw new ArgumentOutOfRangeException("name", name,
                        "Bad Foo start");
    
                int count = name.Length;
                for (int index = 1; index < count; index++)
                {
                    nextChar = name[index];
                    if (!char.IsLetterOrDigit(nextChar) &&
                        nextChar != '_')
                        throw new ArgumentOutOfRangeException("name", name,
                            "Bad Foo characters");
                }
            }
    
            #endregion FooCollection
    
            #region ICloneable
    
            public new FooCollection Clone()
            {
                return (FooCollection)DoClone();
            }
    
            protected override object DoClone()
            {
                FooCollection clone = new FooCollection(this) { _guid = this._guid };
                return clone;
            }
    
            #endregion Clone
        }
    }


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    
    namespace AnotherDataContractIssue
    {
        [Serializable]
        [CollectionDataContract(IsReference = true)]
        public class ClassCollection<T> : EquatableCollection<T>, ICloneable
            where T : class, IEquatable<T>, ICloneable
        {
            #region ctor
    
            public ClassCollection()
            {
            }
    
            public ClassCollection(IEnumerable<T> collection) :
                base(collection)
            {
            }
    
            public ClassCollection(int capacity) :
                base(capacity)
            {
            }
    
            #endregion ctor
    
            #region ICloneable
    
            public ClassCollection<T> Clone()
            {
                return (ClassCollection<T>)DoClone();
            }
    
            protected virtual object DoClone()
            {
                return new ClassCollection<T>(
                    this.Select(i => (T)i.Clone()).ToList());
            }
    
            object ICloneable.Clone()
            {
                return DoClone();
            }
    
            #endregion ICloneable
        }
    }
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    
    namespace AnotherDataContractIssue
    {
        [Serializable]
        [CollectionDataContract(IsReference = true)]
        public class EquatableCollection<T> : List<T>,
            IEquatable<EquatableCollection<T>>
            where T : IEquatable<T>
        {
            #region ctor
    
            public EquatableCollection()
            {
            }
    
            public EquatableCollection(IEnumerable<T> collection) :
                base(collection)
            {
            }
    
            public EquatableCollection(int capacity) :
                base(capacity)
            {
            }
    
            #endregion ctor
    
            #region IEquatable
    
            public bool Equals(EquatableCollection<T> other)
            {
                if (null == other)
                    return false;
    
                int thisCount = this.Count;
                if (thisCount != other.Count)
                    return false;
    
                for (int index = 0; index < thisCount; index++)
                    if (!this[index].Equals(other[index]))
                        return false;
    
                return true;
            }
    
            public override bool Equals(object obj)
            {
                return Equals(obj as EquatableCollection<T>);
            }
    
            public static bool operator ==(EquatableCollection<T> left,
                EquatableCollection<T> right)
            {
                if (ReferenceEquals(left, right))
                {
                    return true;
                }
    
                if (ReferenceEquals(left, null))
                {
                    return false;
                }
    
                if (ReferenceEquals(right, null))
                {
                    return false;
                }
    
                return left.Equals(right);
            }
    
            public static bool operator !=(EquatableCollection<T> left,
                EquatableCollection<T> right)
            {
                return !(left == right);
            }
    
            #endregion IEquatable
        }
    }


    using System;
    using System.Runtime.Serialization;
    
    namespace AnotherDataContractIssue
    {
        [Serializable]
        [DataContract(IsReference = true)]
        public abstract class Foo : IEquatable<Foo>, ICloneable
        {
            #region Fields
    
            [DataMember]
            private string _comment;
            [DataMember]
            private bool _enabled = true;
    
            #endregion Fields
    
            #region Properties
    
            public bool Enabled
            {
                get { return this._enabled; }
                set { this._enabled = value; }
            }
    
            public string Comment
            {
                get { return this._comment; }
                set { this._comment = value; }
            }
    
            public abstract string Name { get; }
    
            #endregion Properties
    
            #region IEquatable
    
            public bool Equals(Foo other)
            {
                if (ReferenceEquals(null, other))
                    return false;
    
                return this._enabled == other._enabled && this._comment == other._comment;
            }
    
            public override bool Equals(object obj)
            {
                return Equals(obj as Foo);
            }
    
            public static bool operator ==(Foo left, Foo right)
            {
                if (ReferenceEquals(left, right))
                {
                    return true;
                }
    
                if (ReferenceEquals(left, null))
                {
                    return false;
                }
    
                if (ReferenceEquals(right, null))
                {
                    return false;
                }
    
                return left.Equals((object)right);
            }
    
            public static bool operator !=(Foo left, Foo right)
            {
                return !(left == right);
            }
    
            public override int GetHashCode()
            {
                unchecked
                {
                    return ((this._comment != null ? this._comment.GetHashCode() : 0) * 397)
                           ^ this._enabled.GetHashCode();
                }
            }
    
            #endregion
    
            #region ICloneable
    
            public Foo Clone()
            {
                return (Foo)(DoClone());
            }
    
            protected virtual object DoClone()
            {
                return MemberwiseClone();
            }
    
            object ICloneable.Clone()
            {
                return DoClone();
            }
    
            #endregion ICloneable
        }
    }


    using System.Runtime.Serialization;
    
    namespace AnotherDataContractIssue
    {
        [System.Serializable]
        [DataContract(IsReference = true)]
        public class Foot : Foo
        {
            public override string Name => "My foot!";
        }
    }


    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Xml;
    
    namespace AnotherDataContractIssue
    {
        public class SimpleDataContractResolver : DataContractResolver
        {
            public SimpleDataContractResolver()
            {
                var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
                foreach (Assembly assembly in loadedAssemblies)
                    AddAssemblyName(assembly.FullName);
            }
    
            private static readonly List<string> _loadedAssemblyNames = new List<string>();
    
            private static void AddAssemblyName(string name)
            {
                if (!(name.StartsWith("System.", StringComparison.CurrentCultureIgnoreCase) ||
                    name.StartsWith("Microsoft.", StringComparison.CurrentCultureIgnoreCase) ||
                    name.StartsWith("mscorlib,", StringComparison.CurrentCultureIgnoreCase) ||
                    name.StartsWith("System,", StringComparison.CurrentCultureIgnoreCase) ||
                    name.StartsWith("Accessibility,", StringComparison.CurrentCultureIgnoreCase)))
                    _loadedAssemblyNames.Add(name);
            }
    
            public override bool TryResolveType(Type type, Type declaredType,
                DataContractResolver knownTypeResolver,
                out XmlDictionaryString typeName,
                out XmlDictionaryString typeNamespace)
            {
                if (!knownTypeResolver.TryResolveType(type, declaredType, null,
                    out typeName, out typeNamespace))
                {
                    XmlDictionary dictionary = new XmlDictionary();
                    typeName = dictionary.Add(type.FullName);
                    typeNamespace = dictionary.Add(
                        type.GetTypeInfo().Assembly.FullName);
                }
    
                return true;
            }
    
            public override Type ResolveName(string typeName, string typeNamespace,
                Type declaredType, DataContractResolver knownTypeResolver)
            {
                Type type = knownTypeResolver.ResolveName(typeName, typeNamespace,
                    declaredType, null);
                // "http://schemas.datacontract.org/2004/07/" is prepended to typeNamespace
                int lastIndexOfForwardSlash = typeNamespace.LastIndexOf('/');
                if (-1 != lastIndexOfForwardSlash)
                    typeNamespace = typeNamespace.Remove(0, lastIndexOfForwardSlash + 1);
                else if (null == type)
                    type = Type.GetType(typeName + ", " + typeNamespace);
    
                // Try the known assemblies names
                if (null == type)
                {
                    foreach (string name in _loadedAssemblyNames)
                    {
                        string fullName = $"{typeNamespace}.{typeName}";
                        type = Type.GetType(fullName + ", " + name);
                        if (null != type)
                            break;
                    }
                }
    
                return type;
            }
        }
    }


    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Xml;
    
    namespace AnotherDataContractIssue
    {
        class Program
        {
            const string NAME = "MyFeet";
            const string COMMENT = "Are flat";
    
            static void Main(string[] args)
            {
                var fooCollection = new FooCollection()
                {
                    Name = NAME,
                    Comment = COMMENT
                };
    
                fooCollection.Add(new Foot());
                fooCollection.Add(new Foot());
    
                //using (Stream stream = new FileStream("fooCollection.bin",
                //    FileMode.Create, FileAccess.Write, FileShare.None))
                //{
                //    BinaryFormatter formatter = new BinaryFormatter();
                //    formatter.Serialize(stream, fooCollection);
                //    stream.Flush();
                //}
                using (FileStream stream = new FileStream("fooCollection.xmlbin",
                    FileMode.Create))
                {
                    var writer =
                        System.Xml.XmlDictionaryWriter.CreateBinaryWriter(stream);
                    var settings = new DataContractSerializerSettings
                    {
                        DataContractResolver =
                            new SimpleDataContractResolver(),
                        PreserveObjectReferences = true
                    };
                    var dcs = new DataContractSerializer(
                        typeof(FooCollection), settings);
                    dcs.WriteObject(writer, fooCollection);
                    writer.Flush();
                }
    
                FooCollection deserialized;
    
                //using (Stream stream = File.Open("fooCollection.bin", FileMode.Open,
                //    FileAccess.Read, FileShare.Read))
                //{
                //    BinaryFormatter formatter = new BinaryFormatter();
                //    deserialized = (FooCollection)
                //        (formatter.Deserialize(stream));
                //    stream.Flush();
                //}
                using (FileStream stream = new FileStream("fooCollection.xmlbin",
                    FileMode.Open))
                {
                    var reader = XmlDictionaryReader.CreateBinaryReader(stream,
                        new XmlDictionaryReaderQuotas());
                    var settings = new DataContractSerializerSettings
                    {
                        DataContractResolver =
                            new SimpleDataContractResolver(),
                        PreserveObjectReferences = true
                    };
                    var dcs = new DataContractSerializer(
                        typeof(FooCollection), settings);
                    deserialized = (FooCollection)dcs.ReadObject(reader, true);
                    stream.Flush();
                }
    
                Trace.Assert(COMMENT == deserialized.Comment);
                Trace.Assert(NAME == deserialized.Name);
                Trace.Assert(2 == deserialized.Count);
    
                Console.WriteLine("OK");
    
                Console.ReadLine();
            }
        }
    }

    I am using VS Community 2017 15.7.5 with .NET 4.7.03056 targeting .NET 4.6.2

    It seems that the SimpleDataContractResolver is never called for FooCollection which is I suppose reasonable as it is specified to the serializer.

    Interestingly changing the DataContract attribute from [CollectionDataContract(IsReference = true)] to
    [DataContract(IsReference = true)] for FooCollection allows the fields to seriaze and deserialize correctly but then the elements of the collection are not serialized!




    Thursday, July 26, 2018 7:11 PM

All replies

  • Hi ellisisware,

    Thank you for posting here.

    For your question, do you want to use it in UWP? If yes, I will move it to UWP forum.

    According to your description, you provide some code samples. What is the error? What do you want to get? Could you provide a file which could reproduce the error and provide what you want to get?

    Best Regards,

    Wendy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Friday, July 27, 2018 7:31 AM
    Moderator
  • I will eventually want to use it in the UWP but I have not tested it there yet, first it needs to work on the desktop which it does not, so it should not be moved to the UWP forum yet.

    If you glance through the code provided you will see that it is a test of the serialization and deserialization of a 'FooCollection' object, the tests at the end of the last code file provided (program.cs Main method)  could be more thorough in verifying this, but incomplete as they are they show that the deserialized objectis not the same as what was serialized.

    The following could be added:

    Trace.Assert(fooCollection.Key == deserialized.Key);

    Which also fails.

    Even better would be:

    Trace.Assert(deserialized == fooCollection);

    In the same file one may see code commented out that demonstrates correct serialization and deserialization by the BinaryFormatter.

    Thank you.

    Friday, July 27, 2018 5:56 PM