Being a .NET developer in a Mac OSX world: serializing XmlDates


I blogged before on differences in implementation between the Mono and the Microsoft implementation of the .NET framework. In this post I investigate a difference in XmlSerialization.

Dates in XSD

When you write an XSD you have (at least) 3 options to use dates and/or times:

Most of the time you probably choose the “dateTime”, since that maps nicely to the DateTime type in .NET. But you might choose “date” for a birthday for example, and you might even choose “time” for uh, well for a time of some sort…

For his post I’ll use the following XSD. It’s a bit contrived but I like this better than “MyDate” and “ADateTimeField”:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:element name="Book" type="BookType"></xsd:element>
 
  <xsd:complexType name="BookType">
    <xsd:sequence>
      <xsd:element name="Title" type="xsd:string"/>
      <xsd:element name="Price" type="xsd:float" nillable="true"/>
      <xsd:element name="PrintedAt" type="xsd:date"/>
      <xsd:element name="SoldAt" type="xsd:dateTime"/>
      <xsd:element name="WillReadTodayAt" type="xsd:time"/>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

Generate classes from your XSD

Both Mono and Microsoft have command-line tools that generate classes from your XSD. Very convenient and a good thing to do when you need simple entities. We did this in a project where we had both C# and Java code. On both sides we generated the classes from the same XSD.

The syntax of both tools is more or less the same:

xsd book.xsd /classes /namespace:BookStore.Books

will work on both systems.
The outcome of both generators is quite different. The Mono version uses public fields, the MS version use properties with back-up private fields. They both adorn the fields/properties with attributes from the XmlSerialization namespace.
For Microsoft it looks like this:

namespace BookStore.Books {
    using System.Xml.Serialization;
 
    ///
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlRootAttribute("Book", Namespace="", IsNullable=false)]
    public partial class BookType {
 
        private string titleField;
        private System.Nullable priceField;
        private System.DateTime printedAtField;
        private System.DateTime soldAtField;
        private System.DateTime willReadTodayAtField;
 
        ///
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public string Title {
            get {
                return this.titleField;
            }
            set {
                this.titleField = value;
            }
        }
 
        ///
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)]
        public System.Nullable Price {
            get {
                return this.priceField;
            }
            set {
                this.priceField = value;
            }
        }
 
        ///
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="date")]
        public System.DateTime PrintedAt {
            get {
                return this.printedAtField;
            }
            set {
                this.printedAtField = value;
            }
        }
 
        ///
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public System.DateTime SoldAt {
            get {
                return this.soldAtField;
            }
            set {
                this.soldAtField = value;
            }
        }
 
        ///
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="time")]
        public System.DateTime WillReadTodayAt {
            get {
                return this.willReadTodayAtField;
            }
            set {
                this.willReadTodayAtField = value;
            }
        }
    }
}

That could have been a bit more readable by removing all the fully-qualified namespaces (since there is a “using” at the top), the empty comments and the back-up fields (if you generate for .NET after version 3).

For Mono it is like this:

namespace BookStore.Books {
 
    ///
    [System.Xml.Serialization.XmlRootAttribute("Book", Namespace="", IsNullable=false)]
    public class BookType {
 
        ///
        public string Title;
 
        ///
        [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
        public System.Single Price;
 
        ///
        [System.Xml.Serialization.XmlElementAttribute(DataType="date")]
        public System.DateTime PrintedAt;
 
        ///
        public System.DateTime SoldAt;
 
        ///
        [System.Xml.Serialization.XmlElementAttribute(DataType="time")]
        public System.DateTime WillReadTodayAt;
    }
}

Much more concise. But with an important omission: the Price has the right XmlAttribute, but should have been defined as “Nullable”. I’ve seen some remarks on this in the mailing-list/forum for Mono, but could not find out if this was already fixed. So I use the Microsoft XSD.exe whenever my XSD changes, copy paste the result to my MonoDevelop environment and recompile. I would rather call the MonoXSD on the background whenever my XSD changes (has anyone used scripts as Custom Tools in MonoDevelop?) so that the class gets re-generated automatically. Maybe the upcoming Mono 2.8 will fix it.

It’s all a DateTime

As you can see, all the fields are generated as DateTimes. Makes sense, since there is nothing else in the framework to hold time-related information. So what happens when you read an Xml file and serialize the Xml into a Book object?

Take this Xml:

<Book>
    <Title>How to be a Mac Developer</Title>
    <PrintedAt>1965-02-16</PrintedAt>
    <SoldAt>1965-02-16</SoldAt>
    <WillReadTodayAt>11:00:01</WillReadTodayAt>
</Book>

And process it with this code:

