I’m working on a new RESTful service (I’m using Asp.Net MVC for this) and I’m keen to make it as easy to integrate with as across as many client platforms as possible.
I’m going to be using XML exclusively, as I it enables me to produce schemas that our service clients will be able to download and then see the shape of each object that the service operations will expect.
I also want to employ XML schema validation on the data coming in from the request. Doing this will trap most data errors it gets further down the request pipeline, thus protecting my code; but will also ensure that the caller gets nice verbose error messages – XML Schema Validation is pretty explicit on what’s gone wrong!
Thus, I’ve wired up an MVC action to produce a schema for the relevant objects using the XsdDataContractExporter class. I will simply point users at this, alongside documentation for each of the operations; which will include schema type names from that ‘live’ schema.
While testing out one of the types, I noticed that a TimeSpan member was being serialized as xs:string and not as xs:duration. I consulted an MSDN topic I should now know by heart (given how many times I’ve looked at it) to check the support for TimeSpan in the DataContractSerializer and sure enough it’s there; but I couldn’t understand why, if DateTime is indeed mapped to the XML DateTime type, it’s not mapped to the XML Duration type.
So I’ve written a TimeSpan type called XmlDuration that is implicitly convertible to System.TimeSpan but which, when you expose it as a member on a Data Contract, presents itself as xs:duration. This then means that if you enable schema validation on your incoming XML, the input string will be validated against the rules attached to the XML Duration type, instead of being simply a string that allows any content.
The code is as follows. There’s one really long line in there which is a Regex that I’ve split into multiple string additions purely for this post; you can join the strings back up again if you so desire:
I’ve taken this class and merged it into the System.Xml namespace – because clearly this will also work with the XmlSerializer as well as for the DataContractSerializer.
A few notes.
The nifty part of this class is in the use of the XmlSchemaProviderAttribute. This is the .Net framework’s preferred mechanism for mapping types to Xml schema. In theory, if you were writing a more complex custom type for which Schema simply cannot be auto-generated, you could manually inject the schema into the XmlSchemaSet passed into the GetTypeSchema method (the name of which is determined by the parameter you pass to the attribute constructor). You would then return the XmlQualifiedName of this schema type to satisfy the framework.
In this case all we have to do is to return the well-known qualified name of the xml duration type: ‘duration’ from the namespace ‘http://www.w3.org/2001/XmlSchema’. We should be able to rely on anyone working with XML to have mapped this namespace already, and we know that a schema exporter will be doing the same since most of the .Net fundamental types are mapped to the same namespace.
Under the hood I’ve written a simple regex parser based on the format for the Duration data type. It will recognise all valid strings, but it also lets one or two invalid ones through (notably ‘PT’). However, if you are also using schema validation then this will not trouble you.
As the comments state - years and months are a problem; since the .Net TimeSpan chickens out and doesn’t encode years/months (presumably because it makes it much easier to calculate them from the difference of two DateTimes, as well as to add one onto a DateTime). Of course neither have a fixed number of days; so I’ve gone for a reasonable average. You could be more clever and take the current month’s number of days plus a strict average of 365.25 days per year – it depends on how accurate you really need it to be.
If you’re writing a new web service, you can simply make sure that all your clients express durations starting with the number of days – e.g. ‘P400D’ which will be deserialized exactly into a .Net TimeSpan representing 400 days.
In situations where a duration is received from a client and might need to be sent back to them – the original input string is preserved (but it will be up to you to persist that server side).
So now you can change a DataContract class like this:
And change it over to this:
In this case these types aren’t annotated of course (whereas in my case all my exported types are).
If you’re publishing a data contract for a type that must also implement some internal interface that exposes a TimeSpan like this:
Then your best policy is to write the DataContract class as follows:
Obviously, there is an argument here that the XmlDuration should, in fact, be a value type and not a class. I’ll leave that up to you to decide.