Fault Propagation

While the default error-masking policy of WCF is a best practice, there are times when you are refrained from relying on it. This is typically the case when there is an existing application (or communication pattern) in place, and the service is required to throw particular exceptions as it processes inputs, reaches certain states, or encounters errors. The client is required to respond to these exceptions in a prescribed way. Obviously, controlling the flow of the application using expectations is hardly a good idea, as it leads to nonstructured programming and couples the client to the service. And yet, the underlying requirements remain: the service is required to report specific errors to the client, and the default masking of the errors by WCF precludes that. Another fundamental problem pertaining to propagating the error to the client is that exceptions are technology-specific, and as such should not be shared across the service boundary. For seamless interoperability, you need a way to map technology-specific exceptions to some neutral error information. This representation is called a SOAP fault. SOAP faults are based on an industry standard that is independent of any technology-specific exceptions, such as CLR, Java, or C++ exceptions. To return a SOAP fault (or just a fault, for short), the service cannot throw a raw CLR exception. Instead, the service must throw an instance of the FaultException<T> class, defined in Example 6-1.

Example 6-1. The FaultException<T> class

[Serializable] //More attributes
public class FaultException : CommunicationException
{
   public FaultException( );
   public FaultException(string reason);
   public FaultException(FaultReason reason);
   public virtual MessageFault CreateMessageFault( );
   //More members
}

[Serializable]
public class FaultException<T> : FaultException
{
   public FaultException(T detail);
   public FaultException(T detail,string reason);
   public FaultException(T detail,FaultReason reason);
   //More members
}

FaultException<T> is a specialization of FaultException, so any client that programs against FaultException will be able to handle FaultException<T> as well.

The type parameter T for FaultException<T> conveys the error details. The detailing type can be any type, and doesn't necessarily have to be an Exception-derived class. The only constraint is that the type must be marked as serializable or a data contract.

Example 6-2 demonstrates a simple calculator service that throws a FaultException<DivideByZeroException> in its implementation of the Divide( ) operation when asked to divide by zero.

Example 6-2. Throwing a FaultException<T>

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

class Calculator : ICalculator
{
   public double Divide(double number1,double number2)
   {
      if(number2 == 0)
      {
         DivideByZeroException exception = new DivideByZeroException( );
         throw new FaultException<DivideByZeroException>(exception);
      }
      return number1 / number2;
   }
   //Rest of the implementation
}

Instead of FaultException<DivideByZeroException>, the service could also have thrown a non-Exception-derived class:

throw new FaultException<double>(number2);

However, I find that using an Exception-derived detailing type is more in line with conventional .NET programming practices and results in more readable code. In addition, it allows for exception promotion, discussed later in this chapter.

The reason parameter passed to the constructor of FaultException<T> is used as the exception message. You can pass a string for the reason:

DivideByZeroException exception = new DivideByZeroException( );
throw new FaultException<DivideByZeroException>(exception,"number2 is 0");

or you can pass a FaultReason, which is useful when localization is required.

Fault Contracts

By default, any exception thrown by a service reaches the client as a FaultException. This is the case even if the service throws a FaultException<T>. The reason is that anything beyond communication errors that the service wishes to share with the client must be part of the service contract in order for the service to inform WCF that it wishes to pierce the error mask. To that end, WCF provides fault contracts, which are a way for the service to list the types 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 fault contracts, the service enables its WCF clients 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. That is, 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 a 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 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.

Warning

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 in an InvalidOperationException at service load time (or when the proxy is created).

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 contracts. 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(FaultException exception)
{...}

catch(CommunicationException exception)
{...}

catch(TimeoutException exception)
{...}

catch(Exception exception)
{...}

Note that the client can still encounter communication exceptions, or any other exception thrown by the service.

The client can choose to treat all non-communication 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)
{...}

Tip

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, even when the service throws an exception listed in a service-side fault contract, the exception will manifest itself on the client as a plain FaultException, not as the contracted fault.

Faults and channels

Listing an expected error in a fault contract hardly makes it an exceptional unexpected case. As a result, when the service throws an exception listed in a 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. This enables the service class to treat the errors listed in the fault contracts differently from regular exceptions, knowing that they will not fault the channel. This ability is not limited to the service class, though. If any downstream .NET class the service invokes throws such an error, it will not fault the channel to the client. The problem is, how can downstream classes know about the fault contracts of the upstream services that call them? Clearly, having this knowledge present downstream introduces undesirable coupling into the system.

To support the ability of a downstream class to throw an exception without faulting the channel, WCF treats any FaultException (or FaultException-derived class) as a special case that does not fault the channel. In fact, WCF does not actually treat FaultException<T> itself as a special case at all—the reason a fault listed in the contract does not fault the channel is because it is derived from FaultException, not because it is listed in the contract.

Even without any fault contracts, the service (or any downstream object it uses) can throw an instance of FaultException directly:

throw new FaultException("Some Reason");

The Message property of the exception object on the client side will be set to the reason construction parameter of FaultException. I call this throwing an unknown fault that will not fault the communication channel, so the client can keep using the proxy as if the exception was part of a fault contract. Throwing an unknown fault also allows the client to handle the exception separately from any other communication error.

Tip

Any FaultException<T> thrown by the service will reach the client as either a FaultException<T> or a FaultException. If no fault contract is in place (or if T is not in the contract), both a FaultException and a FaultException<T> thrown by the service will reach the client as FaultException.

Fault Debugging

A deployed service should be decoupled as much as possible from its clients, declaring in the service fault contracts only the absolute bare minimum and providing as little information as possible about any errors that occur. However, during testing and debugging, it is very useful to include all exceptions in the information sent back to the client. In fact, for a test client, it is instrumental to know exactly which error was thrown as a result of a particular input or use case, to see if the test cases break the service as they should. In such a case, dealing with the all-encompassing yet opaque FaultException is simply inadequate. For that purpose, you should 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 intended 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 a Detail.Type property containing 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( );
try
{
   proxy.MethodWithError( );
}
catch(FaultException<ExceptionDetail> exception)
{
   Debug.Assert(exception.Detail.Type ==
                                    typeof(InvalidOperationException).ToString( ));
   Debug.Assert(exception.Message == "Some error");
}

Since FaultException<ExceptionDetail> derives from FaultException, throwing it will not fault the channel. I do not consider this the desired behavior.

Including exceptions declaratively

The ServiceBehavior attribute offers IncludeExceptionDetailInFaults, a Boolean property 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 a similar effect as the code 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 contracts is unaffected and is propagated as-is to the client.

Another important difference between using the declarative support for including the fault details compared with manually throwing FaultException<ExceptionDetail> is that it will correctly fault the channel, preventing the client from reusing the proxy (if a transport session was present).

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 avoid this potential pitfall automatically you can use conditional compilation, as shown in Example 6-6.

Example 6-6. Setting IncludeExceptionDetailInFaults 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
{...}

Host and exception diagnostics

Obviously, including all exceptions in the fault message contributes greatly in debugging, but it's also useful when you're trying to analyze a problem in an already deployed service. Fortunately, you can set IncludeExceptionDetailInFaults to true both programmatically and administratively in the host config file. To set this behavior 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;
      }
   }
   //More members
}

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 config file 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.

Exception extraction

While including the exception details in the fault is a useful diagnostic technique, it is a cumbersome programming model: the client has to take extra steps to extract the error information out of the ExceptionDetail object. More deterring is the fact that the client must use a single massive catch statement (that catches a single FaultException<ExceptionDetail>) to catch all possible exceptions, and sort them all inside the catch statement. In the world of .NET, this is akin to always catching a mere Exception, and avoiding cascading catch statements.

In addition, when writing a test client, you want to know as much as possible about the original exception that happened on the service side, since your test cases are predicated on producing specific errors. The test client could extract the original exception from the ExceptionDetail object and recursively build the inner exception chain. However, that would be tedious and redundant, and it would require repeated code on every use of the service by the test client. It is therefore better to encapsulate these steps in the proxy using C# 3.0 extensions. To that end, I wrote the ExtractException( ) extension method to FaultException<ExceptionDetail>, defined as:

public static class DebugHelper
{
   public static Exception ExtractException(
                                      this FaultException<ExceptionDetail> fault);
   //More members
}

The implementation of FaultException<ExceptionDetail> has nothing to do with WCF, so I won't show it here (but it is available with ServiceModelEx). The best way of using the extension is to encapsulate it within the proxy, as shown in Example 6-9.

Example 6-9. Automatically extracting the exception

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void MethodWithError( );
}
class MyContractClient : ClientBase<IMyContract>,IMyContract
{
   public MyContractClient( )
   {}
   /* More constructors */

   public void MethodWithError( )
   {
      try
      {
         Channel.MethodWithError( );
      }
      catch(FaultException<ExceptionDetail> exception)
      {
         Abort( );
         throw exception.ExtractException( );
      }
   }
}

In Example 6-9, in the case of a FaultException<ExceptionDetail>, the proxy aborts itself (to prevent the proxy from being used again) regardless of whether a transport session is present or how exactly the service threw the exception. The proxy uses the extension method to throw the extracted exception, allowing the client to catch the raw CLR exception. For example, for this service definition in debug mode:

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void MyMethod( );
}

[ServiceBehavior(IncludeExceptionDetailInFaults =
                 DebugHelper.IncludeExceptionDetailInFaults)]
class MyService : IMyContract
{
public void MyMethod( )
   {
      throw new InvalidOperationException( );
   }
}

when using the proxy from Example 6-9, the client can expect to catch an InvalidOperationException:

MyContractClient proxy = new MyContractClient( );
try
{
   proxy.MyMethod( );
}
catch(InvalidOperationException exception)
{...}

Warning

Exception extraction should be used judiciously, only in specific diagnostic and testing cases, since it negates the core benefit of fault masking and decoupling from the nature of the error and the technology.

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 define fault contracts, as shown in Example 6-10.

Example 6-10. Callback contract with fault contract

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

Tip

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

However, unlike with a normal service invocation, what is propagated to the service and how the error manifests itself also depend upon the following:

  • When the callback is being invoked (i.e., whether the callback is invoked during a service call to its calling client or 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-band—that is, by some party other than the service during a service operation—the callback behaves like a normal WCF operation invocation. Example 6-11 demonstrates out-of-band invocation of the callback contract defined in Example 6-10.

Example 6-11. 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 = (callback)=>
                                           {
                                              try
                                              {
                                                 callback.OnCallBack( );
                                              }
                                              catch(FaultException<...> 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 throws a FaultException or any of its subclasses, 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 occurs, you should avoid using the callback channel.

Likewise, when the service calls back to its calling client, if the callback throws a FaultException or any of its subclasses it will not fault the callback channel, and the service can catch the exception and continue using the callback channel (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 exception)
      {...}
   }
}

Note that the service must be configured for reentrancy to avoid a deadlock, as explained in Chapter 5.

The scenario gets considerably more complex when the service invokes the callback during a service operation, calling back to its calling client, and the exception is not FaultException and does not derive from FaultException. Recall that all bindings capable of duplex communication maintain a transport-level session. The exception during the callback terminates the transport session from the client to the service, as well as from the service to the client (in the case of the WS dual binding). How that exception reflects itself to the client and the service is a product of the binding.

Since both the TCP and IPC bindings use the same transport for calls from the client to the service and callbacks from the service to the client, when the callback throws such an exception the client that called the service in the first place immediately receives a CommunicationException, even if the service catches the exception. This is a direct result of reusing the same transport for both directions, and having faulted the callback transport (which is tantamount to faulting the client-to-service transport as well). The service can catch and handle the exception, but the client still gets its exception:

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

If the service uses the WS dual binding, when the callback throws such an exception it has to fault both transports used. 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 it will eventually be unblocked with a TimeoutException:

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

Tip

The large degree of discrepancy in callback behaviors just described is a design deficiency of WCF; that is, it is a direct result of the channels architecture. It may be partially addressed in future releases.

Callback 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, which can be used to include all non-contract exceptions in the message:

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

As for the service, including the exceptions is instrumental in debugging:

[CallbackBehavior(IncludeExceptionDetailInFaults =
                  DebugHelper.IncludeExceptionDetailInFaults)]
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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.2.225