WCF Spike FaultContract, FaultException and Validation
Ready to have some fun… Today I spent the day investigating WCF FaulContracts and FaultException and some best practices for argument validation. I’m going to do the same in a future post on Workflow Services but I felt it best to really understand the topic from a WCF point of view first.
Investigation Questions
- What happens when a service throws an exception?
- What happens if a service throws a FaultException?
- What happens if the service operation includes a FaultContract and it throws a FaultException<TDetail>?
- How can I centralize validation of DataContracts?
Scenario 1: WCF Service throws an exception
Given
- A service that throws an ArgumentOutOfRangeException
- There is no <serviceDebug> or <serviceDebug includeExceptionDetailInFaults="false" /> in web.config
When
- The service is invoked with a data that will cause the exception
Then
- The client proxy will catch a System.ServiceModel.FaultException
- The FaultCode name will be “InternalServiceFault”
- The Fault.Reason will be
"The server was unable to process the request due to an internal error. For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs."
Conclusions
No surprises here, probably anyone who has done WCF for more than 5 minutes has run into this. For more information see the WCF documentation Sending and Receiving Faults. You might be tempted to just turn on IncludeExceptionDetailInFaults but don’t do it because it can lead to security vulnerabilities. Instead you need a better strategy for dealing with exceptions and that means you need to understand FaultException
Scenario 2: WCF Service throws a FaultException
As we saw in the previous example, WCF already throws a FaultException for you when it encounters an unhandled exception. The problem is in this case that we want to let the caller know they sent an invalid argument even when they are not debugging.
Given
- A service that throws a FaultException
public string GetDataFaultException(int data)
{
if (data < 0)
{
// Let the sender know it is their problem
var faultCode =
FaultCodeFactory.CreateVersionAwareSenderFaultCode(
ContractFaultCodes.InvalidArgument.ToString(), ContractConstants.Namespace);
var faultReason = string.Format(Resources.ValueOutOfRange, "Data", Resources.ValueGreaterThanZero);
throw new FaultException(faultReason, faultCode);
}
return "Data: " + data;
}
When
- The service is invoked with a data value that will cause an exception
Then
- The client proxy will catch a System.ServiceModel.FaultException
- The FaultCode.Name will be “Client” (for SOAP 1.1) or “Sender” (for SOAP 1.2)
- The Fault.Reason will be Argument Data is out of range value must be greater than zero
Conclusions
The main thing is that the client now gets a message saying that things didn’t work and its their fault. They can tell by looking at the FaultException.Code.IsSenderFault property. In my code you’ll notice a class I created called CreateVersionAwareSenderFaultCode to help deal with the differences between SOAP 1.1 and SOAP 1.2. You will find it in the sample.
Some interesting things I learned while testing this
- You should specify a Sender fault if you determine that there is a problem with the message and it should not be resubmitted
- BasicHttpBinding does SOAP 1.1 while the other bindings do SOAP 1.2 so if you are working with both you need to do the version aware sender fault code.
- FaultException.Action never shows up on the client side so don’t bother with it.
- FaultException.HelpLink and FaultException.Data (and other properties inherited from Exception) do not get serialized and will show up empty on the client side so you can’t use them for anything.
While this is better than throwing unhandled exceptions, there is an even better way and that is FaultContracts
Scenario 3: WCF Service with a FaultContract
Given
- A service operation with a FaultContract
- and a service that throws a FaultException<TDetail>
var argValidationFault = new ArgumentValidationFault
{
ArgumentName = "Data",
Message = string.Format(Resources.ValueOutOfRange, "Data", Resources.ValueGreaterThanZero),
HelpLink = GetAbsoluteUriHelpLink("DataHelp.aspx"),
};
throw new FaultException<ArgumentValidationFault>(argValidationFault, argValidationFault.Message, faultCode);
When
- The service is invoked with a data value that will cause an exception
Then
- The client proxy will catch a System.ServiceModel.FaultException
- The FaultCode.Name will be “Client” (for SOAP 1.1) or “Sender” (for SOAP 1.2)
- The Fault.Reason will be Argument Data is out of range value must be greater than zero
- The FaultDetail will be the type TDetail which will be included in the generated in the service reference
Conclusions
This is the best choice. It allows you to pass all kinds of information to clients and it makes your error handling capability truly first class. In the sample code one thing I wanted to do was to use the FaultException.HelpLink property to pass a Url to a help page. Unfortunately I learned that none of System.Exception’s properties are propagated to the sender. No problem, I just added a HelpLink property to my ArgumentValidationFault type and used it instead in FaultException.Details.HelpLink
Some interesting things I learned while testing this
- You can specify an interface in the [FaultContract] attribute but it didn’t seem to work on the caller side for catching a FaultException<T>
Recommendation
Use [FaultContract] on your service operations. You should probably create a base type for your details and perhaps have a few subclasses for special categories of things. Remember that whatever you expose in the FaultContract is part of your public API and that versioning considerations that apply to other DataContracts apply to your faults as well.
Happy Coding!
Ron
https://blogs.msdn.com/rjacobs
Twitter: @ronljacobs
Comments
- Anonymous
January 15, 2011
The comment has been removed - Anonymous
January 17, 2011
@Rory - I agree with you. I'd prefer to stay away from KnownType if at all possible.