Académique Documents
Professionnel Documents
Culture Documents
The client can actually encounter three types of errors when trying to invoke a service. The first
type of error is a communication error, which may occur because of network unavailability, an
incorrect address, the host process not running, and so on. Communication exceptions are
manifested on the client side by a CommunicationException or a CommunicationException-
derived class such as EndpointNotFoundException.
The second type of error the client might encounter is related to the state of the proxy and the
channels. There are many such possible exceptions. For example, these errors may occur when the
client is trying to access an already closed proxy, resulting in an ObjectDisposedException;
when there is a mismatch between the contract and the binding security protection level resulting
in an InvalidOperationException; when the client's credentials are denied by the service
resulting in a SecurityNegotiationException in case of authentication failure, or
SecurityAccessDeniedException in case of authorization failure; or when the transport session
times out, resulting in a TimeoutException.
The third type of error is an error that originates in the execution of the service call itself, as a
result of either the service throwing an exception, or the service calling another object or resource
and having that internal call throw an exception.
As stated at the beginning of this chapter, it is a common illusion that clients care about errors or
have anything meaningful to do when they occur. Any attempt to bake such capabilities into the
client creates an inordinate degree of coupling between the client and the object, raising serious
design questions. How could the client possibly know more about the error than the service,
unless it is tightly coupled to it? What if the error originated several layers below the service—
should the client be coupled to those low-level layers? Should the client try the call again? How
often and how frequently? Should the client inform the user of the error? Is there a user?
All that the client cares about is that something went wrong. The best practice for most clients is
to simply let the exception go up the call chain. The topmost client typically will catch the
exception, not in order to handle it, but simply to prevent the application from shutting down
abruptly. A well-designed client should never care about the actual error; WCF enforces this. In
the interest of encapsulation and decoupling, by default all exceptions thrown on the service side
always reach the client as FaultExceptions:
By having all service exceptions be indistinguishable from each other, WCF decouples the client
from the service. The less the client knows about what happened on the service side, the more
decoupled the interaction will be.
In traditional .NET programming, the client can catch the exception and keep calling the object.
Consider this definition of a class and an interface:
interface IMyContract
{
void MyMethod( );
}
class MyClass : IMyContract
{...}
If the client snuffs out the exception thrown by the object, it can call it again:
This is a fundamental flaw of .NET as a platform. Exceptions, by their very nature, are for
exceptional cases. Here, something totally unexpected and horrible has happened. How could the
client possibly pretend otherwise? The object is hopelessly broken, and yet the client keeps using
it. In classic .NET, developers that did not approve of this behavior had to maintain a flag in each
object, set the flag before throwing an exception (or after catching any downstream exceptions),
and check the flag inside any public method, refusing to use the object if it was called after an
exception had been thrown. This, of course, is cumbersome and tedious. WCF automates this best
practice. If the service has a transport session, any unhandled exceptions (save those derived from
FaultException, as described next) fault the channel (the proxy's state is changed to
CommunicationState.Faulted), thus preventing the client from using the proxy, and the object
behind it, after an exception. In other words, for this service and proxy definition:
[ServiceContract]
interface IMyContract
{
[OperationContract]
void MyMethod( );
}
class MyClass : IMyContract
{...}
//Throws CommunicationObjectFaultedException
proxy.MyMethod( );
The obvious conclusion is that the client should never try to use a WCF proxy after an exception.
If there was a transport session, the client cannot even close the proxy.
The only thing a client might safely do after an exception is to abort the proxy, perhaps to trigger
tracing, or raise events for state changes in the proxy, or to prevent others from using the proxy
(even if there was no transport session):
I recommend against relying on the using statement to close the proxy. The reason is that in the
presence of a transport session, any service-side exception will fault the channel. Trying to
dispose of the proxy when the channel is faulted throws a
CommunicationObjectFaultedException, so code after the using statement will never get
called, even if you catch all exceptions inside the using statement:
This reduces the readability of the code and may introduce defects, since the code will behave
differently than most developers will expect. The only remedy is to encase the using statement
itself in a TRy/catch statement:
try
{
using(MyContractClient proxy = new MyContractClient( ))
{
try
{
proxy.MyMethod( );
}
catch
{}
}
}
catch
{}
Trace.WriteLine("This trace always gets called");
It is therefore far better to call Close( ). In the case of an exception, the exception will skip over
the call to Close( ):
try
{
MyContractClient proxy = new MyContractClient( );
proxy.MyMethod( );
proxy.Close( );
}
catch
{
proxy.Abort( );
}
Trace.WriteLine("This trace always gets called");
When the service is configured as per-call or as sessionful (which mandates the use of a transport
session), the client can never access the same instance after an exception occurs. With a per-call
service this is, of course, always true, but with a sessionful service this is the result of faulting the
channel and terminating the transport session. The one exception to the rule here is a singleton.
When the client calls a singleton service and encounters an exception, the singleton instance is not
terminated and continues running. If there was no transport session (or if the exception was a
FaultException-derived class, as described next), the client can keep using the proxy to connect
to the singleton object. Even if the channel is faulted, the client can create a new proxy instance
and reconnect to the singleton.
[AttributeUsage(AttributeTargets.Class)]
public sealed class DurableServiceAttribute : ...
{
public UnknownExceptionAction UnknownExceptionAction
{get;set;}
//More members
}