Friday, March 27. 2009
Wcf Exception Shielding - Unexpected FaultContracts
So you've got Wcf Exception Shielding in place. All is well and the Enterprise Library Exception Handling Application Block takes care of those nasty exception details that you don't want your site visitors to know about... But have you ever had the urge to do more than just property mapping between exceptions and FaultContract? I know I have 
There is a way to do this and it is actually quite simple to realize. You just have to create your own custom IExceptionHandler. But let's start with a bit of configuration. By default you would have an Exception Policy named "WCF Exception Shielding". Like so:
<exceptionHandling>
<exceptionPolicies>
<add name="WCF Exception Shielding" />
</exceptionPolicies>
</exceptionHandling>
This policy handles specific Exception types and for each of those it will pass the exception through a chain of IExceptionHandlers. The last of these would normally be Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF.FaultContractExceptionHandler. For instance:
<exceptionTypes>
<add type="System.DivideByZeroException, mscorlib"
postHandlingAction="ThrowNewException" name="DivideByZeroException">
<exceptionHandlers>
<add exceptionMessage="Dividing by zero is not allowed."
faultContractType="DivideByZeroFault, MyDll"
type="FaultContractExceptionHandler, ExceptionHandling.WCF"
name="Fault Contract Exception Handler">
<mappings>
<add source="Guid" name="Id" />
<add source="Message" name="Message" />
</mappings>
</add>
</exceptionHandlers>
</add>
</exceptionTypes>
Now what if your FaultContract would need additional data, that you can't just map from the Exception properties? For example:
- you might need the Message property of the InnerException;
- you need data that was logged to a database when the original exception got thrown.
To make this possible, just create your own IExceptionHandler that will be used instead of the provided FaultContractExceptionHandler. There are two things to keep in mind when you do this. First of all you should always set the postHandlingAction to ThrowNewException. Second, you should let your IExceptionHandler return a specific Exception type: FaultContractWrapperException.

