Jump to content

How to work with Channel Faulting in Windows Communication Foundation (WCF)

0
  chco's Photo
Posted Aug 31 2010 06:36 PM

The following is an excerpt from the O'Reilly publication Programming WCF Services, Third Edition. Below, the author gives the best ways to work with .NET programming's Channel Faulting.


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:

IMyContract obj = new MyClass();
try
{
   obj.MyMethod();
}
catch
{}
obj.MyMethod();


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 may be 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
{...}

class MyContractClient : ClientBase<IMyContract>,IMyContract
{...}


the following client code results in a CommunicationObjectFaultedException:

IMyContract proxy = new MyContractClient();
try
{
   proxy.MyMethod();
}
catch
{}

//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.


Note: If there is no transport-level session, the client can technically keep using the proxy after an exception, except again, it should not.



The only thing a client might safely do after an exception is to abort the proxy, perhaps to trigger tracing, or to raise events for state changes in the proxy, or to prevent others from using the proxy (even if there was no transport session):

MyContractClient proxy = new MyContractClient();
try
{
   proxy.MyMethod();
}
catch
{
   proxy.Abort();
}


The problem now is that you have to repeat this over and over for every method invocation. It is better to encapsulate this in the proxy itself:

class MyContractClient : ClientBase<IMyContract>,IMyContract
{
   public void MyMethod()
   {
      try
      {
         Channel.MyMethod();
      }
      catch
      {
         Abort();
         throw;
      }
   }
}


Closing the proxy and the using statement

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:

using(MyContractClient proxy = new MyContractClient())
{
   try
   {
      proxy.MyMethod();
   }
   catch
   {}
}
Trace.WriteLine("This trace may never get called");


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():

MyContractClient proxy = new MyContractClient();
proxy.MyMethod();
proxy.Close();


You can, of course, catch the exception, but now the code is readable:

MyContractClient proxy = new MyContractClient();

try
{
   proxy.MyMethod();
   proxy.Close();
}
catch
{
   proxy.Abort();
}
Trace.WriteLine("This trace always gets called");


Exceptions and instance management

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.

In the case of a durable service, the DurableService attribute offers the UnknownExceptionAction property, defined as:

public enum UnknownExceptionAction
{
    TerminateInstance,
    AbortInstance
}

[AttributeUsage(AttributeTargets.Class)]
public sealed class DurableServiceAttribute : ...
{
   public UnknownExceptionAction UnknownExceptionAction
   {get;set;}
   //More members
}


UnknownExceptionAction defaults to UnknownExceptionAction.TerminateInstance, meaning that any unhandled exception will not only fault the channel but also remove the instance state from the store, thus terminating the workflow. This behavior is analogous to simply faulting the channel with a regular service, preventing future use of the object. The value UnknownExceptionAction.AbortInstance, on the other hand, terminates the channel to the client but keeps the state in the store. While any changes made to the instance are not persisted, this value is analogous to not faulting the channel in the case of a regular service.

Cover of Programming WCF Services
Learn more about this topic from Programming WCF Services, 3rd Edition. 

Programming WCF Services is the authoritative, bestselling guide to Microsoft's unified platform for developing service-oriented applications on Windows. The third edition of this thoroughly practical book provides insight, not documentation, to help you learn the topics and skills you need for building WCF-based applications. Written by Microsoft software legend Juval Löwy, this new edition is revised for the latest productivity-enhancing features for C# 4.0 and .NET 4.

Learn More Read Now on Safari


Tags:
0 Subscribe


0 Replies