原文地址:http://blogs.msdn.com/ashishme/archive/2006/10/05/Exceptional-Handling-in-Windows-Communication-Framework-and-Best-Practices.aspx

Exception handling is critical to all applications for troubleshooting the unexpected problems in the application.  Windows Communication Framework (WCF) provides several options for handling exceptions in WCF services. This Knowledge Brief (KB) discusses these approaches and describes the advantages and disadvantages of each.

The following options are provided to handle exceptions in WCF:

  • Using returnUnknownExceptionsAsFaults
  • Using FaultException
  • Using IErrorHandler

 

 

Exceptions inside a WCF Service

Before describing the details of exception handling in WCF, let’s explore what happens if we do not handle an exception inside the service. Consider a service with the CreateOrder method as shown in Figure 1.

      public void CreateOrder(Order order)

        {

            if (order.isValid())

            {

                //Create the order....

            }

            else

            {throw new ApplicationException("Order Invalid");

            }

        }

Figure 1 : Sample Exception

 

In the above example, if the order is invalid, the ApplicationException is discarded and is no longer handled by the service. When this kind of exception takes place in a service, the communication channel between the service and its client is broken and the client receives the following generic exception message:

CommunicationException - the server did not provide a meaningful reply; this might have been caused due to a contract mismatch, a premature session shutdown or an internal server error.

The client is then unable to communicate with the server using the same channel. To avoid this, you need to handle the exception properly and raise the exception as a Soap Fault[1] so that the communication channel between the client and the service does not break. We can use any of the exception handling methods provided by WCF to handle exceptions like this. The following sections describe each of the exception handling options in detail. 

Using returnUnknownExceptionsAsFaults

WCF provides an option to raise the exception automatically as a Soap Fault. To enable this option, set returnUnknownExceptionsAsFaults as ‘True’ in the service behavior setting in the application configuration file under system.servicemodel section as shown in Figure 2.

    <behaviors>

      <behavior name="OrderingServiceBehavior"

                 returnUnknownExceptionsAsFaults="True">

      </behavior>

    </behaviors>

Figure 2 : Behaviour Configuration

Note: It’s also possible to set this value programmatically using the ServiceBehaviour attribute, but the recommended approach is to set it using config file.

Once returnUnknownExceptionsAsFaults is set to ‘True’, all the exceptions are wrapped as a Soap Fault before being sent to the clients. However, using this option exposes all the exceptions and their details to the client and the details might contain sensitive information like database schema, connection strings, etc. We recommend that this option is only used when debugging in order to identify the exceptions raised by the services.

When you use the returnUnknownExceptionsAsFaults option, the client will receive an UnknownFaultException instead of a CommuncationException. The resulting Soap Fault message is similar to that shown in Figure 3:

<s:Fault>

<s:Code>

  <s:Value>s:Receiver</s:Value>

  </s:Code>

 <s:Reason>

  <s:Text xml:lang="en-US">Order Invalid</s:Text>

  </s:Reason>

 <s:Detail>

  <z:anyType xmlns:d32="http://www.w3.org/2001/XMLSchema" i:type="d32:string" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">System.ApplicationException: Order Invalid at MySamples.ErrorSampleService.CreateOrder(Order order) in D:\SoapBox\WindowsCommunicationFoundation\TechnologySamples\Extensibility\ErrorHandling\CS\service\service.cs:line 40 ...(Stack Trace) </z:anyType>

  </s:Detail>

  </s:Fault>

Figure 3 : Soap Fault

Note:Tthe Soap Fault format can differ based on the Service Bindings configuration,. In the case of BasicHttpBinding, it uses the Soap 1.1 format and for WSHttpBinding, it uses Soap 1.2 formats.

As explained earlier, the returnUnknownExceptionsAsFaults option should only be used during debugging. It must not be used in a production environment as it might expose sensitive information, as an exception, to the client.

Using FaultException

FaultException is a new type of exception introduced in WCF to create a Soap Fault. Using the Fault Exception, you can wrap .NET exception or any custom object into a Soap Fault. A FaultException example is shown in Figure 4.

       public void CreateOrderWithFaultException(Order order)

        {

            try

            {

                if (order.isValid())

                {

                    //Create the order....

                }

                else

                {

                    throw new ApplicationException("Order Invalid");

                }

            }

            catch (ApplicationException ex)

            {

              throw new FaultException<ApplicationException>

            (ex, new FaultReason(ex.Message), new FaultCode("Sender"));

            }

        }

Figure 4 : FaultException Sample

Note: Always provide the appropriate details for a FaultReason and FaultCode when you are raise a FaultException. FaultCode is used to identify whether the fault is due to the client or server information and the Fault reason field can be used to send additional information about the exception.

The bold text in the above example shows that the ApplicationException is wrapped inside a FaultException. Therefore, the user receives a Soap Fault in the format shown in Figure 5.

<s:Fault>

<s:Code>

  <s:Value>s:Sender</s:Value>

  </s:Code>

<s:Reason>

  <s:Text xml:lang="en-US">Order Invalid</s:Text>

  </s:Reason>

<s:Detail>

<z:anyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d46="http://schemas.datacontract.org/2004/07/System" i:type="d46:ApplicationException" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">

  <ClassName xmlns:d32="http://www.w3.org/2001/XMLSchema" i:type="d32:string" xmlns="">System.ApplicationException</ClassName>

  <Message xmlns:d32="http://www.w3.org/2001/XMLSchema" i:type="d32:string" xmlns="">Order Invalid</Message>

  <Data i:nil="true" xmlns="" />

  <InnerException i:nil="true" xmlns="" />

  <HelpURL i:nil="true" xmlns="" />

  <StackTraceString xmlns:d32="http://www.w3.org/2001/XMLSchema" i:type="d32:string" xmlns="">at MySamples.ErrorSampleService.CreateOrderWithFaultException(Order order) in D:\SoapBox\WindowsCommunicationFoundation\TechnologySamples\Extensibility\ErrorHandling\CS\service\service.cs:line 55</StackTraceString>

  <RemoteStackTraceString i:nil="true" xmlns="" />

  <RemoteStackIndex xmlns:d32="http://www.w3.org/2001/XMLSchema" i:type="d32:int" xmlns="">0</RemoteStackIndex>

  <ExceptionMethod xmlns:d32="http://www.w3.org/2001/XMLSchema" i:type="d32:string" xmlns="">8 CreateOrderWithFaultException service, Version=1.0.2274.25242, Culture=neutral, PublicKeyToken=null MySamples.ErrorSampleService Void CreateOrderWithFaultException(MySamples.Order)</ExceptionMethod>

  <HResult xmlns:d32="http://www.w3.org/2001/XMLSchema" i:type="d32:int" xmlns="">-2146232832</HResult>

  <Source xmlns:d32="http://www.w3.org/2001/XMLSchema" i:type="d32:string" xmlns="">service</Source>

  </z:anyType>

  </s:Detail>

  </s:Fault>

Figure 5 : Soap Fault

If you look at the Soap Fault in the above figure, you can see that the whole ApplicationException object is wrapped after serialization[2] inside the Detail element. Other than wrapping the ApplicationException inside the Soap Fault, you also need to specify the fault contract for the service. If not, the client won’t know what type of exception it can receive. If you specify fault contract, client knows that it will receive ApplicationException.

If the FaultContract contract is not specified, the clients receive all the exceptions as unknowfaultexceptions even if the exception is raised as a FaultException for an ApplicationException. FaultContract is an attribute in WCF used to specify the type of exception that can be raised by that service. The FaultContract can be specified along with the operation contract as shown in Figure 6:

        [OperationContract()]

        [FaultContract(typeof(ApplicationException))]

        void CreateOrderWithFaultException(Order order);

Figure 6 : Fault Contract