There is a way to do this and it is actually quite simple to realize. You just have to create your own custom IExceptionHandler. But let's start with a bit of configuration. By default you would have an Exception Policy named "WCF Exception Shielding". Like so:
<exceptionHandling>
<exceptionPolicies>
<add name="WCF Exception Shielding" />
</exceptionPolicies>
</exceptionHandling>
This policy handles specific Exception types and for each of those it will pass the exception through a chain of IExceptionHandlers. The last of these would normally be Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF.FaultContractExceptionHandler. For instance:
<exceptionTypes>
<add type="System.DivideByZeroException, mscorlib"
postHandlingAction="ThrowNewException" name="DivideByZeroException">
<exceptionHandlers>
<add exceptionMessage="Dividing by zero is not allowed."
faultContractType="DivideByZeroFault, MyDll"
type="FaultContractExceptionHandler, ExceptionHandling.WCF"
name="Fault Contract Exception Handler">
<mappings>
<add source="Guid" name="Id" />
<add source="Message" name="Message" />
</mappings>
</add>
</exceptionHandlers>
</add>
</exceptionTypes>
Now what if your FaultContract would need additional data, that you can't just map from the Exception properties? For example:
- you might need the Message property of the InnerException;
- you need data that was logged to a database when the original exception got thrown.
To make this possible, just create your own IExceptionHandler that will be used instead of the provided FaultContractExceptionHandler. There are two things to keep in mind when you do this. First of all you should always set the postHandlingAction to ThrowNewException. Second, you should let your IExceptionHandler return a specific Exception type: FaultContractWrapperException.
Concrete example:
<add type="SumOverflowException, MyDll"
postHandlingAction="ThrowNewException" name="SumOverflowException">
<exceptionHandlers>
<add type="SumOverflowExceptionHandler, MyDll"
name="Sum Overflow Exception Handler" />
</exceptionHandlers>
</add>
The used IExceptionHandler maps a SumOverflowException to a SumOverflowFault. The exception holds the arguments used for the summation in a List<int>. The FaultContract exposes the same arguments as a simple int[].
public class SumOverflowException : OverflowException
{
public List<int> Arguments = new List<int>();
public SumOverflowException() : base() { }
public SumOverflowException(string message) : base(message) { }
public SumOverflowException(string message, Exception inner) : base(message, inner) { }
protected SumOverflowException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
Arguments.AddRange((IEnumerable<int>)info.GetValue("Arguments", Arguments.GetType()));
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Arguments", Arguments, Arguments.GetType());
base.GetObjectData(info, context);
}
}
[DataContract]
public class SumOverflowFault
{
[DataMember]
public int[] Arguments;
[DataMember]
public Guid Id;
[DataMember]
public string Message;
}
[ConfigurationElementType(typeof(CustomHandlerData))]
public class SumOverflowExceptionHandler: IExceptionHandler
{
#region Constructors
public SumOverflowExceptionHandler(NameValueCollection ignore)
{
}
#endregion
#region IExceptionHandler Members
public Exception HandleException(Exception exception, Guid handlingInstanceId)
{
SumOverflowException oex = exception as SumOverflowException;
if (null != oex){
SumOverflowFault fault = new SumOverflowFault();
string[] arguments = oex.Arguments.Select(a => a.ToString()).ToArray();
fault.Arguments = oex.Arguments.ToArray();
fault.Id = handlingInstanceId;
fault.Message = string.Format(CultureInfo.InvariantCulture,
"Overflow while adding up: {0}.",
string.Join(", ", arguments));
return new FaultContractWrapperException(fault, handlingInstanceId, "Shielded SumOverflowException");
}
return exception;
}
#endregion
}
Now for the good stuff. WCF passes all FaultException<TDetail> that have not been specified on the operation as "flattened" FaultExceptions to the client. Far from ideal I would say, because you cannot easily see what went wrong. Enter the UnexpectedFaultContractExceptionHandler. This handler checks if it's handling a generic FaultException, determines for which FaultContract and checks if the operation expects it. If the FaultContract is not specified on the operation, the handler issues a FaultException<UnexpectedFault>.
[DataContract]
public class UnexpectedFault
{
[DataMember]
public Guid Id;
[DataMember]
public string Message;
[DataMember]
public string DetailTypeName;
[DataMember]
public string OriginalMessage;
}
[ConfigurationElementType(typeof(CustomHandlerData))]
public class UnexpectedFaultExceptionHandler : IExceptionHandler
{
/// <summary>
/// Reads the config settings.
/// </summary>
/// <param name="settings">
/// No settings required.
/// </param>
public UnexpectedFaultExceptionHandler(NameValueCollection settings)
{
}
/// <summary>
/// Translates an unexpected <see cref="FaultException"/> to a FaultException<UnexpectedFault>.
/// The FaultException is deemed unexpected if it has not been added as a <see cref="FaultContract"/>
/// to the operation.
/// </summary>
/// <param name="exception">The exception to be processed.</param>
/// <param name="handlingInstanceId">The GUID for the handling instance chain.</param>
/// <returns>
/// The translated exception if the original was not mentioned as FaultContract on the operation.
/// Otherwise the original exception.
/// </returns>
public Exception HandleException(Exception exception, Guid handlingInstanceId)
{
// ignore if it already is a FaultException<UnexpectedFault>
if (exception is FaultException<UnexpectedFault> || !(exception is FaultException))
{
return exception;
}
// We have a FaultException, check if it is the generic kind.
Type exceptionType = exception.GetType();
if (!exceptionType.IsGenericType)
{
return exception;
}
// Check if the FaultContract was specified on the operation.
Type detailType = GetDetailType(exception);
string operationName;
if (IsFaultContractSpecified(detailType, out operationName))
{
return exception;
}
// Create a new FaultContract, stating which FaultContract was not specified on the operation
UnexpectedFault fault = new UnexpectedFault();
fault.DetailTypeName = detailType.Name;
fault.Id = handlingInstanceId;
fault.Message = string.Format(CultureInfo.InvariantCulture,
"Operation [{0}] did not specify [FaultContract(typeof({1}))].",
operationName,
fault.DetailTypeName);
fault.OriginalMessage = exception.Message;
return new FaultContractWrapperException(fault, handlingInstanceId, "Unexpected FaultContract.");
}
private bool IsFaultContractSpecified(Type detailType, out string operationName)
{
// This should work, as we are calling the exception policy from within a service behavior...
OperationContext currentContext = OperationContext.Current;
Debug.Assert(currentContext != null, "Damn... no OperationContext here.");
Uri endpointAddress = currentContext.EndpointDispatcher.EndpointAddress.Uri;
ServiceEndpoint endpoint = currentContext.Host.Description.Endpoints.Find(endpointAddress);
Debug.Assert(endpoint != null, "Where did the endpoint go?");
DispatchOperation dispatchOperation = currentContext.EndpointDispatcher.DispatchRuntime.Operations
.Where(op => op.Action == currentContext.IncomingMessageHeaders.Action)
.First();
operationName = dispatchOperation.Name;
OperationDescription operationDescription = endpoint.Contract.Operations.Find(operationName);
foreach (FaultDescription faultDescription in operationDescription.Faults)
{
if (faultDescription.DetailType == detailType)
{
return true;
}
}
return false;
}
private Type GetDetailType(Exception ex)
{
Type faultExceptionType = ex.GetType();
Type[] genericArguments = faultExceptionType.GetGenericArguments();
return genericArguments[0];
}
private FaultContractAttribute[] GetFaults(MethodInfo methodInfo)
{
object[] attributes = methodInfo.GetCustomAttributes(typeof(FaultContractAttribute), false);
return attributes as FaultContractAttribute[];
}
private bool FindFaultDetailType(List<FaultContractAttribute> faultAttributes, Type detailType)
{
Predicate<FaultContractAttribute> sameFault = (fault) =>
{
Type faultDetailType = fault.DetailType;
return faultDetailType == detailType;
};
return faultAttributes.Exists(sameFault);
}
}
Of course you have to put [FaultContract(typeof(UnexpectedFault))] on each operation. This is a bit tedious, but at the moment there is no direct way to add interface-wide FaultContracts through declarative syntax. See also Microsoft Connect.
I've found a nice solution for this here and here.
Although the MSDN documentation here states you should not modify the ContractDescription as it results in undefined execution behavior?!
Thanks to Sasha Goldshtein, for writing a very nice article on Wcf Error Handling. It helped me a lot putting the UnexpectedFaultExceptionHandler together.
<add type="SumOverflowException, MyDll"
postHandlingAction="ThrowNewException" name="SumOverflowException">
<exceptionHandlers>
<add type="SumOverflowExceptionHandler, MyDll"
name="Sum Overflow Exception Handler" />
</exceptionHandlers>
</add>
The used IExceptionHandler maps a SumOverflowException to a SumOverflowFault. The exception holds the arguments used for the summation in a List<int>. The FaultContract exposes the same arguments as a simple int[].
public class SumOverflowException : OverflowException
{
public List<int> Arguments = new List<int>();
public SumOverflowException() : base() { }
public SumOverflowException(string message) : base(message) { }
public SumOverflowException(string message, Exception inner) : base(message, inner) { }
protected SumOverflowException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
Arguments.AddRange((IEnumerable<int>)info.GetValue("Arguments", Arguments.GetType()));
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Arguments", Arguments, Arguments.GetType());
base.GetObjectData(info, context);
}
}
[DataContract]
public class SumOverflowFault
{
[DataMember]
public int[] Arguments;
[DataMember]
public Guid Id;
[DataMember]
public string Message;
}
[ConfigurationElementType(typeof(CustomHandlerData))]
public class SumOverflowExceptionHandler: IExceptionHandler
{
#region Constructors
public SumOverflowExceptionHandler(NameValueCollection ignore)
{
}
#endregion
#region IExceptionHandler Members
public Exception HandleException(Exception exception, Guid handlingInstanceId)
{
SumOverflowException oex = exception as SumOverflowException;
if (null != oex){
SumOverflowFault fault = new SumOverflowFault();
string[] arguments = oex.Arguments.Select(a => a.ToString()).ToArray();
fault.Arguments = oex.Arguments.ToArray();
fault.Id = handlingInstanceId;
fault.Message = string.Format(CultureInfo.InvariantCulture,
"Overflow while adding up: {0}.",
string.Join(", ", arguments));
return new FaultContractWrapperException(fault, handlingInstanceId, "Shielded SumOverflowException");
}
return exception;
}
#endregion
}
Now for the good stuff. WCF passes all FaultException<TDetail> that have not been specified on the operation as "flattened" FaultExceptions to the client. Far from ideal I would say, because you cannot easily see what went wrong. Enter the UnexpectedFaultContractExceptionHandler. This handler checks if it's handling a generic FaultException, determines for which FaultContract and checks if the operation expects it. If the FaultContract is not specified on the operation, the handler issues a FaultException<UnexpectedFault>.
[DataContract]
public class UnexpectedFault
{
[DataMember]
public Guid Id;
[DataMember]
public string Message;
[DataMember]
public string DetailTypeName;
[DataMember]
public string OriginalMessage;
}
[ConfigurationElementType(typeof(CustomHandlerData))]
public class UnexpectedFaultExceptionHandler : IExceptionHandler
{
/// <summary>
/// Reads the config settings.
/// </summary>
/// <param name="settings">
/// No settings required.
/// </param>
public UnexpectedFaultExceptionHandler(NameValueCollection settings)
{
}
/// <summary>
/// Translates an unexpected <see cref="FaultException"/> to a FaultException<UnexpectedFault>.
/// The FaultException is deemed unexpected if it has not been added as a <see cref="FaultContract"/>
/// to the operation.
/// </summary>
/// <param name="exception">The exception to be processed.</param>
/// <param name="handlingInstanceId">The GUID for the handling instance chain.</param>
/// <returns>
/// The translated exception if the original was not mentioned as FaultContract on the operation.
/// Otherwise the original exception.
/// </returns>
public Exception HandleException(Exception exception, Guid handlingInstanceId)
{
// ignore if it already is a FaultException<UnexpectedFault>
if (exception is FaultException<UnexpectedFault> || !(exception is FaultException))
{
return exception;
}
// We have a FaultException, check if it is the generic kind.
Type exceptionType = exception.GetType();
if (!exceptionType.IsGenericType)
{
return exception;
}
// Check if the FaultContract was specified on the operation.
Type detailType = GetDetailType(exception);
string operationName;
if (IsFaultContractSpecified(detailType, out operationName))
{
return exception;
}
// Create a new FaultContract, stating which FaultContract was not specified on the operation
UnexpectedFault fault = new UnexpectedFault();
fault.DetailTypeName = detailType.Name;
fault.Id = handlingInstanceId;
fault.Message = string.Format(CultureInfo.InvariantCulture,
"Operation [{0}] did not specify [FaultContract(typeof({1}))].",
operationName,
fault.DetailTypeName);
fault.OriginalMessage = exception.Message;
return new FaultContractWrapperException(fault, handlingInstanceId, "Unexpected FaultContract.");
}
private bool IsFaultContractSpecified(Type detailType, out string operationName)
{
// This should work, as we are calling the exception policy from within a service behavior...
OperationContext currentContext = OperationContext.Current;
Debug.Assert(currentContext != null, "Damn... no OperationContext here.");
Uri endpointAddress = currentContext.EndpointDispatcher.EndpointAddress.Uri;
ServiceEndpoint endpoint = currentContext.Host.Description.Endpoints.Find(endpointAddress);
Debug.Assert(endpoint != null, "Where did the endpoint go?");
DispatchOperation dispatchOperation = currentContext.EndpointDispatcher.DispatchRuntime.Operations
.Where(op => op.Action == currentContext.IncomingMessageHeaders.Action)
.First();
operationName = dispatchOperation.Name;
OperationDescription operationDescription = endpoint.Contract.Operations.Find(operationName);
foreach (FaultDescription faultDescription in operationDescription.Faults)
{
if (faultDescription.DetailType == detailType)
{
return true;
}
}
return false;
}
private Type GetDetailType(Exception ex)
{
Type faultExceptionType = ex.GetType();
Type[] genericArguments = faultExceptionType.GetGenericArguments();
return genericArguments[0];
}
private FaultContractAttribute[] GetFaults(MethodInfo methodInfo)
{
object[] attributes = methodInfo.GetCustomAttributes(typeof(FaultContractAttribute), false);
return attributes as FaultContractAttribute[];
}
private bool FindFaultDetailType(List<FaultContractAttribute> faultAttributes, Type detailType)
{
Predicate<FaultContractAttribute> sameFault = (fault) =>
{
Type faultDetailType = fault.DetailType;
return faultDetailType == detailType;
};
return faultAttributes.Exists(sameFault);
}
}
Of course you have to put [FaultContract(typeof(UnexpectedFault))] on each operation. This is a bit tedious, but at the moment there is no direct way to add interface-wide FaultContracts through declarative syntax. See also Microsoft Connect.
I've found a nice solution for this here and here.
Although the MSDN documentation here states you should not modify the ContractDescription as it results in undefined execution behavior?!
All of the IContractBehavior methods pass System.ServiceModel.Description.ContractDescription and System.ServiceModel.Description.ServiceEndpoint as parameters. These parameters are for examination; if you modify the objects the execution behavior is undefined.
Thanks to Sasha Goldshtein, for writing a very nice article on Wcf Error Handling. It helped me a lot putting the UnexpectedFaultExceptionHandler together.
Comments
Display comments as
(Linear | Threaded)
Add Comment