The WCF design guidelines recommends using the exception shielding pattern to sanitize unsafe exceptions and expose only a controlled set of exceptions using fault contracts. Thus, for every operation that you implement in your service adapter (facade), your method must have a try-catch block, code to create a fault contract object containing relevant exception information, and finally code to create and throw a System.ServiceModel.FaultException. This code will look the same in all your operation methods, and you will soon conclude that the code needs to be centralized into a static utility method to achieve better design and maintainability. In addition, a single static method that creates and provides a ready-to-throw fault exception object is also a good candidate for adding e.g. tracing and logging to your service facade.
I have used a combination of custom exceptions and generics to design a flexible exception handling mechanism that implements the exception shielding mechanism of our WCF services. The design involves a a FaultException<T> generics method that constraints T to be of type
FaultContracts.DefaultFaultContract (where T : <base class>) that allows us to create a fault exception containing any fault contract type that we add to our service. Our fault contracts do of course all derive from DefaultFaultContract.
The design also involves a set of custom exceptions derived from System.ApplicationException. This allows us to throw exceptions from any component used by the service facade and catch them in the service operation methods, and still be able to extract data from the exception for use as information in the fault contract. The exception handling mechanism relies on this exception polymorphism to be able to handle any type of exception the same way, using a single method.
This is how the exception handler code looks like:
public static FaultException<T> NewFaultException<T>(Exception ex)
where T : FaultContracts.DefaultFaultContract, new()
{
StackFrame stackFrame = new StackFrame(1);
string methodName = stackFrame.GetMethod().Name;
T fault = new T();
//fault.SetFaultDetails(ex);
Type type = fault.GetType();
type.InvokeMember("SetFaultDetails", BindingFlags.Public BindingFlags.Instance BindingFlags.InvokeMethod, null, fault, new object[] { ex });
//add tracing as applicable
//add logging as applicable
return new FaultException<T>(fault, new FaultReason(methodName + " failed, check detailed fault information."));
}
As you can see, the code is quite simple as it delegates to the object <T> to actually fill any relevent exception data and other details into the fault contract.
Note the use of reflection to call the SetFaultDetails method of the fault contract object. This is needed as .NET does not correctly resolve the different overloaded versions of a method in a generics type, it will just call the overload that has the compile time type of the parameter. Thus, it will always call the
SetFaultDetails(Exception) method, even if the run-time type is a derived exception type. Using InvokeMember circumvents this problem.
Also note the use of the StackFrame object to get the name of the method that caught the exception (i.e. the method that calls NewFaultException exception handler).
As which info is relevant will vary with the type of fault contract and the type of exception, the DefaultFaultContract class have virtual (overridable) methods for any exception that needs special treatment, in addition to a method for System.Exception that other exceptions defaults to:
[System.Runtime.Serialization.DataContractAttribute(Namespace = "http://kjellsj.blogspot.com/2006/11/DefaultFaultContract", Name = "DefaultFaultContract")]
public class DefaultFaultContract
{
. . .
public virtual void SetFaultDetails(Exception ex)
{
//default type
this.errorId = (int) EApprovalErrorCode.Undefined;
this.errorMessage = ex.Message;
}
public virtual void SetFaultDetails(EApprovalException ex)
{
this.errorId = (int)ex.ErrorCode;
this.errorMessage = ex.Message;
}
}
Note that you should not expose implementation details in your fault contract data. The use of the exception message in the above code is just for illustration purposes. This is definitely not recommended, as the goal of using the exception shielding pattern is to sanitize the exposed information to avoid providing the service consumer with data that can be used e.g. for attacking and exploiting your system.
Finally, this is how all our service operation exception handlers look like now:
try
{
. . .
}
catch (Exception ex)
{
throw MessageUtilities.NewFaultException<FaultContracts.DefaultFaultContract>(ex);
}
The type of fault contract <T> used will of course vary with the operation. Remember that all our fault contracts inherits the DefaultFaultContract class and that the virtual SetFaultDetails method ensures that the different exceptions get the correct shielding and handling.
This exception shielding mechanism is designed to be flexible and simple, and using a one-liner for handling errors in the service operation is as easy as it gets.
[UPDATE] More details about WCF faults in this 11 part article by Nicolas Allen.
5 comments:
WCF service layer transparency with exception shielding: http://weblogs.asp.net/stevencohn/archive/2007/01/29/wcf-service-layer-transparency.aspx
Hi,
What namespace is "FaultContracts.DefaultFaultContract" in ?
I am using the RTM .NET 3.0 implementation and can't find the object. Is it a custom implementation or did it get obsoleted in the RTM version.
Thanks for any help you can provide.
This class is a custom class as shown in the post, and is used as T when creating an instance of the generics FaultException<T> class.
Hi. [Good article!] Can you please give an example client use? I am using something similar to:
catch (FaultException>MyService.DefaultFaultContract< ex)
{
MyService.DefaultFaultContract detail = ex.Detail;
Debug.WriteLine(detail.ErrorMessage);
}
in my code, but it seems I can't use the DefaultFaultContract unless I define it using [DataContract], and set a FaultContractAttribute in the Service Contract for each Operation I want to use it on. An example is given at http://msdn2.microsoft.com/en-us/library/ms576199.aspx Thanks.
I have, as you describe, marked the fault contract with DataContract and used FaultContract on all my methods/operations. This happens automatically as I am using the WSSF wizards.
An example of my exception handlers:
catch (FaultException<DefaultFaultContract> ex)
{
proxy.Close();
if (ex.Detail.ErrorId != Constants.ErrorIdInsertDuplicate) throw;
}
Post a Comment