Sunday, December 18, 2011

Enforce Proper Disposal of WCF Channels against a WCF defect

In WCF, when doing client programming, we are all too familiar with

using (ProxyClient client = new ProxyClient(...))
{
...
}

When I deployed a WCF client program to a 2008 server for further testing. I always got such exception:

EndpointNotFoundException: System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.

No specific error info was disclosed in the exception. Searching for answers, I got these posts, looking quite dim about a possible WCF bug long standing: kicking out the real exception in some scenarios.

http://msdn.microsoft.com/en-us/library/aa355056.aspx
http://blogs.msdn.com/b/jjameson/archive/2010/03/18/avoiding-problems-with-the-using-statement-and-wcf-service-proxies.aspx
http://old.iserviceoriented.com/blog/post/Indisposable+-+WCF+Gotcha+1.aspx
http://thorarin.net/blog/post/2010/05/30/Indisposable-WCF-clients.aspx
http://www.codeproject.com/KB/WCF/proper-disposal-of-wcf.aspx

All the solutions / workarounds above suggested catching general exception.


try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}


However, catching general exception is smelling badly in most cases, and such workaround is very clumsy to use, for example, when you have a few dozens of such client calls to multiple WCF services of one or many Web service API in your project. They couldn't be the right solution for such defect in WCF.

So I turned to a guru who gave an elegant solution, as described in this post

Safe way to close down a ClientBase object


Based on Mr Mar's work, I made some slight amendment, as listed below:

    public class SafeCommunicationDisposal : IDisposable where T : ICommunicationObjectIDisposable
    {
        const String traceCategory = "SafeCommunicationDisposal";
        public T Instance { getprivate set; }
        public bool IsSafeToClose { getprivate set; }
        public void SafeToClose()
        {
            this.IsSafeToClose = true;
        }
 
        public SafeCommunicationDisposal(T client)
        {
            this.Instance = client;
        }
 
        bool disposed;
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                Debug.WriteLine("Need to dispose.", traceCategory);
                if (disposing)
                {
                    Close();
                }
                this.disposed = true;
            }
        }
 
        private void Close()
        {
            if (IsSafeToClose)
            {
                Instance.Close();
            }
            else
            {
                Instance.Abort();
            }
        }
    }



In my client codes, against each WCF client proxy class, e.g. RemotePpsr.CollateralRegistrationSearchServiceClient

create a helper class:

        class SafeCollateralRegistrationSearchServiceClient : SafeCommunicationDisposalCollateralRegistrationSearchServiceClient>
        {
            public SafeCollateralRegistrationSearchServiceClient(int environmentTypeId)
                : base(GetCollateralRegistrationSearchClient(environmentTypeId))
            {
 
            }
 
            static RemotePpsr.CollateralRegistrationSearchServiceClient GetCollateralRegistrationSearchClient(int environmentTypeId)
            {
                RemotePpsr.CollateralRegistrationSearchServiceClient client = new RemotePpsr.CollateralRegistrationSearchServiceClient("CollateralRegistrationSearchSoap12" + XXXMethodsHelper.GetEndpointSuffix(environmentTypeId));

                ...
                client.ClientCredentials.UserName.UserName = ...
                ...
                return client;
            }
 
        }

Then for each client API call, I have the following conventional programming style rather than catching general exception while exposing fault error properly:

            try
            {
                using (SafeCollateralRegistrationSearchServiceClient client = new SafeCollateralRegistrationSearchServiceClient(request.EnvironmentTypeId))
                {
                    RemotePpsr.SearchBySerialNumberRequestType ppsrRequest = new RemotePpsr.SearchBySerialNumberRequestType()
                        {
 ...
 
                        };
...
                    ppsrResponse = client.Instance.SearchBySerialNumber(GetTargetEnvironment(request.EnvironmentTypeId), ppsrRequest);
 
                }
 
            }
            catch (System.ServiceModel.FaultExceptionPpsrCollateralRegistrationSearchFaultDetail> e)
            {
                string m = String.Format("{0}; Message: {1}", GetPpsrSoapFaultDetail(e.Detail), e.Message);
                Trace.TraceWarning(m);
                return new RequestResult(RequestExecutionStatus.OperationFailed, e.Message, "PPSR" + e.Detail.ErrorNumber) { TechnicalErrorMessage = m };
            }
 

Notes: PPSR stands for Personal Property Securities Register which is an electronic register allowing security interests in personal property to be registered and searched in accordance with the Australian Personal Property Securities Act 2009.