This isn't really a rant about Windows Mobile per se, but it's an issue I seem to run into on a consistent basis: you can't deserialize an XmlAttribute into a struct. Consider the following code:
using System; using System.Linq; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Xml.Serialization; using System.IO; namespace SmartDeviceProject2 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { XmlSerializer ser = new XmlSerializer(typeof(Foo)); string xml = "<Foo Moo=\"2,3\"/>"; MemoryStream mem = new MemoryStream(); StreamWriter writer = new StreamWriter(mem); writer.Write(xml); writer.Flush(); mem.Seek(0, SeekOrigin.Begin); Foo f = (Foo)ser.Deserialize(mem); } } public struct Bar : IFormattable { public int X; public int Y; public static Bar Parse(string s) { string[] strings = s.Split(','); Bar ret = new Bar(); ret.X = int.Parse(strings[0]); ret.Y = int.Parse(strings[1]); return ret; } public override string ToString() { return string.Format("{0},{1}", X, Y); } #region IFormattable Members public string ToString(string format, IFormatProvider formatProvider) { return ToString(); } #endregion } public struct Foo { [XmlAttribute] public Bar Moo; } }
Attempting to run this code on the desktop would fail on the line that instantiates the XmlSerializer: "Cannot serialize member 'Moo' of type SmartDeviceProject2.Foo. XmlAttribute/XmlText cannot be used to encode complex types.".
However, on .NET CF 2.0, it fails on deserialization with the cryptic message: "The type SmartDeviceProject2.Bar was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."
But my gripe is the following: I have a struct, it is IFormattable, and it can be Parsed (like int.Parse, DateTime.Parse, et al). Why is that not sufficient enough for serialization to and from an XmlAttribute string?
Anyways, here is the workaround:
- For all types that you want to be deserialized from attributes, explicitly give them the [XmlElement] attribute. Yes, you read that right.
- Create an XmlSerializer. Attach a new method to the UnknownAttribute event.
- Whenever this event is fired, use a combination of the attribute name and reflection to find the PropertyInfo/FieldInfo for what XmlSerializer can't deserialize. Then find that object type's static Parse method, and call it on the attribute string. Then use that PropertyInfo/FieldInfo to set the property.
The new code would look something like this (you will need to add your own code to handle if it is a Property, since my sample is only looking for fields):
private void button1_Click(object sender, EventArgs e) { XmlSerializer ser = new XmlSerializer(typeof(Foo)); string xml = "<Foo Moo=\"2,3\"/>"; MemoryStream mem = new MemoryStream(); StreamWriter writer = new StreamWriter(mem); writer.Write(xml); writer.Flush(); mem.Seek(0, SeekOrigin.Begin); ser.UnknownAttribute += new XmlAttributeEventHandler(ser_UnknownAttribute); Foo f = (Foo)ser.Deserialize(mem); } void ser_UnknownAttribute(object sender, XmlAttributeEventArgs e) { FieldInfo field = e.ObjectBeingDeserialized.GetType().GetField(e.Attr.Name); MethodInfo parse = field.FieldType.GetMethod("Parse"); field.SetValue(e.ObjectBeingDeserialized, parse.Invoke(null, new object[] { e.Attr.Value })); }
public struct Foo { [XmlElement] public Bar Moo; }
Unfortunately I have not figured out a similar workaround for serialization...
6 comments:
My friend, you should check the IXmlSerializable interface...
Just implement it on your "Bar" class and you'll have your serialization and desearialization solved!! ;)
Regards, Pedro Lamas
Yes, I checked that.
IXmlSerializable does not let you serialize to/from attributes with complex types either.
Yes, that's true, you can use it only with xml element, not attribute, but why would you want to serialize a complexe type as an attribute?
Consider serialization of a color:
BackColor="#FF00FF00"
Or a point:
Location="2,3"
There are many scenarios where serializing a complex type to a string is desired. And in general, simple strings tend to be more aesthetically pleasing and readable in XML when they are attributes.
As an obvious example: XAML. Both of the above complex types can attributes off a XAML element. I've been working on a XAML type language/UI toolset, and that is when I ran into this issue.
Hum... I can see your point there... :)
One workaround to overcome this problem without using the UnknownAttribute event is to use a 'Surrogate' property of type string in Foo and ignore the original property.
public struct Foo
{
[XmlIgnore]
public Bar Moo;
[XmlAttribute("Moo")]
private string MooSurrogate
{
get{return this.Moo.ToString();}
set{ this.Moo = Bar.Parse(value);}
}
}
This means the class (Foo) controls how it gets serialized and de-serialized and not the code which is serializing or de-serializing.
-Pramod B R
Post a Comment