Skip navigation

Proxies and Exception Handling

Build a Proxy Wrapper to Address Timeouts and Uncaught Exceptions

RELATED: " WCF Proxies: To Cache or Not to Cache?" and " An Elegant Exception-Handling Proxy Solution "

In "WCF and Client Proxies," I discussed different techniques for proxy generation using Visual Studio 2008, and contrasted proxy generation to sharing contract libraries between clients and services when you own both sides. Regardless of your approach to proxy generation, you ll find it beneficial in most cases to create a proxy wrapper that handles common issues related to exception handling and session timeouts. In this article, I ll discuss these issues and show you how to build a proxy wrapper for common error handling that also reduces clutter in your client application code.

 

How Are WCF Proxies Different?

Most developers are accustomed to working with proxies generated for ASMX Web services, and have come to rely on a similar usage pattern. Specifically, when you want to communicate with a Web service you construct the proxy, keep it alive for the lifetime of the application (if you like), and make calls to service operations using the same proxy instance. A new instance of the Web service is constructed for each call, the specified operation invoked, and a response sent back to the client. If the operation throws an exception, it is converted into a SOAP fault (the interoperable standard for returning exceptions in Web services). Services can also decide to specifically format a SOAP fault using the SoapException type, allowing you more control over the fault details. In either case, the client catches a SoapException and handles it accordingly. The proxy instance is unaffected by the fault. Figure 1 illustrates how the same proxy instance can repeatedly invoke service operations despite exceptions.


Figure 1: Calling pattern for traditional ASMX Web service proxies.

WCF can be used to deploy services that are as simple as ASMX Web services, but WCF also provides many extended features that support advanced scenarios for both Web services and for communications behind the firewall over named pipes, TCP, or MSMQ. As soon as you begin to use these advanced features, many of which involve sessions, you must adjust how you work with proxies at the client. Specifically, you must deal with the potential of session timeout and uncaught exceptions faulting the communication channel for the service, and subsequently for the client. Figure 2 illustrates how a session timeout faults the service channel while a user takes their coffee break, and how subsequent attempts to use the same proxy will fail because the communication channel is no longer valid. The next call to the service will report a CommunicationException, indicating the channel is no longer viable, and the client channel is subsequently changed to a faulted state, while the proxy receives the CommunicationException in response.

 


Figure 2: The impact of session timeout on the client channel and proxy when WSHttpBinding supports reliable or secure sessions.

NOTE: If the session is established over HTTP, the client channel is not faulted until the next call that discovers the service channel has faulted. If the session is established over TCP or named pipes, the client channel will know about the fault immediately because of the duplex channel communication, so the CommunicationException is then thrown by the client channel and need not be sent to the service channel.

Figure 3 illustrates the problem of uncaught exceptions. With ASMX Web services, we could happily go about our business throwing CLR exceptions from the business and service tier, letting the Web service platform convert those to SOAP faults (as shown in Figure 1). With WCF, details about CLR exceptions are not reported in SOAP faults by default. You must explicitly throw a FaultException to report useful information to the client. Furthermore, uncaught exceptions have the side-effect that they will fault the service channel if not converted to a FaultException by your code prior to reaching the service model. That means if a session is present, the proxy will become useless after the uncaught exception, as shown in Figure 3.


Figure 3: The impact of uncaught exceptions on the client channel and proxy.

I m going to repeat myself because this is important. Faulted channels resulting from timeouts and uncaught exceptions only impact the client channel (thus the proxy instance) when sessions are present. So, before I provide you with a proxy wrapper to solve the problem, let me elaborate on which scenarios you might encounter relying on a session.

 

Binding Configurations and Sessions

