Click here to Skip to main content
Click here to Skip to main content

WCF-Global Exception Handling using IErrorHandler

, 6 Aug 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Global Exception Handling or Controlling the fault message returned to the client and also additional features like error logging. IErrorHandler, IServiceBehaviuor. HandleError, ProvideFault.

Before we start on this article I have a quick question for you, Have you ever worked with WCF - FaultExceptions?

If "No" then I would recommend you to start with WCF - Exception Handling, FaultException, FaultException<TDetail> and FaultContracts

If "Yes" then continue reading Smile | :)

Quick Introduction

When I got a chance to develop some WCF Services I thought of learning how to handle exceptions. When I started learning FaultContracts I thought it would be even better if I can centralize the FaultContracts. Finally I have reached IErrorHandler for making my exception handling global.

To demonstrate the centrilized excption handling in wcf service, I have created a simple WCF Service as below

Solution Explorer

WCF Global Exception Handling IErrorHandler IServiceBehaviour

ISampleService.cs 

WCF Exception Handling

SampleService.svc.cs

WCF Exception Handling

In this article we will try to learn how to handle wcf exceptions in a global way so that we dont need to handle exceptions separately for all the service operations and also we can avoid any kind of unhandled System.Exceptions raised back to client.

The Default FaultException

Let us run our WCF Service using WCF Test Client (In order to launch WCF Test Client for testing this service, please select SampleService.svc or SampleService.svc.cs at the Solution Explorer and start debugging). As you have seen in the SampleService definition, the code doesn't do anything, it only throws an error, which is enough for this article.

Once the WCF Test Client is launched then select the GetSomeData() operation,

WCF Error Handling

As the function doesn't have any parameters, just click on the Invoke button,

WCF Exception

As expected GetSomeData() method raised an exception of type NotImplementedException, but when a System.Exception is raised in the service layer and no matching catch block is found, the ServiceModel converts it to a System.ServiceModel.FaultException before re-throwing it. When it is simply converted to FaultException the original information is discarded.

I got the below exception when GetSomeData() thrown NotImplementedException.

WCF Exception Handling

"The server was unable to process the request due to an internal error.  For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework SDK documentation and inspect the server trace logs."

The above error doesn't tell anything about the actual cause, it just returned a standard FaultException, which is of no use.

As suggested in the error message, if we want to get exception details for debugging purpose we can enable IncludeExceptionDetailInFaults in web.config but this is strictly for debugging purpose and should not be moved to production. 

Read more about WCF - Exception Handling, FaultException, FaultException<TDetail> and FaultContracts.

FaultContract and FaultExceptions

In our previous article we have learned how to handle exceptions in WCF Service using FaultContract

It is strongly recommended that service operations declare their faults by using the FaultContractAttribute attribute to formally specify all SOAP faults that a client can expect to receive in the normal course of an operation. It is also recommended that you return in a SOAP fault only the information that a client must know to minimize information disclosure. For making FaultContract work we need to decorate the OperationContract with FaultContractAttribute and also we need to define the Fault DataContract, this Fault class needs to be instantiated with error information where we catch the exception and then return a FaultException<TDetail>Read more about WCF - Exception Handling, FaultException, FaultException<TDetail> and FaultContracts.

What if some one forget to add try-catch block in one of your service operations, it might fault the channel and the client app may get down, also the client may not get any idea about the exception. In this article we will learn how to handle exceptions from all service operations by sending a  generic vague FaultException back to the client so the client knows what whent wrong and also we will log the exception details.

Global Exception Handling - IServiceBehavior and IErrorHandler

To demonstrate this we need to create an ErrorHandler class by implementing the IErrorHandler and also a ServiceBehaviorAttribute class by impementing a IServiceBehaviorIServiceBehavior allows you to implement a service behaviour attribute which takes a custom error handler parameter which implements IErrorHandler

The IErrorHandler type exposes two methods. HandleError and ProvideFault

  • Use the HandleError method to implement error-related behaviors such as error logging, system notifications, shutting down the application, and so on, and return a value that specifies whether the exception has been handled appropriate
  • ProvideFault enables the creation of a custom FaultException<TDetail> that is returned to to the client from an exception in the course of a service method.

The IServiceBehavior type exposes three methods. AddBindingParameters, ApplyDispatchBehavior and Validate. For this article we are much interested only in ApplyDispatchBehavior method.

  • Here AddBindingParameters provides the ability to pass custom data to binding elements to support the contract implementation.
  • ApplyDispatchBehavior provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
  • Use the Validate method to confirm whether the current service can execute properly according to your scenario.

How it works

Whenever an unhandled exception occurs in a service decorated with the service behaviour attribute, two methods on the global error handler are called automatically: ProvideFault which allows you to construct a FaultException (rather than let ServiceModel do it), and HandleError which is intended for logging. The ProvideFault method is called first on the worker thread that is invoking the service call and HandlerError is called asynchronously on a separate thread, so lengthy logging operations don’t block the service request thread from sending a FaultException back to the client immediately.

Lets add this functionality to our sample WCF Service application.

Now I'm adding a class named GlobalErrorHandler which implements IErrorHandler as below,

GlobalErrorHandler.cs