Since the FaultContract is specified in the service, the service definition file (wsdl) will contain this information along with the schema of the ApplicationException. The Client can access the ApplicationException from the details of the Soap Fault as shown in Figure 7.

using (ErrorSampleServiceProxy proxy = new ErrorSampleServiceProxy())

{

    try

    {

        Console.WriteLine("Calling Create Order");

        Order order = new Order();       

        proxy.CreateOrder(order);

        Console.WriteLine("CreateOrder Executed Successfully");

    }

    catch (FaultException<ApplicationException>e)

    {

        Console.WriteLine("FaultException<>: " + e.Detail.GetType().Name + " - " + e.Detail.Message);

    }

    catch (FaultException e)

    {

        Console.WriteLine("FaultException: " + e.GetType().Name + " - " + e.Message);

}

    catch (Exception e)

    {

        Console.WriteLine("EXCEPTION: " + e.GetType().Name + " - " + e.Message);

    }

}

Figure 7 : FaultException Client Sample

The Detail property in the FaultException has ApplicationException. Similar to ApplicationException, you can send any custom data object inside a Soap Fault element.

It is a good practice to send a custom data object with required parameters to the client instead of sending the Exception object as it might have unnecessary details, which might not be required for the client. For example, instead of raising the FaultException of the ApplicationException, you can raise the FaultException of a custom object as given in Figure 8.

        public void CreateOrderWithFaultMsg(Order order)

        {

            try

            {

                if (order.isValid())

                {

                    //Create the order....

                }

                else

                {

                    throw new ApplicationException("Order Invalid");

                }

            }

            catch (Exception ex)

            {

               CustomExpMsg customMsg = new CustomExpMsg(ex.Message);                     

                throw new FaultException<CustomExpMsg>(customMsg, new           

             FaultReason(customMsg.ErrorMsg), new FaultCode("Sender"));

            }

        }

Figure 8 : FaultException Sample

You can specify the fault contract for this custom object in the manner similar to the ApplicationException as shown in Figure 9.

        [OperationContract()]

        [FaultContract(typeof(CustomExpMsg))]

        void CreateOrderWithFaultMsg(Order order);

Figure 9 : FaultContract II

You need to define a CustomExpMsg object as shown in Figure 10.

[DataContract]

    public class CustomExpMsg

    {

        public CustomExpMsg()

        {

            this.ErrorMsg = "Service encountered an error;

        }

        public CustomExpMsg(string message)

        { this.ErrorMsg = message;

        }

        private int errorNumber;

        [DataMember(Order = 0)]

        public int ErrorNumber

        {

            get { return errorNumber; }

            set { errorNumber = value; }

        }

        private string errrorMsg;

        [DataMember(Order = 1)]

        public string ErrorMsg

        {

            get { return errrorMsg; }

            set { errrorMsg = value; }

        }

        private string description;

        [DataMember(Order = 2)]

        public string Description

        {

            get { return description; }

            set { description = value; }

        }

 

}

Figure 10 : Custom Object Definition

This object needs to be marked with DataContract attribute, as it needs to be serialized and sent with the Soap Fault. The Soap Fault message looks like the sample shown in Figure 11.

<s:Fault>

<s:Code>

  <s:Value>s:Sender</s:Value>

  </s:Code>

<s:Reason>

  <s:Text xml:lang="en-US">Order Invalid</s:Text>

  </s:Reason>

 <s:Detail>

 <z:anyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d49="http://schemas.datacontract.org/2004/07/MySamples" i:type="d49:CustomExpMsg" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">

  <d49:ErrorNumber>0</d49:ErrorNumber>

  <d49:ErrorMsg>Order Invalid</d49:ErrorMsg>

  <d49:Description i:nil="true" />

  </z:anyType>

  </s:Detail>

  </s:Fault>

  </s:Body>

  </s:Envelope>

Figure 11 : Soap Fault

 

The bold text in the example above shows CustomExpMsg object serialized inside the detail element of Soap Fault.

