Section 6.2. Fault Contracts


6.2. Fault Contracts

By default, any exception thrown by the service reaches the client as a FaultException. The reason is that anything beyond communication errors that the service wishes to share with the client must be part of the service contractual behavior. To that end, WCF provides fault contractsa way for the service to list the type of errors it can throw. The idea is that these types of errors should be the same as the type parameters used with FaultException<T>, and by listing them in a fault contract, a WCF client will be able to distinguish between contracted faults and other errors.

The service defines its fault contracts using the FaultContractAttribute:

 [AttributeUsage(AttributeTargets.Method,AllowMultiple = true,Inherited = false)] public sealed class FaultContractAttribute : Attribute {    public FaultContractAttribute(Type detailType);    //More members } 

You apply the FaultContract attribute directly on a contract operation, specifying the error detailing type, as shown in Example 6-3.

Example 6-3. Defining a fault contract

 [ServiceContract] interface ICalculator {    [OperationContract]    double Add(double number1,double number2);    [OperationContract]    [FaultContract(typeof(DivideByZeroException))]    double Divide(double number1,double number2);    //More methods } 

The effect of the FaultContract attribute is limited to the method it decorates. Only that method can throw that fault and have it propagated to the client. In addition, if the operation throws an exception that is not in the contract, it will reach the client as a plain FaultException. To propagate the exception, the service must throw exactly the same detailing type listed in the fault contract. For example, to satisfy this fault contract definition:

 [FaultContract(typeof(DivideByZeroException))] 

The service must throw FaultException<DivideByZeroException>. The service cannot even throw a subclass of the fault contract's detailing type and have it satisfy the contract:

 [ServiceContract] interface IMyContract {    [OperationContract]    [FaultContract(typeof(Exception))]    void MyMethod( ); } class MyService : IMyContract {    public void MyMethod( )    {       //Will not satisfy contract       throw new FaultException<DivideByZeroException>(new DivideByZeroException( ));    } } 

The FaultContract attribute is configured to allow multiple usages so that you can list multiple fault contracts in a single operation:

 [ServiceContract] interface ICalculator {    [OperationContract]    [FaultContract(typeof(InvalidOperationException))]    [FaultContract(typeof(string))]    double Add(double number1,double number2);    [OperationContract]    [FaultContract(typeof(DivideByZeroException))]    double Divide(double number1,double number2);    //More methods } 

This enables the service to throw any of the exceptions in the contracts and have them propagate to the client.

You cannot provide a fault contract on a one-way operation, because in theory nothing should be returned from a one-way operation:


 //Invalid definition [ServiceContract] interface IMyContract {    [OperationContract(IsOneWay = true)]    [FaultContract(...)]    void MyMethod( ); } 

Trying to do so will result with an InvalidOperationException at the service load time.

6.2.1. Fault Handling

The fault contracts are published along with the rest of the service metadata. When a WCF client imports that metadata, the contract definitions contain the fault contracts as well as the fault detailing type definition, including the relevant data contract. This last point is important if the detailing type is some custom exception type with various dedicated fields.

The client can expect to catch and handle the imported fault types. For example, when you write a client against the contract shown in Example 6-3, the client can catch FaultException<DivideByZeroException>:

 CalculatorClient proxy = new CalculatorClient( ); try {    proxy.Divide(2,0);    proxy.Close( ); } catch(FaultException<DivideByZeroException> exception) {...} catch(CommunicationException exception) {...} 

Note that the client can still encounter communication exceptions.

The client can choose to treat all noncommunication service-side exceptions uniformly by simply handling only the FaultException base exception:

 CalculatorClient proxy = new CalculatorClient( ); try {    proxy.Divide(2,0);    proxy.Close( ); } catch(FaultException exception) {...} catch(CommunicationException exception) {...} 

A somewhat esoteric case is when the client's developer manually changes the definition of the imported contract by removing the fault contract on the client side. In that case, when the service throws an exception listed in the service-side fault contract, the exception will manifest itself on the client as FaultException, not as the contracted fault.


When the service throws an exception listed in the service-side fault contract, the exception will not fault the communication channel. The client can catch that exception and continue using the proxy, or safely close the proxy.

6.2.2. Unknown Faults

The FaultException<T> class is derived from the class FaultException. The service (or any downstream object it uses) can throw an instance of FaultException directly:

 throw new FaultException("Some Reason"); 

FaultException is a special type of an exception I call an unknown fault. An unknown fault is propagated to the client as a FaultException. The unknown fault will not fault the communication channel, so the client can keep using the proxy as if the exception was part of the fault contract.

Note that any FaultException<T> thrown by the service always reaches the client as a FaultException<T> or as FaultException. If no fault contract is in place (or if T is not in the contract) then both FaultException and FaultException<T> thrown by the service reach the client as FaultException.


The Message property of the exception object on the client side will be set to the reason construction parameter of FaultException. The main use of FaultException is by downstream objects that are unaware of the fault contracts being used by their calling services. To avoid coupling such downstream objects to the top-level service, they can throw FaultException if they do not wish to fault the channel or if they wish to allow the client to handle the exception separately from any other communication error.

6.2.3. Fault Debugging

For a service already deployed, it is preferable to decouple that service from its clients, declare in the service fault contracts only the absolute bare minimum, and provide as little information as possible about the original error. However, during testing and debugging, it is very useful to include all exceptions in the information sent back to the client. This enables you to use a test client and a debugger to analyze the source of the error, instead of dealing with the all-encompassing yet opaque FaultException. For that purpose, use the ExceptionDetail class defined as:

 [DataContract] public class ExceptionDetail {    public ExceptionDetail(Exception exception);    [DataMember]    public string HelpLink    {get;private set;}    [DataMember]    public ExceptionDetail InnerException    {get;private set;}    [DataMember]    public string Message    {get;private set;}    [DataMember]    public string StackTrace    {get;private set;}    [DataMember]    public string Type    {get;private set;} } 

You need to create an instance of ExceptionDetail and initialize it with the exception you want to propagate to the client. Next, instead of throwing the indented exception, throw a FaultException<ExceptionDetail> with the instance of ExceptionDetail as a construction parameter, and also provide the original exception's message as the fault reason. This sequence is shown in Example 6-4.

Example 6-4. Including the service exception in the fault message

 [ServiceContract] interface IMyContract {    [OperationContract]    void MethodWithError( ); } class MyService : IMyContract {    public void MethodWithError( )    {       InvalidOperationException exception =                                        new InvalidOperationException("Some error");       ExceptionDetail detail = new ExceptionDetail(exception);       throw new FaultException<ExceptionDetail>(detail,exception.Message);    } } 

Doing so will enable the client to discover the original exception type and message. The client-side fault object will have the Detail.Type property that contains the name of the original service exception, and the Message property will contain the original exception message. Example 6-5 shows the client code processing the exception thrown in Example 6-4.

Example 6-5. Processing the included exception

 MyContractClient proxy = new MyContractClient(endpointName); try {    proxy.MethodWithError( ); } catch(FaultException<ExceptionDetail> exception) {    Debug.Assert(exception.Detail.Type ==                 typeof(InvalidOperationException).ToString( ));    Debug.Assert(exception.Message == "Some error"); } 

6.2.3.1. Declarative exceptions inclusion

The ServiceBehavior attribute offers the Boolean property IncludeExceptionDetailInFaults, defined as:

 [AttributeUsage(AttributeTargets.Class)] public sealed class ServiceBehaviorAttribute : Attribute, ... {    public bool IncludeExceptionDetailInFaults    {get;set;}    //More members } 

IncludeExceptionDetailInFaults defaults to false. Setting it to TRue as in this snippet:

 [ServiceBehavior(IncludeExceptionDetailInFaults = true)] class MyService : IMyContract {...} 

has the same effect as in Example 6-4, only automated: all noncontractual faults and exceptions thrown by the service or any of its downstream objects are propagated to the client and included in the returned fault message for the client program to process them, as in Example 6-5:

 [ServiceBehavior(IncludeExceptionDetailInFaults = true)] class MyService : IMyContract {    public void MethodWithError( )    {       throw new InvalidOperationException("Some error");    } } 

Any fault thrown by the service (or its downstream objects) that is listed in the fault contract is unaffected, and is propagated as is to the client.

While including all exceptions is beneficial for debugging, great care should be taken to avoid shipping and deploying the service with IncludeExceptionDetailInFaults set to true. To automate this and avoid the potential pitfall, you can use conditional compilation as shown in Example 6-6.

Example 6-6. SettingIncludeExceptionDetailInFaults to true in debug only

 public static class DebugHelper {    public const bool IncludeExceptionDetailInFaults = #if DEBUG       true; #else       false; #endif } [ServiceBehavior(IncludeExceptionDetailInFaults =                  DebugHelper.IncludeExceptionDetailInFaults)] class MyService : IMyContract {...} 

When IncludeExceptionDetailInFaults is true, the exception will actually fault the channel, so the client cannot issue subsequent calls.


6.2.3.2. Host and exceptions diagnostics

Obviously, including all exceptions in the fault message contributes greatly in debugging, but it also has a use when trying to analyze a problem in an already-deployed service. Fortunately, you can set IncludeExceptionDetailInFaults to TRue by the host, both programmatically and administratively in the host config file. When you set it programmatically, before opening the host, you need to find the service behavior in the service description and set the IncludeExceptionDetailInFaults property:

 ServiceHost host = new ServiceHost(typeof(MyService)); ServiceBehaviorAttribute debuggingBehavior =                        host.Description.Behaviors.Find<ServiceBehaviorAttribute>( ); debuggingBehavior.IncludeExceptionDetailInFaults = true; host.Open( ); 

You can streamline this procedure by encapsulating it in ServiceHost<T>, as shown in Example 6-7.

Example 6-7. ServiceHost<T> and returning unknown exceptions

 public class ServiceHost<T> : ServiceHost {    public bool IncludeExceptionDetailInFaults    {       set       {          if(State == CommunicationState.Opened)          {             throw new InvalidOperationException("Host is already opened");          }          ServiceBehaviorAttribute debuggingBehavior =                             Description.Behaviors.Find<ServiceBehaviorAttribute>( );          debuggingBehavior.IncludeExceptionDetailInFaults = value;       }       get       {          ServiceBehaviorAttribute debuggingBehavior =                             Description.Behaviors.Find<ServiceBehaviorAttribute>( );          return debuggingBehavior.IncludeExceptionDetailInFaults;       }    } } 

Using ServiceHost<T> is trivial and readable:

 ServiceHost<MyService> host = new ServiceHost<MyService>( ); host.IncludeExceptionDetailInFaults = true; host.Open( ); 

To apply this behavior administratively, add a custom behavior section in the host configfile and reference it in the service definition, as shown in Example 6-8.

Example 6-8. Administratively including exceptions in the fault message

 <system.serviceModel>    <services>      <service name = "MyService" behaviorConfiguration = "Debugging">         ...      </service>    </services>    <behaviors>       <serviceBehaviors>          <behavior name = "Debugging">             <serviceDebug includeExceptionDetailInFaults = "true"/>          </behavior>       </serviceBehaviors>    </behaviors> </system.serviceModel> 

The advantage of administrative configuration in this case is the ability to toggle the behavior in production post-deployment without affecting the service code.

6.2.4. Faults and Callbacks

Callbacks to the client can of course fail due to communication exceptions or because the callback itself threw an exception. Similar to service contract operations, callback contract operations can too define fault contracts, as shown in Example 6-9.

Example 6-9. Callback contract with fault contract

 [ServiceContract(CallbackContract = typeof(IMyContractCallback))] interface IMyContract {    [OperationContract]    void DoSomething( ); } interface IMyContractCallback {    [OperationContract]    [FaultContract(typeof(InvalidOperationException))]    void OnCallBack( ); } 

Callbacks in WCF are usually configured as one-way calls, and as such cannot define their own fault contracts.


However, unlike normal service invocation, what is propagated to the service and how the error manifests itself is also the product of the following:

  • When the callback is being invoked; meaning, whether the callback is being invoked during a service call to its calling client, or whether it is invoked out-of-band by some other party on the host side.

  • The binding used.

  • The type of the exception thrown.

If the callback is invoked out-of-bandthat is, by some party besides the service during a service operationthen the callback behaves like a normal WCF operation invocation. Example 6-10 demonstrates out-of-band invocation of the callback contract defined in Example 6-9.

Example 6-10. Fault handling in out-of-band invocation

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyService : IMyContract {    static List<IMyContractCallback> m_Callbacks =                                             new List<IMyContractCallback>( );    public void DoSomething( )    {       IMyContractCallback callback =                 OperationContext.Current.GetCallbackChannel<IMyContractCallback>( );       if(m_Callbacks.Contains(callback) == false)       {          m_Callbacks.Add(callback);       }    }    public static void CallClients( )    {    Action<IMyContractCallback> invoke = delegate(IMyContractCallback callback)                                         {                                             try                                             {                                                callback.OnCallback( );                                             }                          catch(FaultException<InvalidOperationException> exception)                                             {...}                                             catch(FaultException exception)                                             {...}                                             catch(CommunicationException exception)                                             {...}                                          };       m_Callbacks.ForEach(invoke);    } } 

As you can see, it is valid to expect to handle the callback fault contract because faults are propagated to the host side according to it. If the client callback throws an exception listed in the callback fault contract, or if the callback threw a FaultException, it will not fault the callback channel and you can catch the exception and continue using the callback channel. However, as with service calls, after an exception that is not part of the fault contract, avoid using the callback channel.

If the callback is invoked by the service during a service operation and the exception is listed in the fault contract or if the client callback threw a FaultException, the callback fault behaves just as with the out-of-band invocation:

 [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] class MyService : IMyContract {    public void DoSomething( )    {       IMyContractCallback callback =                 OperationContext.Current.GetCallbackChannel<IMyContractCallback>( );       try       {          callback.OnCallBack( );       }       catch(FaultException<int> exception)       {...}    } } 

Note that the service must be configured for reentrancy to avoid a deadlock, as explained in Chapter 5. Because the callback operation defines a fault contract, it is guaranteed not to be a one-way method, hence the need for reentrancy.

Both the out-of-band and the service callbacks as described so far provide for the expected intuitive behavior.

The scenario gets considerably more complex when the service invokes the callback and the callback operation throws an exception not listed in the fault contract (or not a FaultException).

If the service uses either the TCP or the IPC binding, then when the callback throws an exception not in the contract, the client that called the service in the first place immediately receives a CommunicationException, even if the service catches the exception. The service then gets a FaultException. The service can catch and handle the exception, but the exception faults the channel, so that the service cannot reuse it:

 [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] class MyService : IMyContract {    public void DoSomething( )    {       IMyContractCallback callback =                 OperationContext.Current.GetCallbackChannel<IMyContractCallback>( );       try       {          callback.OnCallBack( );       }       catch(FaultException exception)       {...}    } } 

If the service uses the dual WS binding, when the callback throws an exception not in the contract, the client that called the service in the first place immediately receives a CommunicationException, even if the service catches the exception. Meanwhile, the service is blocked and will eventually be unblocked with a timeout exception:

 [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] class MyService : IMyContract {    public void DoSomething( )    {       IMyContractCallback callback =                 OperationContext.Current.GetCallbackChannel<IMyContractCallback>( );       try       {          callback.OnCallBack( );       }       catch(TimeoutException exception)       {...}    } } 

The service cannot reuse the callback channel.

The large degree of discrepancy in callback behaviors just described is a design deficiency of WCF; that is, it is by design and not a defect. That said, it may be partially addressed in future releases.


6.2.4.1. Callbacks debugging

While the callback can use the same technique shown in Example 6-4 to manually include the exception in the fault message, the CallbackBehavior attribute provides the Boolean property IncludeExceptionDetailInFaults, used to include all nonfault contract exceptions (besides FaultException) in the message:

 [AttributeUsage(AttributeTargets.Class)] public sealed class CallbackBehaviorAttribute : Attribute,... {    public bool IncludeExceptionDetailInFaults    {get;set;}    //More members } 

Similar to the service, including the exceptions is instrumental in debugging:

 [CallbackBehavior(IncludeExceptionDetailInFaults = true)] class MyClient : IMyContractCallback {    public void OnCallBack( )    {       ...       throw new InvalidOperationException( );    } } 

You can also configure this behavior administratively in the client config file:

 <client>    <endpoint ... behaviorConfiguration = "Debug"    ...    /> </client> <behaviors>    <endpointBehaviors>       <behavior name = "Debug">          <callbackDebug includeExceptionDetailInFaults = "true"/>       </behavior>    </endpointBehaviors> </behaviors> 

Note the use of the endpointBehaviors tag to affect the client's callback endpoint.




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net