I ve talked about bindings in previous installments of this column. In this section, I want to quickly revisit your typical Web service bindings and how sessions might be supported, along with related bindings that inherently support sessions. To achieve the equivalent of ASMX Web services using SOAP 1.1, you would use BasicHttpBinding, which doesn t support sessions. For SOAP 1.2 services that also support WS-Addressing headers, you ll use WSHttpBinding but there are some gotchas when you use the default settings for this binding:

  • Negotiation is enabled by default, and this isn t interoperable.
  • Secure sessions are enabled by default, which means you have a channel-level session and may not even know it.
  • If your service doesn t explicitly set the InstanceContextMode to PerCall, your service instance will be kept alive for the lifetime of the proxy, and you may not realize this.

To achieve services without session, which use SOAP 1.2 and WS-Addressing, you should use the following WSHttpBinding configuration:



 

   

     

    

 

 

Furthermore, the service definition should include an explicit ServiceBehaviorAttribute to set the service to a PerCall service:

[ServiceBehavior(InstanceContextMode=

 InstanceContextMode.PerCall)]

public class CounterServicePerCall:ICounterServicePerCall

Enabling secure sessions for your Web services can reduce message size because a symmetric session key is used instead of private/public key pairs, and can reduce the overhead of authentication because a session has been established for the authenticated caller. Furthermore, sessions are required for WSFederationHttpBinding. This is based on the interoperable WS-SecureConversation standard and is one form of session that you may rely on for your Web services. For this, set establishSecurityContext to its default of true:



 

   

      

    

 

 

Reliable sessions make it possible to overcome intermittent hiccups in network connectivity, where the client channel will retry sending messages if an acknowledgement (similar to TCP ACK over HTTP) is not received within a specified time. This is based on the interoperable WS-ReliableMessaging standard and is a second form of session that you may support to increase reliability of message transfers. For that, enable reliability on the binding:



 

   

   

      

    

 

 

If you decide to support application sessions, that means you want your service instance to be kept alive for the duration of the proxy lifetime (and maintain some form of state in that service instance). I don t recommend this for Web services, unless you are in a low-traffic environment. Traditional Web services are state-unaware, and store state in the database between calls. Regardless, to support application sessions you would configure your service so the contract requires sessions, and the service instance supports sessions as follows:

[ServiceContract(Namespace="http://www.thatindigogirl.com/

 samples/2006/06",

SessionMode=SessionMode.Required)]

public interface ICounterServiceSession

[ServiceBehavior(InstanceContextMode=

 InstanceContextMode.PerSession)]

public class CounterServiceSession:ICounterServiceSession

If you are exposing services over named pipes or TCP, you ll have a transport session. These are not typical bindings for Web services, but for intranet applications or calls behind the firewall it is important for you to be aware of the impact of sessions on the use of proxies. With previous technologies (.NET Remoting and Enterprise Services) proxies were not impacted by sessions.

 

Handling Uncaught Exceptions and Timeouts

In a client application that consumes Web services, you ll traditionally keep the proxy alive for the lifetime of the application and reuse it to call service operations. As noted, a WCF proxy will behave much like its ASMX predecessors in the absence of a session. In other words, uncaught exceptions and timeouts will not fault the communication channel.

Consider the service shown in Figure 4. The service has three methods:

  • IncrementCounter, which does not throw an exception.
  • ThrowException, which throws a CLR exception.
  • ThrowFault, which throws a FaultException instead of a CLR exception.

Using the proxy generated by Add Service Reference or SvcUtil, you can construct a proxy instance (and the underlying client channel) and call each service operation. If any of the aforementioned sessions are present, the call to ThrowException will result in an unknown FaultException received by the client. Calls to ThrowFault will not fault the service channel, but the error is still reported as an explicit FaultException to the client. Of course, the user should be made aware of both types of faults because in both cases it is an application error of some sort. The problem is that in the case of ThrowException, the client channel will be faulted after the call, because the exception was not a known FaultException, thus the proxy is no longer useful. The next call to the service will result in a CommunicationObjectFaultedException forever more. Most likely, you don t want the user to see this message. Instead, you should create a new proxy/channel silently.

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]

public class CounterServicePerCall:ICounterServicePerCall

{

 private int m_counter;

 int ICounterServicePerCall.IncrementCounter()

