简体   繁体   中英

Binary Deserializing implementations of an abstract class

I have an abstract class that I'm trying to serialize and deserialize the concrete implementations of. In my abstract base class I have this:

[DataContract]
public class MyAbstractBase
{
    [DataMember]
    public string Foo { get; set; }

    // some other abstract methods that derived classes have to implement
}

And to that class I add a method to serialize:

public string SerializeBase64()
{
    // Serialize to a base 64 string
    byte[] bytes;
    long length = 0;
    MemoryStream ws = new MemoryStream();
    DataContractSerializer serializer = new DataContractSerializer(this.GetType());
    XmlDictionaryWriter binaryDictionaryWriter = XmlDictionaryWriter.CreateBinaryWriter(ws);
    serializer.WriteObject(binaryDictionaryWriter, this);
    binaryDictionaryWriter.Flush();
    length = ws.Length;
    bytes = ws.GetBuffer();
    string encodedData = bytes.Length + ":" + Convert.ToBase64String(bytes, 0, bytes.Length, Base64FormattingOptions.None);
    return encodedData;
}

This seems to work fine, in that it produces "something" and doesn't actually throw any errors.

Of course, the problem comes with deserialization. I added this:

public static MyAbstractBase DeserializeBase64(string s)
{
    int p = s.IndexOf(':');
    int length = Convert.ToInt32(s.Substring(0, p));
    // Extract data from the base 64 string!
    byte[] memorydata = Convert.FromBase64String(s.Substring(p + 1));
    MemoryStream rs = new MemoryStream(memorydata, 0, length);
    DataContractSerializer serializer = new DataContractSerializer(typeof(MyAbstractBase ), new List<Type>() { typeof(SomeOtherClass.MyDerivedClass) });
    XmlDictionaryReader binaryDictionaryReader = XmlDictionaryReader.CreateBinaryReader(rs, XmlDictionaryReaderQuotas.Max);   
    return (MyAbstractBase)serializer.ReadObject(binaryDictionaryReader);
}

I thought by adding the "known types" to my DataContractSerializer , it would be able to figure out how to deserialize the derived class, but it appears that it doesn't. It complains with the error:

Expecting element 'MyAbstractBase' from namespace ' http://schemas.datacontract.org/2004/07/MyApp.Foo '.. Encountered 'Element' with name 'SomeOtherClass.MyDerivedClass', namespace ' http://schemas.datacontract.org/2004/07/MyApp.Foo.Bar '.

So any idea what I'm missing here?

I put together a simple demonstration of the problem on a dot net fiddle here:

http://dotnetfiddle.net/W7GCOw

Unfortunately, it won't run directly there because it doesn't include the System.Runtime.Serialization assemblies. But if you drop it into a Visual Studio project, it will serialize fine, but balks at deserialization.

When serializing your data, use the same overloaded method for serialization as you use for deserialization:

DataContractSerializer serializer = new DataContractSerializer(typeof(MyAbstractBase ), new List<Type>() { typeof(SomeOtherClass.MyDerivedClass) });

Also, Declare a KnownType attribute around your base class so it knows what possible derived classes it may deserialize:

[DataContract]
[KnownType(typeof(SomeOtherclass.MyDerivedClass))]
public class MyAbstractBase
{
    [DataMember]
    public string Foo { get; set; }

    // some other abstract methods that derived classes have to implement
}

So I identified a couple of problems. The first is that the CreateBinaryWriter just doesn't seem to work at all. So I dropped it and just directly serialized with serializer.WriteObject(ws,this);

The second problem is on serialization I did this:

DataContractSerializer serializer = new DataContractSerializer(this.GetType(), new List<Type>() { typeof(SomeOtherClass.MyDerivedClass) });

The problem is that the type I'm passing there isn't the base type, it's whatever type I'm calling this function from. But in deserialization I have this:

DataContractSerializer serializer = new DataContractSerializer(typeof(MyAbstractBase ), new List<Type>() { typeof(SomeOtherClass.MyDerivedClass) });

This isn't the same serializer. The type is different. So changing both to typeof(MyAbstractBase) fixed the problem in my simple example.

Of course, in my real project I'm still getting errors with it complaining on deserialization with The data at the root level is invalid. Line 1, position 1. The data at the root level is invalid. Line 1, position 1. which is odd because I've compared both the data coming out of serialization and the data going into deserialization and they are absolutely identical. No rogue BOM or anything.

EDIT : I've solved the data at the root level is invalid problem. It seems that decorating my [DataContract] attributes with explicit Name and Namespace properties solved the problem and, as an added bonus, reduced the size of my data because I greatly shortened the namespaces. Quite why the serializer couldn't cope with the real namespaces I don't know.

Another Edit : One last wrinkle. I think the namespace thing was a red herring and I got it working only by pure coincidence. The root of the problem (along with the solution) is explained here:

https://msmvps.com/blogs/peterritchie/archive/2009/04/29/datacontractserializer-readobject-is-easily-confused.aspx

When you do GetBuffer() on the MemoryStream you can get excess null characters because of the way the underlying array resizes itself as needed. This puts a bunch of nulls on the end of your serialization (which can be spotted as a bunch of A s after base 64'ing the array) and these are what are screwing up the deserialization with the very confusing The data at the root level is invalid. Line 1, position 1. The data at the root level is invalid. Line 1, position 1. . It's confusing because the problem isn't at the beginning at all, it's at the END !!!

In case anybody is interested, by serialization now looks like this:

    public string SerializeBase64()
    {
        // Serialize to a base 64 string
        byte[] bytes;
        long length = 0;
        using (MemoryStream ws = new MemoryStream())
        {
            XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(ws);
            DataContractSerializer serializer = new DataContractSerializer(typeof(MyAbstractBase ), new List<Type>() { typeof(SomeOtherClass.MyDerivedClass) });
            serializer.WriteObject(writer, this);
            writer.Flush();
            length = ws.Length;
            // Note: https://msmvps.com/blogs/peterritchie/archive/2009/04/29/datacontractserializer-readobject-is-easily-confused.aspx
            // We need to trim nulls from the buffer produced by the serializer because it'll barf on them when it tries to deserialize.
            bytes = new byte[ws.Length];
            Array.Copy(ws.GetBuffer(), bytes, bytes.Length);
        }
        string encodedData = bytes.Length + ":" + Convert.ToBase64String(bytes, 0, bytes.Length, Base64FormattingOptions.None);
        return encodedData;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM