|
Exception
Handling in Windows Communication Framework and Best
Practices
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 article 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 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:The 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 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"));
}
}
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
.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.
|