 {

   m_counter++;

   MessageBox.Show(String.Format("Incrementing PerCall counter to {0}

                   .\r\nSession Id: {1}", m_counter,

                   OperationContext.Current.SessionId));

   return m_counter;

 }

 public void ThrowException()

 {

   throw new NotImplementedException("The method or operation

                                     is not implemented.");

 }

 public void ThrowFault()

 {

   throw new FaultException ("This is an invalid operation.");

 }

}

Figure 4: Operations exposed by the counter service from the sample code.

NOTE: I m assuming here that the session is not an application session, whereby the abrupt termination of the service channel did not lose data. If you are using application sessions, you ll likely want the user to be aware of the problem.

This means that your client code should really check the state of the channel after each call, and if it is faulted, act accordingly. If the faulted channel is not something your users need to be aware of, you can recreate the proxy (see Figure 5).

try

{

 CounterServicePerCallClient proxy = GetPerCallProxy();

 proxy.ThrowException();

}

catch (FaultException faultEx)

{

 MessageBox.Show(string.Format("FaultException: {0}",

   faultEx.ToString()));

}

catch (CommunicationException comEx)

{

 MessageBox.Show(string.Format("CommunicationException:

    {0}", comEx.ToString()));

}

catch (Exception ex)

{

 MessageBox.Show(string.Format("Exception: {0}",

    ex.ToString()));

}

if (GetPerCallProxy().State == CommunicationState.Faulted)

 this.CreatePerCallProxy();

Figure 5: Detecting FaultException, CommunicationException, and other Exception types after each service call.

This technique solves the problem of recreating the proxy after an uncaught exception from the service that faults the communication channel. It doesn t address the case of timeout, or one-way calls that do not report the fault until the next call to the service. In the latter case, a CommunicationException will be thrown on the next call, which the user would be presented with in a MessageBox, when the CommunicationException is caught in the code from Figure 5. But do you really want your users to know about the session timeout? Do you want them to know the channel has faulted with an uncaught exception? If not, you must catch the CommunicationException, recreate the proxy, and retry the call, as shown here:

catch (CommunicationException comEx)

{

 if (GetPerCallProxy().State == CommunicationState.Faulted)

 {

   CreatePerCallProxy();

   GetPerCallProxy().ThrowException();

 }

 //MessageBox.Show(string.Format("CommunicationException:

   {0}", comEx.ToString()));

}

If an exception is thrown at this point, you know there is a bigger problem at the service; for example, it could be offline, and of course the user should be notified and your code should react accordingly.

This last code snippet prevents users from seeing unwanted error messages related to timeout or uncaught exceptions thrown by one-way methods that faulted the communication channel at the service (and thus impact the client). There are still some flaws in this approach that I should point out.

Unfortunately, a specific TimeoutException is not thrown when the service channel times out. Depending on the binding configuration, the following exceptions result from a timeout:

  • Over HTTP with reliable sessions or secure sessions the client channel doesn t know about timeout until the next call. With reliable sessions, any call (including one-way) will report the problem. Without reliable sessions, only a two-way call will report the problem. The service channel reports a fault related to a bad security context token; this is converted to a MessageSecurityException at the client.
  • Over TCP or named pipes, the client channel knows about the timeout before the next call. Thus, it is the client channel throwing a CommunicationException indicating that the TCP socket has aborted or the pipe is closing.
  • With TCP and reliable sessions enabled, the client channel is actually faulted with the timeout, before the next call; so, in theory, you could detect the faulted state before the next call instead of catching a CommunicationException indicating a CommunicationObjectFaultedException.

The common flaw in these scenarios is that we don t really know that the problem is a timeout. The workaround is to recreate the channel assuming it is a timeout, and if subsequent calls still fail, the problem is reported to the user and logged.

As for uncaught exceptions, a good service design dictates that uncaught exceptions should never propagate to the client unless they are part of a fatal issue at the service. Unfortunately, we cannot dictate good service design, so at the client if we want the user to be productive, we owe it to them to at least try to complete the call with a new proxy, assuming the faulted channel from exceptions or timeout does not impact future communications.

 

Creating a Proxy Wrapper

The burden of writing code to recreate proxies when the channel is faulted, and handle retries for timeouts, is at best cumbersome in the client application. To solve this problem, I ve devised a base proxy wrapper that creates the channel factory and channel directly, checking to see if the channel is faulted before use and recreating the channel if there is a fault. Figure 6 shows the code for ProxyBase.

public abstract class ProxyBase

{

 private object m_channelLock = new object();

 private ChannelFactory m_innerChannelFactory = null;

 private T m_innerChannel = default(T);

 protected bool m_recreateOnFault = true;

 protected bool m_retryAfterException = true;

 public bool RetryAfterException

 {

   get { return m_retryAfterException; }

   set { m_retryAfterException = value; }

 }

 public bool RecreateOnFault

 {

   get { return m_recreateOnFault; }

   set { m_recreateOnFault = value; }

 }

 public ProxyBase(string endpointConfigurationName)

 {

   lock (m_channelLock)

   {

     m_innerChannelFactory =

       new ChannelFactory(endpointConfigurationName);

   }

 }

 // other constructors

 protected T InnerChannel

 {

   get

   {

       lock (m_channelLock)

       {

         if (!object.Equals(m_innerChannel, default(T)))

         {

           ICommunicationObject channelObject =

             m_innerChannel as ICommunicationObject;

           if (channelObject.State ==

               CommunicationState.Faulted &&

               m_recreateOnFault)

           {

             m_innerChannel = default(T);

           }

         }

           if (object.Equals(m_innerChannel, default(T)))

           {

             m_innerChannel =

               m_innerChannelFactory.CreateChannel();

           }

       }

     return m_innerChannel;

   }

 }

}

Figure 6: Partial listing for ProxyBase.

ProxyBase by default caches the channel factory and channel for the lifetime of the proxy. There are three properties:

  • RecreateOnFault is a flag indicating if you want the InnerChannel to be recreated if there is a faulted state. The default is true. It is used by the InnerChannel property before returning the channel to the derived class.
  • RetryAfterException is a flag indicating if you want to retry calls to the service when a CommunicationException is thrown the first time. This addresses the issue of timeout and one-way, which would report an unwanted exception before the channel is faulted.
  • InnerChannel is a protected property accessible only to the derived class. Your job is to implement a derived class that is strongly typed for the service contract, and, if desired, check CommunicationException after each call and perform a single retry if RetryAfterException is true (see Figure 7).

public class CountersPerCallProxy:

 ProxyBase,

 Contracts.ICounterServicePerCall

{

  // constructors

 public int IncrementCounter()

 {

   int counter = 0;

   try

   {

     counter = this.InnerChannel.IncrementCounter();

   }

   catch (CommunicationException comEx)

   {

     if (this.m_retryAfterException)

       counter = this.InnerChannel.IncrementCounter();

   }

   return counter;

 }

  // other methods

}

Figure 7: Implement a derived class that is strongly typed for the service contract, then check CommunicationException after each call and perform a single retry if RetryAfterException is true.

Your client application code changes to use the proxy wrapper and need only catch and display FaultException or other Exception types with full knowledge that those unwanted CommunicationExceptions will be suppressed for at least one call unless the problem goes beyond a simple faulted channel or timeout.

 

Conclusion

It s important to think about the lifetime of your client proxies and how you want to handle timeouts and exceptions in terms of notifying the end-user of the issue, as well as creating a new proxy and communication channel for the service. Whatever your decision about the relevance of timeout and uncaught exceptions, and the impact of this to the user, a proxy wrapper can be helpful to simplify the client code reducing it to dealing only with relevant application exceptions. The sample code for this article includes a sample before using the proxy wrapper, and after, testing all WCF binding protocols. Enjoy!

Download the samples for this article from http://www.dasblonde.net/downloads/aspprojan08.zip.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish