Tuesday, July 27, 2004

DefaultValue and XmlSerializer Don't Mix?

Maybe someone can explain to me why this:


using System;
using System.Xml.Serialization;


public class Foo {
  [XmlAttribute]
  [System.ComponentModel.DefaultValueAttribute("three")]
  public string value = "three";
}


public class App {
  public static void Main() {
    XmlSerializer ser = new XmlSerializer(typeof(Foo));
    Foo foo = new Foo();
    ser.Serialize(Console.Out, foo);
  }
}


produces this:


  <Foo />


whereas if you remove the DefaultValue attribute, you get this:


  <Foo value=”three” />


which is what I would expect. This is particularly troubling, since wsdl.exe will generate proxies that use System.ComponentModel.DefaultValueAttribute when it sees schema types with fixed value constraints.


I have to assume this is a bug in System.Xml.Serialization, but I'm willing to believe that there's some subtlety I've missed. I've confirmed this behavior in CLR 1.1 and 2.0.40607.

12 comments:

  1. I too encountered this. Don't know if it should be classified as a bug or just as a peculiar decision by the XmlSerializer team. Basically, the serializer checks if the current value is equal to the default value, and if it is it doesn't bother including it in the serialized XML. Presumably the assumption is whoever is consuming the XML on the "other side" already knows the default value and will simply use that when it encounters the missing value (for example, by initializing it in an object just as you did). It's kind of like the way a server control only persist a property's value in markup if the value is different from the default value.



    This approach probably made more sense back in the "object to XML to object" days of the original ASMX model. Both the server-side object and the client-side proxy know the default value, so by not sending it there's a small performance gain.



    At least that's my guess for why it behaves that way. In any event, it makes a lot less sense in the "XML document exchange" model that is the current thinking for web services. Perhaps you can include this as a "customer ask" on Don Box's wiki. :)



    ReplyDelete
  2. OK, thanks guys, that makes more sense. I'm not sure whether I agree with their decision or not, but at least I understand the issue.

    ReplyDelete
  3. On the bright side, according to that KB article:

    "Microsoft plans to change this behavior in the next major version release of the .NET Framework."

    ReplyDelete
  4. This is wholly consistent with the way that the DefaultValue attribute is used by the design-time serialization engine in VS.NET. (The oft-ignored third serialization mechanism in .NET...)



    I wasn't aware that DefaultValue was used anywhere else actually, but in the world of Design Time, it has *always* meant "If this property has this value, you don't need to bother serializing it."



    I'm slightly surprised to see it being used in the XML Serialization mechanism, but it is actually a pretty good match for the standard 'default value' semantics in XSD as someone already mentioned, so I suppose it does actually make sense. It's just that I always thought of this as being a part of the design time architecture.



    I'm not sure why you find the behaviour surprising - the behaviour you say you were expecting doesn't make any more sense to me. Indeed there are situations where it would the wrong thing - in certain design-time scenarios, there is a difference between explicitly setting a value to its default and leaving to be its default. (E.g. with the Windows Forms Control class, setting the BackColor property turns off the ambient behaviour for that property.)



    (Of course this means that if you specify [DefaultValue..] without actually making sure that the property/field really does have the default you say it will, it won't work properly.)

    ReplyDelete
  5. There are two reasons I find it surprising, although I now understand why it works the way it does.



    1) To cross namespaces like that (i.e. an attribute from System.ComponentModel controlling the way System.Xml.Serialization works) seems dirty. This is purely aesthetic, of course, but part of good API design is not catching people off guard.



    2) I found it surprising because the somewhat subtle behavior incorrect behavior you rightly point out is exactly that...somewhat subtle. "Default value" has a semantic that to most people means, "If I don't do anything, otherwise, it should have *this* value."



    The latter point is, of course, a deficiency in my understanding. However, I highly doubt I'd be the only one (or even in the minority) when it comes to being caught off guard by the semanitcs here. Hence the post. ;)



    The real issue here is that the semantics of having a fixed default value in an XSD is just weird. It means that if I give you an instance document, you have to go look at the *schema* to figure out what the value of that attribute should be. That's just wrong.

    ReplyDelete
  6. But, if the contract *can* be clearly communicated -- and I take your point, Craig, about the dangers of expecting the client to go check a schema -- it *is* a valuable option to have around. We had a performance issue with one Web service where the response message contained date/times. Most of these values were effectively "null" (i.e. no time applicable), and serialising those values was taking up a lot of time and bandwidth. Using DefaultValue to serialise only non-"null" times measurably (indeed significantly) improved the performance of the service.

    ReplyDelete
  7. Actually, I'd say what you're running into is the mismatch between objects (where DateTime can't be null) and XML (where an element can either appear with a datetime value or not). Nothing to do with XSD's fixed value semantics.



    I agree that it's nice to have the ability to do what you want (and you can do it without DefaultValue if you're willing to handle the XML yourself (which is pretty easy)) but I dislike the way it's implemented.

    ReplyDelete
  8. The formating of my last post seems to have gone a bit mad. For some reason the namespaces have been repeated.

    ReplyDelete
  9. FWIW, I belive it's XMLSpy that's wrong, and not XmlSerializer.



    But XSD defaults are evil, and you should avoid them if you can.

    ReplyDelete
  10. I think this was my fault. In the schema I had set both the default="false" and minOccurs="1". I guess these two settings conflict.

    ReplyDelete
  11. Yep, that would make sense, since the XSD meaning of default="false" is that the value is always "false", and should never actually appear in the document. Whereas minOccurs="1" indicates that it *must* appear in the document.



    Hence my argument that XSD default sucks: it means that in order to know what the XML actually contains, you have to look at both the instance document and the schema. Blech.

    ReplyDelete
  12. This is a very important feature which allows you to only send data you need over the wire.



    When a Web client/proxy recreates the object it will be recreated witht he data which the client can then change.



    Used well and it will half your traffic.



    Ben

    ReplyDelete