Argh... XmlSerialization with inherited types

S

Steve K.

I'm getting an error deserializing some XML and it really doesn't make sense
to me.

Here are my classes that I'm serializing/deserializing:
<code>

public abstract class BusinessEntity : INotifyPropertyChanged
{
// only has one protected method, no need to serialize
}

[Serializable]
[XmlInclude(typeof(Doc100Entry))]
public class DocEntryBase : BusinessEntity
{

}

[Serializable]
public abstract class Doc100EntryBase : DocEntryBase
{

}

[Serializable]
public class Doc100Entry : Doc100EntryBase
{

}

</code>

Here is a snippet of the XML

<code>

<?xml version="1.0" encoding="us-ascii"?>
<DocEntryBase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="Doc100Entry">
(snipped...)
</DocEntryBase>

</code>

I'm getting the following exception when deserializing an instance
Doc100Entry:
"<DocEntryBase xmlns=''> was not expected."

I made a change to DocEntryBase that started this whole mess, it used to be
an abstract class (and worked fine). I removed the abstract modifier
because of a design change and I've had serialization problems ever since.

Anyone see an obvious problem with my setup here?
 
S

Steve K.

What I've learned is that if I explicitly case my reference to the actual
type it is then things will work fine.

If you change this line:
byte[] serializedData = SerializationUtils.Serialize(db);

To:
byte[] serializedData = SerializationUtils.Serialize(db as Doc100Entry);

It will work.

I googled and read some more about the XmlInclude attribute, so I tried it
out by adding this above DocEntryBase:
[XmlInclude(typeof(Doc100Entry))]

It will serialize fine with that attribute (good!) but when I try to
deserialize it I get this exception:
"<DocEntryBase xmlns=''> was not expected."


Here is a (semi) complete example that shows the problem:
<code>
// Argh.cs
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Text;

namespace ImageConversion
{
public class BusinessEntity
{
private string _id;

public string ID
{
get { return _id; }
set { _id = value; }
}
}

[Serializable]
public abstract class DocEntryBase : BusinessEntity
{
private string _name;

public string Name
{
get { return _name; }
set { _name = value; }
}
}

[Serializable]
public abstract class Doc100EntryBase : DocEntryBase
{
private string _lastName;

public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
}

[Serializable]
public class Doc100Entry : Doc100EntryBase
{
private int _age;

public int Age
{
get { return _age; }
set { _age = value; }
}
}
}

// (snipped from Form.cs)
private void button6_Click(object sender, EventArgs e)
{
try
{
Doc100Entry d = new Doc100Entry();
d.Age = 333;
d.ID = "argh!";
d.LastName = "Klett";
d.Name = "Steve Klett";

DocEntryBase db = d;

byte[] serializedData = SerializationUtils.Serialize(db);
FileUtils.WriteBytesToFile(@"c:\xmlTest.xml", serializedData);

serializedData = null;
serializedData = FileUtils.ReadWholeFileBytes(@"c:\xmlTest.xml");
d = null;
d = SerializationUtils.Deserialize<Doc100Entry,
DocEntryBase>(serializedData);
}
catch (Exception ee)
{
Console.WriteLine(ee.ToString());
}
}

// SerializationUtils.cs
namespace PMD.Library
{
public static class SerializationUtils
{
public static byte[] Serialize<TType/*, TBaseType*/>(TType entity)
where TType : class/*, TBaseType*/
{
byte[] data = null;
using (MemoryStream memStream = new MemoryStream())
{
XmlSerializer xs = new XmlSerializer(typeof(TType));
XmlTextWriter xmltr = new XmlTextWriter(memStream,
Encoding.ASCII);
xmltr.Formatting = Formatting.Indented;

xs.Serialize(xmltr, entity);

data = (xmltr.BaseStream as MemoryStream).ToArray();
}

return data;
}

public static TType Deserialize<TType, TBaseType>(byte[] data)
where TType : class, TBaseType
{
XmlSerializer serializer = new XmlSerializer(typeof(TType));
return serializer.Deserialize(new MemoryStream(data)) as TType;
}
}
}

// FileUtils.cs
using System;
using System.Drawing;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace PMD.Library
{
public static class FileUtils
{
public static byte[] ReadWholeStream(Stream stream)
{
byte[] buffer = new byte[stream.Length];

int offset = 0;
int remaining = (int)stream.Length;
while (remaining > 0)
{
int read = stream.Read(buffer, offset, remaining);
if (read <= 0)
{
throw new EndOfStreamException
(String.Format("End of stream reached with {0} bytes
left to read", remaining));
}

remaining -= read;
offset += read;
}

return buffer;
}

public static byte[] ReadWholeFileBytes(string filename)
{
if(string.IsNullOrEmpty(filename))
{
throw new ApplicationException("filename must be a valid
path to a file and not null");
}

if(File.Exists(filename) == false)
{
throw new FileNotFoundException(string.Format("Failed
finding file {0}", filename));
}

using (Stream stream = new FileStream(filename, FileMode.Open))
{
return ReadWholeStream(stream);
}
}


/// <summary>
/// Saves the byte[] data to a file on the file system.
/// If the file or path don't exist they will be created.
/// </summary>
/// <param name="filename">Name of the file to be saved.</param>
/// <param name="data">Data to save to the file.</param>
public static void WriteBytesToFile(string filename, byte[] data)
{
// Check if the target directory exists, if not create it.
string path = Path.GetDirectoryName(filename);

if(Directory.Exists(path) == false)
{
Directory.CreateDirectory(path);
}

using(Stream stream = new FileStream(filename,
FileMode.OpenOrCreate, FileAccess.Write))
{
stream.Write(data, 0, data.Length);
}
}
}
}
</code>

I'm lost at this point ;0|


Steve K. said:
I'm getting an error deserializing some XML and it really doesn't make
sense to me.

Here are my classes that I'm serializing/deserializing:
<code>

public abstract class BusinessEntity : INotifyPropertyChanged
{
// only has one protected method, no need to serialize
}

[Serializable]
[XmlInclude(typeof(Doc100Entry))]
public class DocEntryBase : BusinessEntity
{

}

[Serializable]
public abstract class Doc100EntryBase : DocEntryBase
{

}

[Serializable]
public class Doc100Entry : Doc100EntryBase
{

}

</code>

Here is a snippet of the XML

<code>

<?xml version="1.0" encoding="us-ascii"?>
<DocEntryBase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="Doc100Entry">
(snipped...)
</DocEntryBase>

</code>

I'm getting the following exception when deserializing an instance
Doc100Entry:
"<DocEntryBase xmlns=''> was not expected."

I made a change to DocEntryBase that started this whole mess, it used to
be an abstract class (and worked fine). I removed the abstract modifier
because of a design change and I've had serialization problems ever since.

Anyone see an obvious problem with my setup here?
 
A

Adam Benson

IMHO XML serialization can be a real pain in the neck.

Best to go with binary+manual serialization if you can.

- Adam.
======
 
M

Marc Gravell

For XmlInclude to be useful here, you need to use the [de]serializer for the
base-type:

XmlSerializer serializer = new XmlSerializer(typeof(TBaseType));

This will allow any of TBaseType or any associated (XmlInclude) sub-types to
be deserialized.

For Adam's comment re using binary - in my experience the exact opposite is
true. Xml serialization has limitations (such as trees rather than graphs,
unless you use some of the DataContractSerializer stuff) - but it is far
more transportable (even just within your own code...).

Marc
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top