Exceptions and Faults

How to Handle Exceptions and Throw Faults from Your WCF Services

This installment of my monthly column explores exceptions and faults for WCF services. For more information on WCF, see "Security Practices for WCF " and " WCF Service Instanting "

The .NET Framework offers structured exception handling for exceptions to be caught, handled, thrown, or otherwise ignored and propagated up the call chain. At some point, if an exception goes unhandled to the top of the call chain, the .NET runtime terminates the executing thread. In a traditional Windows application, this terminates the application. For Web applications and services, an unhandled exception terminates the executing request thread but exception details may also be reported to the client. For exceptions to flow across process or machine boundaries in a distributed system, the exception must be transferred as part of a serialized message. WCF provides a mechanism for reporting exceptions as SOAP faults to calling clients, making it possible to flow exceptions across distributed boundaries that may also be technology and platform boundaries requiring interoperable communication.

In this article, I ll provide some background information on SOAP faults and their relationship to service metadata because they are the standard for propagating exceptions from Web services. Then I ll discuss how WCF services and clients handle CLR exceptions, and how to declare and throw proper SOAP faults.

 

SOAP Faults

A SOAP fault is a standards-based format for transferring exceptions between applications. The SOAP specification includes a definition for SOAP faults, providing structure for the contents of the message body when errors occur. This makes it possible for different technologies and platforms to provide plumbing to handle faults in a predictable manner. There are two specifications for SOAP faults: SOAP 1.1 and SOAP 1.2. Not only do these specifications describe the format for faults, they also supply guidance on the contents of faults (such as error codes) and on the way platforms should process faults. Though they essentially contain similar information when serialized, the naming conventions for XML elements were refactored slightly in the SOAP 1.2 specification. The table in Figure 1 compares elements exposed by each specification and describes their purpose.

 

SOAP 1.1

SOAP 1.2

Element Description

faultcode

Code

Required. Can be one of the specification s predefined codes or a custom code for the application. Predefined codes differ for SOAP 1.1 and SOAP 1.2. For the latter, Receiver and Sender are examples of predefined codes.

faultstring

Reason

Required. String explanation of the fault. SOAP 1.2 supports multiple translated reasons.

faultactor

Role

Optional. Required for all SOAP nodes except the ultimate receiver. A Uri describing the source of the fault.

detail

Detail

Optional. Required if the SOAP body could not be processed. Should provide information about the body element(s) that failed.

 

Node

Optional. Required for all SOAP nodes except for the ultimate receiver. A Uri describing the node that caused the failure.

Figure 1: Elements exposed by each SOAP specification.

Figure 2 shows an example of a simple SOAP 1.2 fault. At a minimum, a fault code and reason are always supplied. Services throwing SOAP faults should provide clients with a list of relevant fault codes (if they are not using the default codes from the SOAP specification and the reason is typically a simple error message to be displayed at the client).

<Fault xmlns:s="http://www.w3.org/2003/05/soap-envelope" >

   <Code>

       <Value>s:Sender</Value>

   </Code>

   <Reason>

       <Text xml:lang="en-US">An invalid operation has

        occurred.</Text>

   </Reason>

</Fault> 

Figure 2: A simple SOAP 1.2 fault.

Fortunately, the resulting XML for a SOAP fault is generated for you when exceptions or faults are thrown by a WCF service. Developers interact with CLR types representative of the fault to deal with error conditions. Let s turn our attention to the default behavior of WCF with relation to faults.

 

Debugging Exceptions

When clients call a service operation, it is possible that downstream business or data access components may throw an exception. If the service operation doesn t catch the exception, thus allowing it to flow up the channel stack, the channel is faulted and a SOAP fault is returned to the calling client.

The details of the original exception are not serialized with the SOAP fault by default. For example, consider the following exception:

