While the default error-masking policy of WCF is a best practice, there are times when
you are refrained from relying on it. This is typically the case when there is an existing
application (or communication pattern) in place, and the service is required to throw
particular exceptions as it processes inputs, reaches certain states, or encounters errors.
The client is required to respond to these exceptions in a prescribed way. Obviously,
controlling the flow of the application using expectations is hardly a good idea, as it
leads to nonstructured programming and couples the client to the service. And yet, the
underlying requirements remain: the service is required to report specific errors to the
client, and the default masking of the errors by WCF precludes that. Another fundamental
problem pertaining to propagating the error to the client is that exceptions are
technology-specific, and as such should not be shared across the service boundary. For
seamless interoperability, you need a way to map technology-specific exceptions to some
neutral error information. This representation is called a SOAP fault.
SOAP faults are based on an industry standard that is independent of any technology-specific
exceptions, such as CLR, Java, or C++ exceptions. To return a SOAP fault (or just a fault,
for short), the service cannot throw a raw CLR exception. Instead, the service must throw an
instance of the FaultException<T>
class, defined in
Example 6-1.
Example 6-1. The FaultException<T> class
[Serializable] //More attributes public class FaultException : CommunicationException { public FaultException( ); public FaultException(string reason); public FaultException(FaultReason reason); public virtual MessageFault CreateMessageFault( ); //More members } [Serializable] public class FaultException<T> : FaultException { public FaultException(T detail); public FaultException(T detail,string reason); public FaultException(T detail,FaultReason reason); //More members }
FaultException<T>
is a specialization of
FaultException
, so any client that programs against
FaultException
will be able to handle FaultException<T>
as well.
The type parameter T
for FaultException<T>
conveys the error details. The detailing type can be
any type, and doesn't necessarily have to be an Exception
-derived class. The only constraint is that the type must be marked as
serializable or a data contract.
Example 6-2 demonstrates a simple
calculator service that throws a FaultException<DivideByZeroException>
in its implementation of the
Divide( )
operation when asked to divide by
zero.
Example 6-2. Throwing a FaultException<T>
[ServiceContract] interface ICalculator { [OperationContract] double Divide(double number1,double number2); //More methods } class Calculator : ICalculator { public double Divide(double number1,double number2) { if(number2 == 0) {DivideByZeroException
exception = new DivideByZeroException( ); throw new FaultException<DivideByZeroException
>(exception
); } return number1 / number2; } //Rest of the implementation }
Instead of FaultException<DivideByZeroException>
, the service could also have thrown
a non-Exception
-derived class:
throw new FaultException<double
>(number2);
However, I find that using an Exception
-derived
detailing type is more in line with conventional .NET programming practices and results in
more readable code. In addition, it allows for exception promotion, discussed later in this
chapter.
The reason
parameter passed to the constructor of
FaultException<T>
is used as the exception
message. You can pass a string
for the reason:
DivideByZeroException exception = new DivideByZeroException( );
throw new FaultException<DivideByZeroException>(exception,"number2 is 0"
);
or you can pass a FaultReason
, which is useful when
localization is required.
By default, any exception thrown by a service reaches the client as a FaultException
. This is the case even if the service throws a
FaultException<T>
. The reason is that anything
beyond communication errors that the service wishes to share with the client must be part
of the service contract in order for the service to inform WCF that it wishes to pierce
the error mask. To that end, WCF provides fault contracts, which are
a way for the service to list the types of errors it can throw. The idea is that these
types of errors should be the same as the type parameters used with FaultException<T>
, and by listing them in fault
contracts, the service enables its WCF clients to distinguish between contracted faults
and other errors.
The service defines its fault contracts using the FaultContractAttribute
:
[AttributeUsage(AttributeTargets.Method,AllowMultiple =true
,Inherited = false)] public sealed class FaultContractAttribute : Attribute { public FaultContractAttribute(Type detailType
); //More members }
You apply the FaultContract
attribute directly on a
contract operation, specifying the error detailing type, as shown in Example 6-3.
Example 6-3. Defining a fault contract
[ServiceContract]
interface ICalculator
{
[OperationContract]
double Add(double number1,double number2);
[OperationContract]
[FaultContract(typeof(DivideByZeroException))]
double Divide(double number1,double number2);
//More methods
}
The effect of the FaultContract
attribute is
limited to the method it decorates. That is, only that method can throw that fault and
have it propagated to the client.
In addition, if the operation throws an exception that is not in the contract, it will
reach the client as a plain FaultException
. To
propagate the exception, the service must throw exactly the same detailing type listed in
the fault contract. For example, to satisfy this fault contract definition:
[FaultContract(typeof(DivideByZeroException))]
The service must throw a FaultException<DivideByZeroException>
. The service cannot even throw a
subclass of the fault contract's detailing type and have it satisfy the contract:
[ServiceContract] interface IMyContract { [OperationContract] [FaultContract(typeof(Exception
))] void MyMethod( ); } class MyService : IMyContract { public void MyMethod( ) { //Willnot
satisfy contract throw new FaultException<DivideByZeroException
>(new DivideByZeroException( )); } }
The FaultContract
attribute is configured to allow
multiple usages, so you can list multiple fault contracts in a single operation:
[ServiceContract] interface ICalculator { [OperationContract] [FaultContract(typeof(InvalidOperationException
))] [FaultContract(typeof(string
))] double Add(double number1,double number2); [OperationContract] [FaultContract(typeof(DivideByZeroException))] double Divide(double number1,double number2); //More methods }
This enables the service to throw any of the exceptions in the contracts and have them propagate to the client.
You cannot provide a fault contract on a one-way operation, because in theory nothing should be returned from a one-way operation:
//Invalid definition
[ServiceContract]
interface IMyContract
{
[OperationContract(IsOneWay = true
)]
[FaultContract(...)]
void MyMethod( );
}
Trying to do so will result in an InvalidOperationException
at service load time (or when the proxy is
created).
The fault contracts are published along with the rest of the service metadata. When a WCF client imports that metadata, the contract definitions contain the fault contracts as well as the fault detailing type definition, including the relevant data contracts. This last point is important if the detailing type is some custom exception type with various dedicated fields.
The client can expect to catch and handle the imported fault types. For example,
when you write a client against the contract shown in Example 6-3, the client can catch FaultException<DivideByZeroException>
:
CalculatorClient proxy = new CalculatorClient( );
try
{
proxy.Divide(2,0);
proxy.Close( );
}
catch(FaultException<DivideByZeroException>
exception)
{...}
catch(FaultException exception)
{...}
catch(CommunicationException exception)
{...}
catch(TimeoutException exception)
{...}
catch(Exception exception)
{...}
Note that the client can still encounter communication exceptions, or any other exception thrown by the service.
The client can choose to treat all non-communication service-side exceptions
uniformly by simply handling only the FaultException
base exception:
CalculatorClient proxy = new CalculatorClient( );
try
{
proxy.Divide(2,0);
proxy.Close( );
}
catch(FaultException
exception)
{...}
catch(CommunicationException exception)
{...}
A somewhat esoteric case is when the client's developer manually changes the
definition of the imported contract by removing the fault contract on the client side.
In that case, even when the service throws an exception listed in a service-side fault
contract, the exception will manifest itself on the client as a plain FaultException
, not as the contracted fault.
Listing an expected error in a fault contract hardly makes it an exceptional unexpected case. As a result, when the service throws an exception listed in a service-side fault contract, the exception will not fault the communication channel. The client can catch that exception and continue using the proxy, or safely close the proxy. This enables the service class to treat the errors listed in the fault contracts differently from regular exceptions, knowing that they will not fault the channel. This ability is not limited to the service class, though. If any downstream .NET class the service invokes throws such an error, it will not fault the channel to the client. The problem is, how can downstream classes know about the fault contracts of the upstream services that call them? Clearly, having this knowledge present downstream introduces undesirable coupling into the system.
To support the ability of a downstream class to throw an exception without faulting
the channel, WCF treats any FaultException
(or
FaultException
-derived class) as a special case
that does not fault the channel. In fact, WCF does not actually treat FaultException<T>
itself as a special case at all—the
reason a fault listed in the contract does not fault the channel is because it is
derived from FaultException
, not because it is listed
in the contract.
Even without any fault contracts, the service (or any downstream object it uses) can
throw an instance of FaultException
directly:
throw new FaultException("Some Reason");
The Message
property of the exception object on
the client side will be set to the reason
construction parameter of FaultException
. I call this
throwing an unknown fault that will not fault the communication
channel, so the client can keep using the proxy as if the exception was part of a fault
contract. Throwing an unknown fault also allows the client to handle the exception
separately from any other communication error.
Any FaultException<T>
thrown by the
service will reach the client as either a FaultException<T>
or a FaultException
. If no fault contract is in place (or if T
is not in the contract), both a FaultException
and a FaultException<T>
thrown by the service will reach the client as
FaultException
.
A deployed service should be decoupled as much as possible from its clients, declaring
in the service fault contracts only the absolute bare minimum and providing as little
information as possible about any errors that occur. However, during testing and
debugging, it is very useful to include all exceptions in the information sent back to the
client. In fact, for a test client, it is instrumental to know exactly which error was
thrown as a result of a particular input or use case, to see if the test cases break the
service as they should. In such a case, dealing with the all-encompassing yet opaque
FaultException
is simply inadequate. For that
purpose, you should use the ExceptionDetail
class,
defined as:
[DataContract]
public class ExceptionDetail
{
public ExceptionDetail(Exception exception);
[DataMember]
public string HelpLink
{get;private set;}
[DataMember]
public ExceptionDetail InnerException
{get;private set;}
[DataMember]
public string Message
{get;private set;}
[DataMember]
public string StackTrace
{get;private set;}
[DataMember]
public string
Type
{get;private set;}
}
You need to create an instance of ExceptionDetail
and initialize it with the exception you want to propagate to the client. Next, instead of
throwing the intended exception, throw a FaultException<ExceptionDetail>
with the instance of ExceptionDetail
as a construction parameter, and also provide
the original exception's message as the fault reason. This sequence is shown in Example 6-4.
Example 6-4. Including the service exception in the fault message
[ServiceContract] interface IMyContract { [OperationContract] void MethodWithError( ); } class MyService : IMyContract { public void MethodWithError( ) { InvalidOperationException exception = new InvalidOperationException("Some error"); ExceptionDetail detail = new ExceptionDetail(exception); throw new FaultException<ExceptionDetail>(detail,exception.Message); } }
Doing so will enable the client to discover the original exception type and message.
The client-side fault object will have a Detail.Type
property containing the name of the original service exception, and the Message
property will contain the original exception message.
Example 6-5 shows the client code processing the
exception thrown in Example 6-4.
Example 6-5. Processing the included exception
MyContractClient proxy = new MyContractClient( ); try { proxy.MethodWithError( ); } catch(FaultException<ExceptionDetail> exception) { Debug.Assert(exception.Detail.Type == typeof(InvalidOperationException).ToString( )); Debug.Assert(exception.Message == "Some error"); }
Since FaultException<ExceptionDetail>
derives
from FaultException
, throwing it will not fault the
channel. I do not consider this the desired behavior.
The ServiceBehavior
attribute offers IncludeExceptionDetailInFaults
, a Boolean property defined
as:
[AttributeUsage(AttributeTargets.Class)] public sealed class ServiceBehaviorAttribute : Attribute, ... { public bool IncludeExceptionDetailInFaults {get;set;} //More members }
IncludeExceptionDetailInFaults
defaults to
false
. Setting it to true
, as in this snippet:
[ServiceBehavior(IncludeExceptionDetailInFaults = true
)]
class MyService : IMyContract
{...}
has a similar effect as the code in Example 6-4, only automated. All noncontractual faults and exceptions thrown by the service or any of its downstream objects are propagated to the client and included in the returned fault message for the client program to process them, as in Example 6-5:
[ServiceBehavior(IncludeExceptionDetailInFaults = true
)]
class MyService : IMyContract
{
public void MethodWithError( )
{
throw new InvalidOperationException("Some error");
}
}
Any fault thrown by the service (or its downstream objects) that is listed in the fault contracts is unaffected and is propagated as-is to the client.
Another important difference between using the declarative support for including the
fault details compared with manually throwing FaultException<ExceptionDetail>
is that it will correctly fault the
channel, preventing the client from reusing the proxy (if a transport session was
present).
While including all exceptions is beneficial for debugging, great care should be
taken to avoid shipping and deploying the service with IncludeExceptionDetailInFaults
set to true
. To avoid this potential pitfall automatically you can use conditional
compilation, as shown in Example 6-6.
Example 6-6. Setting IncludeExceptionDetailInFaults to true in debug only
public static class DebugHelper
{
public const bool IncludeExceptionDetailInFaults =
#if DEBUG
true;
#else
false;
#endif
}
[ServiceBehavior(IncludeExceptionDetailInFaults =
DebugHelper.IncludeExceptionDetailInFaults
)]
class MyService : IMyContract
{...}
Obviously, including all exceptions in the fault message contributes greatly in
debugging, but it's also useful when you're trying to analyze a problem in an already
deployed service. Fortunately, you can set IncludeExceptionDetailInFaults
to true
both programmatically and administratively in the host config file. To set this behavior
programmatically, before opening the host you need to find the service behavior in the
service description and set the IncludeExceptionDetailInFaults
property:
ServiceHost host = new ServiceHost(typeof(MyService));
ServiceBehaviorAttribute debuggingBehavior =
host.Description.Behaviors.Find<ServiceBehaviorAttribute>( );
debuggingBehavior.IncludeExceptionDetailInFaults = true
;
host.Open( );
You can streamline this procedure by encapsulating it in ServiceHost<T>
, as shown in Example 6-7.
Example 6-7. ServiceHost<T> and returning unknown exceptions
public class ServiceHost<T> : ServiceHost { public bool IncludeExceptionDetailInFaults { set { if(State == CommunicationState.Opened) { throw new InvalidOperationException("Host is already opened"); } ServiceBehaviorAttribute debuggingBehavior = Description.Behaviors.Find<ServiceBehaviorAttribute>( ); debuggingBehavior.IncludeExceptionDetailInFaults = value; } get { ServiceBehaviorAttribute debuggingBehavior = Description.Behaviors.Find<ServiceBehaviorAttribute>( ); return debuggingBehavior.IncludeExceptionDetailInFaults; } } //More members }
Using ServiceHost<T>
is trivial and
readable:
ServiceHost<MyService> host = new ServiceHost<MyService>( );
host.IncludeExceptionDetailInFaults = true
;
host.Open( );
To apply this behavior administratively, add a custom behavior
section in the host config file and reference it in the service
definition, as shown in Example 6-8.
Example 6-8. Administratively including exceptions in the fault message
<system.serviceModel> <services> <service name = "MyService" behaviorConfiguration = "Debugging
"> ... </service> </services> <behaviors> <serviceBehaviors> <behavior name = "Debugging"> <serviceDebugincludeExceptionDetailInFaults = "true"
/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
The advantage of administrative configuration in this case is the ability to toggle the behavior in production post-deployment without affecting the service code.
While including the exception details in the fault is a useful diagnostic technique,
it is a cumbersome programming model: the client has to take extra steps to extract the
error information out of the ExceptionDetail
object.
More deterring is the fact that the client must use a single massive catch
statement (that catches a single FaultException<ExceptionDetail>
) to catch all possible
exceptions, and sort them all inside the catch
statement. In the world of .NET, this is akin to always catching a mere Exception
, and avoiding cascading catch
statements.
In addition, when writing a test client, you want to know as much as possible about
the original exception that happened on the service side, since your test cases are
predicated on producing specific errors. The test client could extract the original
exception from the ExceptionDetail
object and
recursively build the inner exception chain. However, that would be tedious and
redundant, and it would require repeated code on every use of the service by the test
client. It is therefore better to encapsulate these steps in the proxy using C# 3.0
extensions. To that end, I wrote the ExtractException(
)
extension method to FaultException<ExceptionDetail>
, defined as:
public static class DebugHelper { public static Exception ExtractException( this FaultException<ExceptionDetail> fault); //More members }
The implementation of FaultException<ExceptionDetail>
has nothing to do with WCF, so I
won't show it here (but it is available with ServiceModelEx). The
best way of using the extension is to encapsulate it within the proxy, as shown in Example 6-9.
Example 6-9. Automatically extracting the exception
[ServiceContract] interface IMyContract { [OperationContract] void MethodWithError( ); } class MyContractClient : ClientBase<IMyContract>,IMyContract { public MyContractClient( ) {} /* More constructors */ public void MethodWithError( ) { try { Channel.MethodWithError( ); } catch(FaultException<ExceptionDetail> exception
) {Abort( );
throw exception.ExtractException( );
} } }
In Example 6-9, in the case of a
FaultException<ExceptionDetail>
, the proxy
aborts itself (to prevent the proxy from being used again) regardless of whether a
transport session is present or how exactly the service threw the exception. The proxy
uses the extension method to throw the extracted exception, allowing the client to catch
the raw CLR exception. For example, for this service definition in debug mode:
[ServiceContract] interface IMyContract { [OperationContract] void MyMethod( ); } [ServiceBehavior(IncludeExceptionDetailInFaults = DebugHelper.IncludeExceptionDetailInFaults)] class MyService : IMyContract { public void MyMethod( ) { throw new InvalidOperationException( ); } }
when using the proxy from Example 6-9,
the client can expect to catch an InvalidOperationException
:
MyContractClient proxy = new MyContractClient( );
try
{
proxy.MyMethod( );
}
catch(InvalidOperationException
exception)
{...}
Exception extraction should be used judiciously, only in specific diagnostic and testing cases, since it negates the core benefit of fault masking and decoupling from the nature of the error and the technology.
Callbacks to the client can, of course, fail due to communication exceptions, or because the callback itself threw an exception. Similar to service contract operations, callback contract operations can define fault contracts, as shown in Example 6-10.
Example 6-10. Callback contract with fault contract
[ServiceContract(CallbackContract = typeof(IMyContractCallback
))] interface IMyContract { [OperationContract] void DoSomething( ); } interface IMyContractCallback { [OperationContract][FaultContract(typeof(InvalidOperationException))]
void OnCallBack( ); }
Callbacks in WCF are usually configured as one-way calls, and as such cannot define their own fault contracts.
However, unlike with a normal service invocation, what is propagated to the service and how the error manifests itself also depend upon the following:
When the callback is being invoked (i.e., whether the callback is invoked during a service call to its calling client or is invoked out-of-band by some other party on the host side)
The binding used
The type of the exception thrown
If the callback is invoked out-of-band—that is, by some party other than the service during a service operation—the callback behaves like a normal WCF operation invocation. Example 6-11 demonstrates out-of-band invocation of the callback contract defined in Example 6-10.
Example 6-11. Fault handling in out-of-band invocation
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyService : IMyContract { static List<IMyContractCallback> m_Callbacks = new List<IMyContractCallback>( ); public void DoSomething( ) { IMyContractCallback callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>( ); if(m_Callbacks.Contains(callback) == false) { m_Callbacks.Add(callback); } } public static void CallClients( ) { Action<IMyContractCallback> invoke = (callback)=> { try { callback.OnCallBack( ); } catch(FaultException<...> exception) {...} catch(FaultException exception) {...} catch(CommunicationException exception) {...} }; m_Callbacks.ForEach(invoke); } }
As you can see, it is valid to expect to handle the callback fault contract, because
faults are propagated to the host side according to it. If the client callback throws an
exception listed in the callback fault contract, or if the callback throws a FaultException
or any of its subclasses, it will not fault the
callback channel, and you can catch the exception and continue using the callback channel.
However, as with service calls, after an exception that is not part of the fault contract
occurs, you should avoid using the callback channel.
Likewise, when the service calls back to its calling client, if the callback throws a
FaultException
or any of its subclasses it will not
fault the callback channel, and the service can catch the exception and continue using the
callback channel (just as with the out-of-band invocation):
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant
)]
class MyService : IMyContract
{
public void DoSomething( )
{
IMyContractCallback callback =
OperationContext.Current.GetCallbackChannel<IMyContractCallback>( );
try
{
callback.OnCallBack( );
}
catch(FaultException exception)
{...}
}
}
Note that the service must be configured for reentrancy to avoid a deadlock, as explained in Chapter 5.
The scenario gets considerably more complex when the service invokes the callback
during a service operation, calling back to its calling client, and the exception is not
FaultException
and does not derive from FaultException
. Recall that all bindings capable of duplex
communication maintain a transport-level session. The exception during the callback
terminates the transport session from the client to the service, as well as from the
service to the client (in the case of the WS dual binding). How that exception reflects
itself to the client and the service is a product of the binding.
Since both the TCP and IPC bindings use the same transport for calls from the client
to the service and callbacks from the service to the client, when the callback throws such
an exception the client that called the service in the first place immediately receives a
CommunicationException
, even if the service catches
the exception. This is a direct result of reusing the same transport for both directions,
and having faulted the callback transport (which is tantamount to faulting the
client-to-service transport as well). The service can catch and handle the exception, but
the client still gets its exception:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
class MyService : IMyContract
{
public void DoSomething( )
{
IMyContractCallback callback =
OperationContext.Current.GetCallbackChannel<IMyContractCallback>( );
try
{
callback.OnCallBack( );
}
catch(FaultException exception) //Client still
gets CommunicationException
{...}
}
}
If the service uses the WS dual binding, when the callback throws such an exception it
has to fault both transports used. The client that called the service in the first place
immediately receives a CommunicationException
, even if
the service catches the exception. Meanwhile, the service is blocked, and it will
eventually be unblocked with a TimeoutException
:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
class MyService : IMyContract
{
public void DoSomething( )
{
IMyContractCallback callback =
OperationContext.Current.GetCallbackChannel<IMyContractCallback>( );
try
{
callback.OnCallBack( );
}
catch(TimeoutException
exception)
{...}
}
}
The large degree of discrepancy in callback behaviors just described is a design deficiency of WCF; that is, it is a direct result of the channels architecture. It may be partially addressed in future releases.
While the callback can use the same technique shown in Example 6-4 to manually include the exception
in the fault message, the CallbackBehavior
attribute
provides the Boolean property IncludeExceptionDetailInFaults
, which can be used to include all
non-contract exceptions in the message:
[AttributeUsage(AttributeTargets.Class)] public sealed class CallbackBehaviorAttribute : Attribute,... { public bool IncludeExceptionDetailInFaults {get;set;} //More members }
As for the service, including the exceptions is instrumental in debugging:
[CallbackBehavior(IncludeExceptionDetailInFaults = DebugHelper.IncludeExceptionDetailInFaults)] class MyClient : IMyContractCallback { public void OnCallBack( ) { ... throw new InvalidOperationException( ); } }
You can also configure this behavior administratively in the client config file:
<client> <endpoint ... behaviorConfiguration = "Debug" ... /> </client> <behaviors> <endpointBehaviors> <behavior name = "Debug"> <callbackDebug includeExceptionDetailInFaults = "true"/> </behavior> </endpointBehaviors> </behaviors>
Note the use of the endpointBehaviors
tag to
affect the client's callback endpoint.
3.136.22.179