In the HandleError method the GlobalErrorHandler writes a log of the error to an error.txt file in the applications Log folder and the ProvideFault method converts the System.Exception to a meaningful FaultException by adding the original exception information to the fault message rather than being discarded. You can format the new FaultException to a more simplified and not to include sensitive data to the client.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Web;
using System.Web.Hosting;

namespace WcfService
{
    public class GlobalErrorHandler : IErrorHandler
    {
        // Provide a fault. The Message fault parameter can be replaced, or set to
        // null to suppress reporting a fault.

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            var newEx = new FaultException(
                string.Format("Exception caught at GlobalErrorHandler{0}Method: {1}{2}Message:{3}",
                             Environment.NewLine, error.TargetSite.Name, Environment.NewLine, error.Message));

            MessageFault msgFault = newEx.CreateMessageFault();
            fault = Message.CreateMessage(version, msgFault, newEx.Action);
        }

        // HandleError. Log an error, then allow the error to be handled as usual.
        // Return true if the error is considered as already handled

        public bool HandleError(Exception error)
        {
            string path = HostingEnvironment.ApplicationPhysicalPath;

            using (TextWriter tw = File.AppendText(Path.Combine(path, @"Logs\\error.txt")))
            {
                if (error != null)
                {
                    tw.WriteLine("Exception:{0}{1}Method: {2}{3}Message:{4}",
                        error.GetType().Name , Environment.NewLine , error.TargetSite.Name ,
                        Environment.NewLine,  error.Message + Environment.NewLine);
                }
                tw.Close();
            }

            return true;
        }
    }
}

The HandlerError is called asynchronously on a separate thread, so lengthy logging operations don’t block the service request thread from sending a FaultException back to the client immediately.

GlobalErrorBehaviorAttribute.cs

The GlobalErrorBehaviorAttribute exists as a mechanism to register an error handler with a service. This attribute takes a single type parameter. That type should implement the IErrorHandler interface and should have a public, empty constructor. The attribute then instantiates an instance of that error handler type and installs it into the service. It does this by implementing the IServiceBehavior interface and then using the ApplyDispatchBehavior method to add instances of the error handler to the service.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Web;

namespace WcfService
{
    public class GlobalErrorBehaviorAttribute : Attribute, IServiceBehavior
    {
        private readonly Type errorHandlerType;

        public GlobalErrorBehaviorAttribute(Type errorHandlerType)
        {
            this.errorHandlerType = errorHandlerType;
        }

        #region IServiceBehavior Members

        void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
        }

        void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
            IErrorHandler errorHandler;

            try
            {
                errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
            }
            catch (MissingMethodException e)
            {
                throw new ArgumentException("The errorHandlerType specified in the ErrorBehaviorAttribute constructor must have a public empty constructor.", e);
            }
            catch (InvalidCastException e)
            {
                throw new ArgumentException("The errorHandlerType specified in the ErrorBehaviorAttribute constructor must implement System.ServiceModel.Dispatcher.IErrorHandler.", e);
            }

            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                channelDispatcher.ErrorHandlers.Add(errorHandler);
            }
        }

        #endregion  IServiceBehavior Members
    }
}

ISampleService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WcfService
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "ISampleService" in both code and config file together.
    [ServiceContract]
    public interface ISampleService
    {

        [OperationContract]
        string GetSomeData();

        [OperationContract]
        string GetUserDetails(string name);

        // TODO: Add your service operations here
    }
}

Finally, let our service implemention know that we have a global exception handling in place by decorating the service with GlobalErrorBehaviorAttribute providing the type of our custome error handler, GlobalErrorHandler.

SampleService.svc..cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WcfService
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "SampleService " in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select SampleService .svc or SampleService .svc.cs at the Solution Explorer and start debugging.
    [GlobalErrorBehaviorAttribute(typeof(GlobalErrorHandler))]
    public class SampleService : ISampleService
    {
        public string GetSomeData()
        {
            throw new NotImplementedException();
        }

        public string GetUserDetails(string name)
        {
            throw new Exception("Unable to find user.");
        }
    }
}

If we Invoke the Service Operation now we won't be getting the generic error messgae,

WCF Exception Handling

Click Invoke after giving a value for the parameter name, we will get the excpetion details as below,

WCF Global Exception Handling

converting System.Exception to FaultException

Previoulsy we used to get below message

WCF generic exception

I have attached the source code with this article

WCF Centralized Exception Handling

References

History

In this article I have explained WCF - Global Exception Handling using IErrorHandler and IServiceBehaviour. I hope you have enjoyed this article and got some value addition to your knowledge.

I have put my time and efforts on all of my articles, Please don't forget to mark your votes, suggestions and feedback to improve the quality of this and upcoming articles. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Shemeer NS
Software Developer (Senior)
India India
Technology Specialist | CodeProject MVP | Visual Studio Gallery Contributor | Author | Geek | Netizen | Husband | ChessPlayer
 
Most of my articles are listed on top 5 of the respective 'Best articles of the month' and some of my articles are published on ASP.NET WebSite's Article of the Day section.
 
Check my contributions in Visual Studio Gallery and Code Project
 
Technical Blog: http://www.shemeerns.com
Facebook: http://facebook.com/shemeernsblog
Twitter : http://twitter.com/shemeerns
Google+ : http://google.com/+Shemeernsblog
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralMy vote of 5 Pinprofessionalkhurram ali lashari21-Sep-14 8:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 6 Aug 2014
Article Copyright 2014 by Shemeer NS
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid