Update 24/05/2009: I totally blew it with this blog post. At the moment of this writing I was relatively new to WCF and got confused with the IsRequired property being some kind of input validation. Alas, this won't be the last mistake I'll ever make. I'll leave the content of this post online so I can go back and see how I made this mistake. Thanks to Travis Spencer and Daryl for bringing this to my attention.
Today I learned about a small issue when using the IsRequired property of the DataMember attribute in WCF. I had a String property on a data contract written like so:
[DataMember(IsRequired=true, Name="Code")]
public String Code
{
get{ return _code; }
set { _code = value; }
}
I thought I was set to go. To me it seemed that setting the IsRequired property to true ensured that whoever is going to make use of my service has to provide a value for this particular property. As so many times before, I was wrong. I tested it with setting this value on the client to a null reference. I even tried it without setting any value at all. It didn't throw an expected exception on the client, and a null reference was received on the service.
At first I thought I found a bug. Now what? I looked at the XML schema for the contract that was included in the WSDL. The following line appeared:
<xs:element name="Code" nillable="true" type="xs:string">
The minOccurs attribute is not included, so this means it defaults to 1 which is exactly what I wanted. Now, notice the nillable="true". It seems that for reference types, the nillable attribute is emitted by WCF and that this cannot be altered or changed through the DataMember attribute.
The reason why its possible to send null reference values to the service is because this is the default value for String. Even if I don't specify a value for it, it automatically defaults to a null reference that is sent to the service. The EmitDefaultValue property of the DataMember attribute is responsible for this. By default it is set to true, which means that a null reference is sent for every reference type in a data contract. In order to solve my problem, I had to change the code snippet to:
[DataMember(EmitDefaultValue=false, IsRequired=true,
Name="Code")]
public String Code
{
get{ return _code; }
set { _code = value; }
}
This ensured that the client always has to specify a value for this particular property in my data contract. The XML schema is changed like so:
<xs:element name="Module" nillable="true" type="xs:string">
<xs:annotation>
<xs:appinfo>
<DefaultValue EmitDefaultValue="false"/>
</xs:appinfo>
</xs:annotation>
</xs:element>
Now think about a Int32 property on the data contract.
[DataMember(IsRequired=true, Name="SomeNumber")]
public Int32 SomeNumber
{
get{ return _someNumber; }
set { _someNumber = value; }
}
Because its a value type, it is described in the XML schema like the following:
<xs:element name="SomeNumber" type="xs:int"/>
It means that you have to specify a value for SomeNumber. Making this property nullable changes the XML schema to:
<xs:element name="SomeNumber" nillable="true"
type="xs:int"/>
Now its no longer necessary to provide a value for SomeNumber although IsRequired is not changed.
For me, putting an IsRequired property on the DataMember attribute is kind of misleading, certainly when it only does half of the things that its name promises.
The lesson I learned from this is that one should not rely on WCF to enforce the use of the data contract. As I wrote in a previous blog post, making use of design-by-contract in the underlying domain is necessary in order to prevent improper use. Be aware to use IsRequired in combination of EmitDefaultValue in order to achieve the strictness you want from your data contract.