Leaving a SOAP field element empty results in a cast error for native types. (sadly cannot use xsi:nil="true" due to client constraints)
Marking the WCF contract native type as nullable<> does not appear to be enough to stop the following error being returned to the client.
The string '' is not a valid Boolean value. at System.Xml.XmlConvert.ToBoolean(String s) at System.Xml.XmlConverter.ToBoolean(String value) System.FormatException
does anyone know the best method of instructing the DataContractSerializer to convert empty elements to be deserialized to null?
My example WCF service contract;
[ServiceContract()]
public interface IMyTest
{
[OperationContract]
string TestOperation(TestRequest request);
}
[ServiceBehavior()]
public class Settings : IMyTest
{
public string TestOperation(TestRequest request)
{
if (request.TestDetail.TestBool.HasValue)
return "Bool was specified";
else
return "Bool was null";
}
}
[DataContract()]
public class TestRequest
{
[DataMember(IsRequired = true)]
public int ID { get; set; }
[DataMember(IsRequired = true)]
public TestDetail TestDetail { get; set; }
}
[DataContract()]
public class TestDetail
{
[DataMember()]
public bool? TestBool { get; set; }
}
How can we get WCF to accept the following submission;
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ster="mynamespace">
<soapenv:Header/>
<soapenv:Body>
<ster:TestOperation>
<ster:request>
<ster:ID>1</ster:ID>
<ster:TestDetail>
<ster:TestBool></ster:TestBool>
</ster:TestDetail>
</ster:request>
</ster:TestOperation>
</soapenv:Body>
</soapenv:Envelope>
The client is only able to change the value it inserts <ster:TestBool>{here}</ster:TestBool>
so true false or nothing are the only options.
Ok I believe I have cracked this by using an Operation Behavior to modify the underlying message before its formatted via IDispatchMessageFormatter.
The following code provides a solution against a service that is based on WCF file-less activation.
I wanted to have my IOperationBehavior live in the form of a Attribute class. Then I could simply decorate each Service Operation with my new attribute which would instigate the IOperationBehavior for that Operation - very nice and simple for the end user.
The key problem is where you apply the behavior, this is critical. The order of the operation behaviors that are called by WCF when applying the behavior via an attribute are different to when applying at the service host. The attribute based order is as follows:
For some reason an operation behavior (only when applied via the use of an attribute) will be called before the DataContractSerializerOperationBehavior. This is a problem because in my behavior I want to delegate deserialization to the DataContractSerializerOperationBehavior Formatter (passed into my behavior as the inner formatter) within my formatter, after I have adjusted the message (see code). I don't want to have to re-write a deserialization routine when Microsoft provided a perfectly good deserializer already. I merely correct the XML in the first instance so that blanks are converted to nulls which are correctly represented within the XML so that the DataContractSerializer can tie them up to nullable types in the service interface.
So this means we cannot use attribute-based behaviors as they were intended since WCF may well be broken in a rather subtle way here since I can see no reason for this phenomenon. So we can still add an IOperationBehavior to an operation, we just have to manually assign it at the service host creation stage, because then our IOperationBehavior is inserted into the 'correct' sequence, that is, after the DataContractSerializerOperationBehavior has been created, only then can I get a reference to the inner formatter.
Perhaps since I am only modifying the incoming message, I could instead use IDispatchMessageInspector which will remove the dependency on the IDispatchMessageFormatter activation order. But this works for now ;)
Usage:
A. if you have .svc simply reference MyServiceHost
B. if your using file-less activation services, add this to your web.config file