    class MainClass
    {
        public static void Main (string[] args)
        {
            string bookXml = @"
            <Book>
                <Title>How to be a Mac Developer</Title>
                <PrintedAt>1965-02-16</PrintedAt>
                <SoldAt>1965-02-16</SoldAt>
                <WillReadTodayAt>11:00:01</WillReadTodayAt>
            </Book>
            ";
 
            BookType book = (BookType) ToObject(bookXml, typeof(BookType), Encoding.Default);
            Console.WriteLine("PrintedAt:{0}", book.PrintedAt);
            Console.WriteLine("SoldAt:{0}", book.SoldAt);
            Console.WriteLine("WillReadAt:{0}", book.WillReadTodayAt);
            Console.ReadLine();
        }
 
        public static object ToObject(string xml, Type xmlObjectType, Encoding encoding)
        {
            MemoryStream xmlStream = new MemoryStream(encoding.GetBytes(xml));
            StreamReader reader = new StreamReader(xmlStream, encoding, false);
            XmlReaderSettings readerSettings = new XmlReaderSettings();
            readerSettings.CloseInput = true;
            XmlReader xmlReader = XmlReader.Create(reader, readerSettings);
            XmlSerializer xmlSerializer = new XmlSerializer(xmlObjectType);
            return xmlSerializer.Deserialize(xmlReader);
        }
    }

Different ideas on default dates

You get this output in Windows (running the exe build by MonoDevelop directly on my VMWare-WindozeXP):
Screen shot 2010-09-21 at 11.26.27 AM

And this output on the Mac:
Screen shot 2010-09-21 at 11.30.34 AM

Interesting difference, right? Windows sets the date part of “WillReadTodayAt” to DateTime.MinValue and Mono assumes it is today. It doesn’t matter that much, since I’m going to ignore the date-part anyway for that property.
The time part of the date-olny property “PrintedAt” is set to “12:00:00AM” in Windows and “00:00:00″ in Mono. Again, I’m going to ignore the time-part, but what will happen when I compare dates for equality? I prefer the Mono setting.

Invalid time data

Take this slightly modified Xml:

<Book>
    <Title>How to be a Mac Developer</Title>
    <PrintedAt>1965-02-16</PrintedAt>
    <WillReadTodayAt>2010-09-21T11:00:01</WillReadTodayAt>
    <SoldAt>1965-02-16</SoldAt>
</Book>

Running this on Mono result in an FormatException: Screen shot 2010-09-21 at 11.41.53 AM
Fair enough, the data in the xml is not a Time, it is a DateTime. So an exception makes sense.

Wonder what Windows will do? After you have started an instance of Visual Studio to see the error, you get this:
Screen shot 2010-09-21 at 11.45.46 AM

So both platforms agree that the data is invalid. Unfortunately the position that Windows gives us is right after the offending data, at not before as I was expecting. So it took some time (way more than an hour) to realize that the problem was not in the “SoldAt”, but in the “WillReadTodayAt” just before it. Be warned.

Invalid date data

Now pass in a date-with-time on the field that expects only a date:

<Book>
    <Title>How to be a Mac Developer</Title>
    <PrintedAt>1965-02-16T08:01:03</PrintedAt>
    <WillReadTodayAt>11:00:01</WillReadTodayAt>
    <SoldAt>1965-02-16T09:00:02</SoldAt>
</Book>

Mono doesn’t like it. Same error as before, no indication on what line and what xml-tag, alas.
Windows doesn’t like it either. Same error as before, positioned right before “WillReadTodayAt”.

Conclusions

There’s a big difference in the C# classes that get generated. The Mono implementation is missing the support for nullables, the MS implementation is a bit too verbose.
The handling of incorrect date/time information is okay in both systems.
The date-part in a xsd:time gets defaulted to “1-1-1″ in MS and “today” in Mono. The time-part gets defaulted to “0:0:0″ in Mono and “12:0:0AM” in MS.

The code for this article

, , ,

  1. Nog geen reacties.
(wordt niet gepubliceerd)
  1. Nog geen trackbacks.