FaultException approach is the best approach for handling exception in WCF. However, in this approach, you have two options, one is to raise the .Net exception as is and the other is to raise it with custom object. You should always opt for the custom object option, as it would not expose all the details of .Net exception to the client, instead it sends only the required information to the client as a custom object. Another advantage of this option is that it will work well in other platform clients, as custom object type is described in WSDL.

In the case of .Net exception type, it is not described in WSDL and hence other platform clients would not understand it. You should use .Net exception option only when you know that both the client and service are using WCF and the client needs to get the exact .Net exception that occurred in the WCF service.

Using IErrorHandler

The last approach for exception handling in the WCF is the IErrorHandler approach. With this approach, you can intercept all the exceptions raised inside a service using IErrorHandler. If you implement IErrorHandler interface methods in your service, you can intercept all the exceptions and you can “log and suppress” the exceptions or you can “log and throw” them as FaultException.

The structure of IErrorHandler is shown in Figure 12.

 

   public interface IErrorHandler

    {

        bool HandleError(Exception error, MessageFault fault);

        void ProvideFault(Exception error, ref MessageFault fault, ref string faultAction);

}

Figure 12 : IErrorHandler Sample

If you want to suppress the fault message, just implement the HandlerError method and return false. If you want to raise FaultException instead of suppressing, then implement the ProvideFault method to provide the MessageFault value.

IErrorHandler approach should be used with care, since it handles all the exceptions inside the service in a generic way. It is always better to handle the exception where it occurred instead of going with the generic exception handler like IErrorHandler.

Integration with Enterprise Library Exceptional Handling Application Block

Enterprise Library Exceptional Handling Application Block [EHAB] can be used along with the WCF exception handling methods. You can use EHAB to handle exceptions and to log exceptions at all the layers of the service. However, after capturing the exception in service, raise the exception as FaultException, so that WCF client will receive the Soap Fault properly. The EHAB usage at the service layer in WCF service is shown in Figure 13.

try

         {

             //Execute any code

         }

         catch (Exception ex)

         {

             bool result = ExceptionPolicy.HandleException(ex, "Service");

             if (result)

             {

                 CustomExpMsg customMsg = new CustomExpMsg(ex.Message);

                 throw new FaultException<CustomExpMsg>(customMsg, new FaultReason(customMsg.ErrorMsg), new FaultCode("Server"));

             }

         }

Figure 13 : EHAB Sample

In the above figure, ExceptionPolicy[3].HandleException method handles the exception as specified in .config file of the application under exceptionHandling section. It returns a Boolean based on the EHAB policy settings, which specifies whether to raise the exception after capturing the exception. If the return value is true, the exception is raised again. However, the exception is raised as FaultException. Therefore, the client will receive proper information about Exception by using the FaultException as explained previously.

In other layers like data access and business layer, you can follow the best practices of Exception Handling Application Block. For more details about EHAB, check out the article in the following link.

http://msdn.microsoft.com/library/en-us/dnpag2/html/ehab.asp?frame=true

 

Summary

Since there are many options to handle exceptions in the WCF, you need to choose the best option based on your requirement. You can use returnUnknownExceptionsAsFaults option only for debugging and not in a production environment. In the case of interoperating applications on different platforms, you should use FaultException approach with custom data object. In the case of WCF applications, you can use FaultException approach with .Net exception type, as both ends of applications can understand .Net exception types. IErrorHandler approach can be used only for the exception that cannot be handled using faultException approach.

 



[1] Soap Fault is a SOAP standard used to pass exception-handling information across web services.

[2] Serialization – The process of storing the state of an object instance to a storage medium. For more details on serialization, see this article.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnexxml/html/xml01202003.asp

 

[3] ExceptionPolicy is the object used by EHAB to handle the exceptions.

Posted on 2006-10-06 15:30  Adrian H.  阅读(1836)  评论(0编辑  收藏  举报