WCF Client Fault Size Limit
Configuring WCF message quotas on the server and client sides is not a trivial task. There are quite a few settings that you can tweak, including limits for a single string’s length in a message, the number of objects in the serialization graph, the serialization depth, and the array size.
However, there’s one limit my service hit a few days ago that I wasn’t at all aware of—I didn’t even realize there was a reason for it to exist. Consider the following service method:
public string Foo(bool throwFault, int stringLength)
{
string s = new string('A', stringLength);
if (throwFault)
throw new FaultException<string>(s);
return s;
}
And the following service method invocation:
proxy.Foo(false, 10000);
The service host is using the NetTcpBinding, with the default configuration settings. The result of the previous line would be the following exception:
System.ServiceModel.CommunicationException: Error in deserializing body of reply message for operation 'Foo'. The maximum string content length quota (8192) has been exceeded while reading XML data. This quota may be increased by changing the MaxStringContentLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader.
All righty then, off we go and change the MaxStringContentLength property to 200K. The above now works, but the following fails:
proxy.Foo(false, 100000);
This time, we’re hitting this:
System.ServiceModel.CommunicationException: The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.
Oh, now we have to tune the maximum message side on the receiving binding. After doing that, the above works, but the following fails:
proxy.Foo(true, 100000);
With the only difference being that this time, we’re throwing a fault with a 100000-character message, instead of simply returning it from the service operation. Evidently, there is some difference here, as the exception reads:
System.ServiceModel.QuotaExceededException: The size necessary to buffer the XML content exceeded the buffer quota.
And the call stack is on the client side:
System.Runtime.BufferedOutputStream.WriteCore(…)
System.Runtime.BufferedOutputStream.Write(…)
System.Xml.XmlBinaryNodeWriter.FlushBuffer()
System.Xml.XmlStreamNodeWriter.UnsafeWriteUTF8Chars(…)
System.Xml.XmlBinaryNodeWriter.UnsafeWriteText(…)
System.Xml.XmlBinaryNodeWriter.WriteText(…)
System.Xml.XmlBinaryNodeWriter.WriteEscapedText(…)
System.Xml.XmlBaseWriter.WriteChars(…)
System.Xml.XmlBinaryWriter.WriteTextNode(…)
System.Xml.XmlDictionaryWriter.WriteNode(…)
System.ServiceModel.Channels.ReceivedFault.CreateFault12Driver(…)
System.ServiceModel.Channels.MessageFault.CreateFault(…)
System.ServiceModel.Channels.ServiceChannel.HandleReply(…)
System.ServiceModel.Channels.ServiceChannel.Call(…)
System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(…)
System.ServiceModel.Channels.ServiceChannelProxy.Invoke(…)
After using Reflector to inspect the implementation of ServiceChannel.HandleReply, I realized that there’s another quota being used here—the client runtime’s maximum fault size, i.e. the maximum size of a fault message that the client can process.
Modifying a property of the client runtime is not completely trivial, and the solution I’m showing below uses a contract behavior. It’s also possible to accomplish using an endpoint behavior, or a channel factory customization.
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public sealed class MaximumFaultSizeAllowedAttribute :
Attribute, IContractBehavior
{
private readonly int _maxFaultSize;
public MaximumFaultSizeAllowedAttribute(
int maxFaultSize)
{
_maxFaultSize = maxFaultSize;
}
public void Validate(
ContractDescription contractDescription,
ServiceEndpoint endpoint)
{
}
public void ApplyDispatchBehavior(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
DispatchRuntime dispatchRuntime)
{
}
public void ApplyClientBehavior(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
clientRuntime.MaxFaultSize = _maxFaultSize;
}
public void AddBindingParameters(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters)
{
}
}
Placing this attribute on the service contract configures appropriately the maximum fault size allowed on the client side.
[This behavior is obscure enough to warrant someone opening a Connect bug claiming that reader quotas are ignored when throwing exceptions. The bug was closed two days later.]