Monday, November 10, 2008

Web Services XML: Nullable AND Optional xsd:string

@YaronNaveh

In xml an element can be both optional and nillable:


<s:element name="Name" type="s:string" minOccurs="0" nillable="true" />


So in xml this element can have a value:


<wrapper>
   <s>123</s>
</wrapper>


Be NULL:


<wrapper>
   <s xsi:nil="true" />
</wrapper>


Or be omitted at all:


<wrapper />


This is a challenge to many web services frameworks since when generating proxy code an element is usually mapped to one language variable. A variable in a programming language does not natively support metadata such as being "nullable" or "optional".

In some frameworks the solution is to add a "controller" field. For example some Java frameworks would create something like this:


setName(String Name) {}
setNameNillable(bool isNillable) {}
setNameOptional(bool isOptional) {}


In .Net 2.0 part of this informaiton is in a controller field and part in .Net attributes. For example a nillable string is:


[System.Xml.Serialization.XmlElement(IsNullable=true)]
public string s;


And an optional int is:


public int i;
[System.Xml.Serialization.XmlIgnore()]
public bool iSpecified;


What happens in .Net when an element is both nullable and optional?
There is no problem with an int - we can use System.Nullable:


public int? i;
[System.Xml.Serialization.XmlIgnore()]
public bool iSpecified;


So now "i" can both have a value of null and seperately be specified as ommited.

String is more challenging since it can have null from the first place so "string?" is meaningless. We would expect to have:


[System.Xml.Serialization.XmlElement(IsNullable=true)]
public string s;
[System.Xml.Serialization.XmlIgnore()]
public bool sSpecified;


However for some reason .Net does not generate this for the client side so it does not support out of the box a string which if both optional and nullable.
The solution is to manually add the sSpecified element and the XmlIgnore attribute as above to the client proxy or the server side code.
Note: For the client proxy updating the service reference will override any such change so use this method only if you have no other choice. For example use it when you try to interoperate with a framework that requires this.

@YaronNaveh

What's next? get this blog rss updates or register for mail updates!

4 comments:

kk said...

The serializer seems not to work well with int values.
If I have a Nullable and the soap response contains the empty element:



the deserialization gives an exception trying to convert to Int32 anyway.

Is it a known error?

Yaron Naveh (MVP) said...

in c# int cannot be created from an empty string. An "empty" int should be represented using the "int?" type. I guess if the element does not exists at all .Net might just use the default int value (0). But an empty value is definitely not valid.

BizTalker said...

What if your property is an object, not a simple type like a string? And to make it more complex, because this is what I'm dealing with today...

The element should be nillable, but it has a required attribute. so the element would have xsi:nil="true" and the required attribute would have a value. How to represent this in the proxy object and do the serialization/deserialization?

Yaron Naveh (MVP) said...

you shuold consider using the xsd:any type which will translate to XmlElement in the proxy, so you could serialize the xml yourself. Of course you should try the regular attributes before (as mentioned here) since they should work for complex objects also.