Proxies and Exception Handling

Build a Proxy Wrapper to Address Timeouts and Uncaught Exceptions

14 Min Read
ITPro Today logo

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 forproxy generation using Visual Studio 2008, and contrasted proxy generation tosharing contract libraries between clients and services when you own bothsides. Regardless of your approach to proxy generation, you ll find itbeneficial in most cases to create a proxy wrapper that handles common issuesrelated to exception handling and session timeouts. In this article, I lldiscuss these issues and show you how to build a proxy wrapper for common errorhandling that also reduces clutter in your client application code.

 

How Are WCF Proxies Different?

Most developers are accustomed to working with proxiesgenerated for ASMX Web services, and have come to rely on a similar usagepattern. Specifically, when you want to communicate with a Web service youconstruct the proxy, keep it alive for the lifetime of the application (if youlike), and make calls to service operations using the same proxy instance. Anew instance of the Web service is constructed for each call, the specifiedoperation invoked, and a response sent back to the client. If the operationthrows an exception, it is converted into a SOAP fault (the interoperablestandard for returning exceptions in Web services). Services can also decide tospecifically format a SOAP fault using the SoapException type, allowing youmore control over the fault details. In either case, the client catches aSoapException and handles it accordingly. The proxy instance is unaffected bythe fault. Figure 1 illustrates how the same proxy instance can repeatedlyinvoke service operations despite exceptions.


Figure 1: Calling pattern fortraditional ASMX Web service proxies.

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

 


Figure 2: The impact of sessiontimeout on the client channel and proxy when WSHttpBinding supports reliable orsecure sessions.

NOTE: If the session is established over HTTP, the clientchannel is not faulted until the next call that discovers the service channelhas faulted. If the session is established over TCP or named pipes, the clientchannel will know about the fault immediately because of the duplex channelcommunication, so the CommunicationException is then thrown by the clientchannel 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 CLRexceptions from the business and service tier, letting the Web service platformconvert those to SOAP faults (as shown in Figure 1). With WCF, details aboutCLR exceptions are not reported in SOAP faults by default. You must explicitlythrow a FaultException to report useful information to the client. Furthermore,uncaught exceptions have the side-effect that they will fault the servicechannel if not converted to a FaultException by your code prior to reaching theservice model. That means if a session is present, the proxy will become uselessafter the uncaught exception, as shown in Figure 3.


Figure 3: The impact of uncaughtexceptions 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 impactthe client channel (thus the proxy instance) when sessions are present. So,before I provide you with a proxy wrapper to solve the problem, let meelaborate on which scenarios you might encounter relying on a session.

 

Binding Configurations and Sessions

I ve talked about bindings in previous installments ofthis column. In this section, I want to quickly revisit your typical Webservice bindings and how sessions might be supported, along with relatedbindings that inherently support sessions. To achieve the equivalent of ASMX Webservices using SOAP 1.1, you would use BasicHttpBinding, which doesn t supportsessions. For SOAP 1.2 services that also support WS-Addressing headers, you lluse WSHttpBinding but there are some gotchas when you use the defaultsettings 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.2and WS-Addressing, you should use the following WSHttpBinding configuration:

               

Furthermore, the service definition should include anexplicit 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 reducemessage size because a symmetric session key is used instead of private/publickey pairs, and can reduce the overhead of authentication because a session hasbeen established for the authenticated caller. Furthermore, sessions arerequired for WSFederationHttpBinding. This is based on the interoperableWS-SecureConversation standard and is one form of session that you may rely onfor your Web services. For this, set establishSecurityContext to its default oftrue:

                

Reliable sessions make it possible to overcomeintermittent hiccups in network connectivity, where the client channel willretry sending messages if an acknowledgement (similar to TCP ACK over HTTP) isnot received within a specified time. This is based on the interoperableWS-ReliableMessaging standard and is a second form of session that you maysupport to increase reliability of message transfers. For that, enablereliability on the binding:

                   

If you decide to support application sessions, that meansyou want your service instance to be kept alive for the duration of the proxylifetime (and maintain some form of state in that service instance). I don trecommend this for Web services, unless you are in a low-traffic environment.Traditional Web services are state-unaware, and store state in the databasebetween calls. Regardless, to support application sessions you would configureyour service so the contract requires sessions, and the service instancesupports 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 llhave a transport session. These are not typical bindings for Web services, butfor intranet applications or calls behind the firewall it is important for youto be aware of the impact of sessions on the use of proxies. With previoustechnologies (.NET Remoting and Enterprise Services) proxies were not impactedby sessions.

 

Handling Uncaught Exceptions and Timeouts

In a client application that consumes Web services, you lltraditionally keep the proxy alive for the lifetime of the application andreuse it to call service operations. As noted, a WCF proxy will behave muchlike 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 hasthree 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 orSvcUtil, you can construct a proxy instance (and the underlying client channel)and call each service operation. If any of the aforementioned sessions arepresent, the call to ThrowException will result in an unknown FaultExceptionreceived 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. Ofcourse, the user should be made aware of both types of faults because in bothcases it is an application error of some sort. The problem is that in the caseof ThrowException, the client channel will be faulted after the call, becausethe exception was not a known FaultException, thus the proxy is no longeruseful. The next call to the service will result in a CommunicationObjectFaultedExceptionforever 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}                   .rSession 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 anapplication session, whereby the abrupt termination of the service channel didnot lose data. If you are using application sessions, you ll likely want theuser to be aware of the problem.

This means that your client code should really check thestate of the channel after each call, and if it is faulted, act accordingly. Ifthe faulted channel is not something your users need to be aware of, you canrecreate 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 typesafter each service call.

This technique solves the problem of recreating the proxyafter an uncaught exception from the service that faults the communicationchannel. It doesn t address the case of timeout, or one-way calls that do notreport the fault until the next call to the service. In the latter case, aCommunicationException will be thrown on the next call, which the user would bepresented with in a MessageBox, when the CommunicationException is caught inthe code from Figure 5. But do you really want your users to know about thesession timeout? Do you want them to know the channel has faulted with anuncaught exception? If not, you must catch the CommunicationException, recreatethe 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 isa bigger problem at the service; for example, it could be offline, and ofcourse the user should be notified and your code should react accordingly.

This last code snippet prevents users from seeing unwantederror messages related to timeout or uncaught exceptions thrown by one-waymethods that faulted the communication channel at the service (and thus impactthe client). There are still some flaws in this approach that I should pointout.

Unfortunately, a specific TimeoutException is not thrownwhen the service channel times out. Depending on the binding configuration, thefollowing 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 reallyknow that the problem is a timeout. The workaround is to recreate the channelassuming it is a timeout, and ifsubsequent calls still fail, the problem is reported to the user and logged.

As for uncaught exceptions, a good service design dictatesthat uncaught exceptions should never propagate to the client unless they arepart of a fatal issue at the service. Unfortunately, we cannot dictate goodservice design, so at the client if we want the user to be productive, we oweit to them to at least try to complete the call with a new proxy, assuming thefaulted channel from exceptions or timeout does not impact futurecommunications.

 

Creating a Proxy Wrapper

The burden of writing code to recreate proxies when thechannel is faulted, and handle retries for timeouts, is at best cumbersome inthe client application. To solve this problem, I ve devised a base proxywrapper that creates the channel factory and channel directly, checking to seeif the channel is faulted before use and recreating the channel if there is afault. 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: Partiallisting for ProxyBase.

ProxyBase by default caches the channel factoryand 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: Implementa derived class that is strongly typed for the service contract, then checkCommunicationException after each call and perform a single retry ifRetryAfterException is true.

Your client application code changes to use the proxywrapper and need only catch and display FaultException or other Exception typeswith full knowledge that those unwanted CommunicationExceptions will besuppressed for at least one call unless the problem goes beyond a simplefaulted channel or timeout.

 

Conclusion

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

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

Read more about:

Microsoft
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like