throw new InvalidOperationException("The method or

 operation is not implemented."); 

This exception would generate a SOAP fault, as shown in Figure 3.

<Fault xmlns="http://www.w3.org/2003/05/soap-envelope">

 <Code>

   <Value>Receiver</Value>

   <Subcode>

     <Value xmlns:a="http://schemas.microsoft.com/

      net/2005/12/windowscommunicationfoundation/

      dispatcher">a:InternalServiceFault</Value>

   </Subcode>

 </Code>

 <Reason>

   <Text xml:lang="en-US">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.</Text>

 </Reason>

</Fault> 

Figure 3: The default SOAP 1.2 fault for uncaught exceptions.

NOTE: Recall that the format of the SOAP fault will follow the specification indicated by the selected binding. For example, basicHttpBinding serializes SOAP 1.1 messages, and all other bindings, including wsHttpBinding, serialize SOAP 1.2 messages.

For debugging purposes, you can tell the service model to include information about the original exception, including the stack trace, in the resulting SOAP fault. This is done by enabling the service debug behavior, IncludeExceptionDetailsInFaults. To enable this behavior in the host configuration file, associate a service behavior with the service and add the <serviceDebug> section shown in Figure 4.

<system.serviceModel>

 <services>

   <service name="ExceptionService.Service"

    behaviorConfiguration="serviceBehavior">

     <endpoint address="http://localhost:8000/Service"

       contract="ExceptionService.IService"

      binding="wsHttpBinding" />

   </service>

 </services>

 <behaviors>

   <serviceBehaviors>

     <behavior name="serviceBehavior" >

       <serviceDebug includeExceptionDetailInFaults ="true"/>

     </behavior>

   </serviceBehaviors>

 </behaviors> 

Figure 4: Enabling the service debug behavior, IncludeExceptionDetailsInFaults.

You can also enable this behavior programmatically by applying the ServiceBehaviorAttribute to the service, as follows:

[ServiceBehaviorAttribute(

 IncludeExceptionDetailsInFaults=true)]

public class Service : IService

Using the configuration file is preferable for this setting, as this allows developers to enable the feature for testing, while administrators can disable the feature on production systems.

NOTE: It is also possible to programmatically initialize the ServiceHost instance with this and other service debug behavior settings, but this removes the configurability of the setting for testing purposes.

Generally speaking, you shouldn t share detailed exceptions from downstream components with clients, as it may include sensitive information about the underlying system (file paths, SQL statements, etc.), thus posing a security threat. In addition, the service model treats uncaught exceptions as a serious issue and faults the server channel taking any sessions along with it. This renders the client proxy useless for subsequent calls where sessions are involved, including transport sessions, application sessions, reliable sessions, and secure sessions because the proxy will be using a session that is no longer valid at the service.

NOTE: Client applications should always be prepared to reconstruct the proxy, and associated channel of communication with a service endpoint, in the event of uncaught exceptions (or other issues, such as timeouts) faulting the server channel. The question you must ask yourself is if the user needs to be notified that the channel must be reestablished. If they might lose application session data, the answer might be yes. If the result is simply a new secure, reliable session for communications, the answer might be no.

To report useful information to clients and avoid faulting the server channel unnecessarily, services should throw fault exceptions (as I ll discuss in the next section). There are a few ways to throw faults: you can throw a FaultException; throw a strongly-typed FaultException<T>; or use the MessageFault type to construct a fault with additional SOAP fault specifics.

 

FaultException

FaultException is useful for throwing a simple fault. For example, you can construct a FaultException providing only a string for the fault reason, as shown here:

throw new FaultException(

 "An invalid operation has occurred."); 

This initializes the SOAP fault, indicating a sender fault with the provided reason. The following examples provide equivalent results:

throw new FaultException(new FaultReason(

 "An invalid operation has occurred."));

throw new FaultException(new FaultReason(

 "An invalid operation has occurred."),

 FaultCode.CreateSenderFaultCode(null)); 

Usually faults are receiver or sender faults, per the SOAP 1.2 specification (in SOAP 1.1 this was referred to as server or client faults). To indicate a receiver fault instead of the default sender fault, you can do the following:

throw new FaultException(new FaultReason("Invalid data

 provided."), FaultCode.CreateReceiverFaultCode(null)); 

Optionally, with SOAP 1.2 you can provide additional sub-code information, as shown in the <Subcode> section of Figure 3. For example, the following specifies a sub code of InvalidOperation:

throw new FaultException(new FaultReason("An invalid

 operation has occurred."),

 FaultCode.CreateSenderFaultCode(new

 FaultCode("InvalidOperation"))); 

Throwing a fault using FaultException allows you to provide error codes and messages to the client. You can also provide a <Detail> section in some of the advanced constructors that take MessageFault as a parameter (discussed shortly). A much simpler way to include additional details for clients is to use FaultException<T>.

 

FaultException<T>

Providing a <Detail> element for a SOAP fault gives clients additional information about what went wrong, beyond a simple fault code and message. This is particularly important when the client is at fault, because there could be elements of the message they sent that could not be processed and they ll need to know which elements failed.

When you throw FaultException<T>, T represents a data contract or serializable type that is to be included in the <Detail> element. The following example illustrates the use of a CLR exception type for additional details:

throw new FaultException<InvalidOperationException>(new

 InvalidOperationException("An invalid operation has

 occured."), "Invalid operation.",

 FaultCode.CreateSenderFaultCode(null)); 

The result is that the SOAP fault includes the serialized InvalidOperationException within the <Detail> element. In the event that your WCF services are behind the firewall and the goal is to merely transfer CLR exceptions across service boundaries, this can be an acceptable approach. If clients are aware of the CLR type, in this case InvalidOperationException, they can catch the fault and access the details as a strongly-typed value:

catch (FaultException<InvalidOperationException> fe)

{

 InvalidOperationException exception = fe.Detail;

 Console.WriteLine("Message: {0}", exception.Message);

}

Although convenient, CLR exceptions are not interoperable types for other platforms, so it is best to define custom data contracts that represent any details to share with clients.

Consider the data contracts for ReceiverFault and SenderFault types shown in Figure 5. While the ReceiverFault reports only a message and detailed description, the SenderFault includes a collection of strings indicating elements of the message that could not be processed. Of course, you can customize this for the needs of your specific application, but supplying a standard data contract for receiver and sender issues is one possible way to handle reporting faults.

 

 [DataContract(Namespace =

   "http://schemas.thatindigogirl.com/samples/2006/06")]

public class ReceiverFault

{

 private string m_message;

 private string m_description;

  [DataMember(Name = "Message", IsRequired = true,

   Order = 0)]

 public string Message

 {

   get { return m_message; }

   set { m_message = value; }

 }

  [DataMember(Name = "Description", IsRequired = false,

   Order = 1)]

 public string Description

 {

   get { return m_description; }

   set { m_description = value; }

 }

}

[DataContract(Namespace =

 "http://schemas.thatindigogirl.com/samples/2006/06")]

public class SenderFault

{

 private string m_message;

 private string m_description;

 private List<string> m_failedBodyElements =

   new List<string>();

  [DataMember(Name = "Message", IsRequired = true,

   Order = 0)]

 public string Message

 {

   get { return m_message; }

   set { m_message = value; }

 }

  [DataMember(Name = "Description", IsRequired = false,

   Order = 1)]

 public string Description

 {

   get { return m_description; }

   set { m_description = value; }

 }

  [DataMember(Name = "FailedBodyElements", IsRequired =

   true, Order = 2)]

 public List<string> FailedBodyElements

 {

   get { return m_failedBodyElements; }

   set { m_failedBodyElements = value; }

 }

}

Figure 5: Data contracts for ReceiverFault and SenderFault.

To throw a fault using a data contract for the detail type, do the following:

ReceiverFault receiverFault = new ReceiverFault("Invalid

 operation", "An invalid operation occurred and the

 operation cannot be completed.");

throw new FaultException<ReceiverFault>(receiverFault,

 receiverFault.Message,

 FaultCode.CreateReceiverFaultCode(null)); 

When clients process faults, they can extract the <Detail> element (if it is present) to find out additional information about the problem. In this case, the client requires knowledge of the ReceiverFault type to access the strongly-typed detail element:

catch (FaultException<ReceiverFault> fe)

{

 ReceiverFault info = fe.Detail;

 Console.WriteLine("Message: {0}, Description: {1}",

   info.Message, info.Description);

}

For clients to process the details element in this way, they must have access to the detail type T as thrown by FaultException<T>. The WSDL document can include information about fault types to be thrown by service operations using the FaultContractAttribute (discussed shortly). This is what enables proxy generation to include relevant fault types.

 

MessageFault

For greater control over the SOAP fault format, you can use the MessageFault type to supply additional elements like <Role> and <Node> (described earlier) although, normally, the defaults will suffice for these elements. Figure 6 shows how to create a FaultException with an InvalidOperationException as the <Detail> element, using a MessageFault.

MessageFault mfault = MessageFault.CreateFault(

FaultCode.CreateSenderFaultCode(null),

new FaultReason("Invalid operation"),

new InvalidOperationException("An invalid operation

 has occurred."), null, " http://www.thatindigogirl.com/

 intermediary", " http://www.thatindigogirl.com:8000/

 intermediary/service");

FaultException fe = FaultException.CreateFault(mfault,

 typeof(InvalidOperationException));

throw fe; 

Figure 6: Use a MessageFault to create a FaultException with an InvalidOperationException as the <Detail> element.

A SOAP actor may be provided if an intermediary such as a content-based router is throwing the fault. In this case, SOAP 1.2 allows you to also provide the node, or address, of the application throwing the fault. NOTE: You will rarely, if ever, set these values.

To throw a fault with these details, create a FaultException by passing the MessageFault instance to CreateFault, along with the <Detail> element type for serialization.

 

FaultContractAttribute

By default, clients are not aware of the types of faults that can be thrown by services. You can declare faults as part of the WSDL contract for a service, so that clients have access to the metadata for known fault types. This is what makes it possible for clients to distinguish the types of faults they catch, and access the details element as a strong type.

To declare fault types as part of the service contract, you can apply the FaultContractAttribute to each operation for which the fault applies, and specify the type for the details element. For example:

[ServiceContract(Name="PhotoUploadContract", Namespace =

 "http://www.thatindigogirl.com/samples/2006/06")]

public interface IPhotoUpload

{

  [OperationContract]

  [FaultContract(typeof(ReceiverFault))]

  [FaultContract(typeof(SenderFault))]

 void UploadPhoto(PhotoLink fileInfo, byte[] fileData);

}

 

This example illustrates stacking two FaultContractAttributes to the one and only operation in the contract. As an alternative, you could apply the FaultContractAttribute to the entire service contract to associate faults with all operations in the contract:

[ServiceContract(Name="PhotoUploadContract", Namespace =

 "http://www.thatindigogirl.com/samples/2006/06")]

[FaultContract(typeof(ReceiverFault))]

[FaultContract(typeof(SenderFault))]

public interface IPhotoUpload

{

  [OperationContract]

 void UploadPhoto(PhotoLink fileInfo, byte[] fileData);

}

When faults are declared this way, the WSDL will include a description for possible fault messages for each operation. When clients generate a proxy from the WSDL, or from metadata, fault types are generated along with any other complex types that can be serialized with messages to the service. This allows clients to work with CLR types representative of each detail type. In addition, the client-side service contract includes FaultContractAttribute declarations so the client channel can reconstitute the FaultException<T> from incoming fault messages. Otherwise, a general FaultException will be supplied without access to a strongly-typed detail section. In fact, what allows clients to catch faults in this way:

catch (FaultException<ReceiverFault> fe)

{

 ReceiverFault info = fe.Detail;

 Console.WriteLine("Message: {0}, Description: {1}",

   info.Message, info.Description);

}

is the fact that the FaultContractAttribute is present in the client-side service contract definition. If you remove this declaration, you can still access the detail element and convert it to its CLR type, with the following workaround:

catch (FaultException fe)

{

 MessageFault mf = fe.CreateMessageFault();

 ReceiverFault ex = mf.GetDetail<ReceiverFault>();

}

So long as you have access to the ReceiverFault data contract, the call to GetDetail<T> will be able to construct the data contract from the <Detail> element returned in the SOAP fault.

 

Exception Handling Guidance

Based on what I ve discussed in this article, consider the following guidance for exception handling for your WCF services:

  • Enable the service debugging behavior to include exception details in faults only for development testing. Never enable this on production, unless for a short period to solve a problem that cannot be duplicated on test machines.
  • Remember that uncaught exceptions can fault the service channel. A good service citizen will catch known exceptions from the business or data access layer and convert them to faults (unless they are fatal exceptions).
  • At the very least, throw a simple FaultException and remember to set the fault code to Sender or Receiver, accordingly. Try to use declared faults to provide more useful information to clients beyond an error string.
  • Avoid exception types and use data contracts for declared faults so services can be interoperable. Always throw FaultException<T>, where T is any declared fault that clients can explicitly catch.

Another important part of the exception handling puzzle is the error handler, which allows you to provide centralized exception handling for your services. I ll discuss this in the next article of this column, and expand on the exception handling patterns you can adopt. Until then, watch your exceptions!

For examples related to exception handling discussed in this article, see sample code for Chapter 8 of my book, Learning WCF (code for the entire book is available from http://www.thatindigogirl.com).

 

Michele Leroux Bustamante is Chief Architect of IDesign Inc., Microsoft Regional Director for San Diego, Microsoft MVP for Connected Systems, and a BEA Technical Director. At IDesign Michele provides training, mentoring, and high-end architecture consulting services focusing on Web services, scalable and secure architecture design for .NET, federated security scenarios, Web services, interoperability, and globalization architecture. She is a member of the International .NET Speakers Association (INETA), a frequent conference presenter, conference chair for SD West, and is frequently published in several major technology journals. Michele is also on the board of directors for IASA (International Association of Software Architects), and a Program Advisor to UCSD Extension. Her latest book is Learning WCF (O Reilly, 2007); see her book blog at http://www.thatindigogirl.com. Reach her at mailto:[email protected] or visit http://www.idesign.net and her main blog at http://www.dasblonde.net.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish