XML Serialization: Problem deserializing an abstract property
I am still trying to wrap my brain around this whole xml serialization thing, and it appears I am in need of some assistance yet again.
I need to be able to deserialize a property of an abstract type. This type will have many different concrete types being added over time, and is referenced in a lot of different models, so explicitly listing each of the concrete types is not an ideal solution.
I have read the XML Serialization and Inherited Types thread, and have come up with the following:
<Page>
<introCommand>
<PlayElement />
</introCommand>
</Page>
**
namespace TestService
{
public class Page
{
[XmlElement("introCommand", Type = typeof(XmlCommandSerializer<AbstractCommandModel>))]
//[XmlElement(typeof(PlayElement))] **NOTE: the example works if I use this instead
public AbstractCommandModel introCommand;
}
}
**
namespace TestService
{
public class AbstractCommandModel
{
}
}
**
namespace TestService
{
public class PlayElement : AbstractCommandModel
{
}
}
**
namespace TestService
{
public class XmlCommandSerializer<AbstractCommandModel> : IXmlSerializable
{
// Override the Implicit Conversions Since the XmlSerializer
// Casts to/from the required types implicitly.
public static implicit operator AbstractCommandModel(XmlCommandSerializer<AbstractCommandModel> o)
{
return o.Data;
}
public static implicit operator XmlCommandSerializer<AbstractCommandModel>(AbstractCommandModel o)
{
return o == null ? null : new XmlCommandSerializer<AbstractCommandModel>(o);
}
private AbstractCommandModel _data;
/// <summary>
/// [Concrete] Data to be stored/is stored as XML.
/// </summary>
public AbstractCommandModel Data
{
get { return _data; }
set { _data = value; }
}
/// <summary>
/// **DO NOT USE** This is only added to enable XML Serialization.
/// </summary>
/// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
public XmlCommandSerializer()
{
// Default Ctor (Required for Xml Serialization - DO NOT USE)
}
/// <summary>
/// Initialises the Serializer to work with the given data.
/// </summary>
/// <param name="data">Concrete Object of the AbstractCommandModel Specified.</param>
public XmlCommandSerializer(AbstractCommandModel data)
{
_data = data;
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null; // this is fine as schema is unknown.
}
public void ReadXml(System.Xml.XmlReader reader)
{
// Cast the Data back from the Abstract Type.
string typeAttrib = reader.GetAttribute("type");
// Ensure the Type was Specified
if (typeAttrib == null)
throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractCommandModel).Name +
"' because no 'type' attribute was specified in the XML.");
Type type = Type.GetType(typeAttrib);
// Check the Type is Found.
if (type == null)
throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractCommandModel).Name +
"' because the type specified in the XML was not found.");
// Check the Type is a Subclass of the AbstractCommandModel.
if (!type.IsSubclassOf(typeof(AbstractCommandModel)))
throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractCommandMode开发者_如何学Pythonl).Name +
"' because the Type specified in the XML differs ('" + type.Name + "').");
// Read the Data, Deserializing based on the (now known) concrete type.
reader.ReadStartElement();
this.Data = (AbstractCommandModel)new
XmlSerializer(type).Deserialize(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
// Write the Type Name to the XML Element as an Attrib and Serialize
Type type = _data.GetType();
// BugFix: Assembly must be FQN since Types can/are external to current.
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
new XmlSerializer(type).Serialize(writer, _data);
}
#endregion
}
}
However, when I run the deserializer I get an InvalidOperationException stating "There is an error in XML document (3,3).". The only thing I have changed from the example in the before mentioned thread is the class names.
Am I on the right track with this? If so, what did I mess up to cause this error?
From your example:
//[XmlElement(typeof(PlayElement))] **NOTE: the example works if I use this instead
This is the recommended way to support abstract classes. You can switch instances by element name and use multiple declarations like this:
[XmlElement("playElement", typeof(PlayElement))]
[XmlElement("testElement", typeof(TestElement))]
public AbstractCommandModel Command;
Of course you will still need to drop the "introCommand" element or add another class to nest the above declaration into.
...
If you still need to do the serialization manually then you are on the correct path. Your example works well enough I guess, here is the XML output:
<Page>
<introCommand type="TestService.PlayElement, TestService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<PlayElement />
</introCommand>
</Page>
And this xml reads just fine into your object; however, this presents a security concern. Anyone with access to modify or inject this XML into your application can easily inject code.
The following was used to test your example code:
private static void Main()
{
StringWriter dataOut = new StringWriter();
XmlTextWriter writer = new XmlTextWriter(dataOut);
writer.Formatting = Formatting.Indented;
Page p = new Page();
p.introCommand = new PlayElement();
new XmlSerializer(typeof(Page)).Serialize(writer, p);
string xml = dataOut.ToString();
Console.WriteLine(xml);
XmlTextReader reader = new XmlTextReader(new StringReader(xml));
Page copy = (Page) new XmlSerializer(typeof (Page)).Deserialize(reader);
}
精彩评论