次の方法で共有


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

  1. What happens when a service throws an exception?
  2. What happens if a service throws a FaultException?
  3. What happens if the service operation includes a FaultContract and it throws a FaultException<TDetail>?
  4. 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