Handling wcf service exceptions using fault contracts and data contracts.

In this article, we will see how we can use WCF fault contracts to handle exceptions. WCF gives you enough flexibility to handle exceptions and send required detail to clients. By default, WCF sends exception details to clients using SOAP FaultContract. The default FaultContract will allow you to set Message, Reason properties and throw it to clients.

We can easily customize the FaultContract with generic version FaultContract<T> which helps you to provide more appropriate and meaningful details to clients with custom error messages and also the flexibility to log or take actions on critical errors. To get more details about exceptions you can enable WCF tracing and logging.

Why we should use the FaultContract rather than using .Net exceptions?

Exceptions have limitations and security risks.

  1. .Net exception can be used by only CLR supported languages so losing great WCF feature of interoperability.
  2. Throwing exceptions can provide service implementation and private details to clients.
  3. Exceptions are tightly coupled between clients and service.

Walkthrough for implementing FaultContracts.

  1. Create WCF Service library

    Create a new WCF Service library as suggested in Create new WCF service library and test using WCFTestClient. This article will give you a basic service implementation.

  2. Add DataContract for FaultContract

    Add new DataContract which will work as FaultContract to send error messages to clients.

                
    [DataContract]
    public class ServiceException
    {
        /// <summary>
        /// Exception Message
        /// </summary>
        [DataMember]
        public string Message { get; set; }
    
        /// <summary>
        /// If critical, user should redirect to error page 
        /// and exception details should log.
        /// </summary>
        [DataMember]
        public bool IsCritical { get; set; }
    }
                 

    See details about DataMember and EnumMember.

  3. Exception Manager

    Add a new sealed class to the project and name it as ExceptionManager. This class will work as a single point to handle all WCF service exceptions and errors.

                
    public sealed class ExceptionManager
    {
        /// <summary>
        /// Private Constructor to ensure that the class cannot be instantiated
        /// </summary>
        private ExceptionManager()
        {
    
        }
    
        public static ServiceException HandleException(string message)
        {
            return HandleException(message, false); 
        }
    
        public static ServiceException HandleException(string message,
                                                            bool isCritical)
        {
            ServiceException exception = new ServiceException();
            exception.Message = message;
            exception.IsCritical = isCritical; 
    
            // Log exception details if it is critical
    
            return exception;
        }
    
        public static ServiceException HandleException(System.Exception ex)
        {
            ServiceException exception = new ServiceException();
            exception.Message = ex.Message;
            exception.IsCritical = IsCritical(ex);
                
            // Log exception details if it is critical
    
            return exception;
        }
    
        /// <summary>
        /// If error is not user defined then IsCritical should be true.
        /// </summary>
        /// <param name="ex">Exception</param>
        /// <returns>bool value</returns>
        private static bool IsCritical(System.Exception ex)
        {
            bool returnValue = true;
            if (ex is SqlException)
            {
                SqlException sqlEx = (SqlException)ex;
                returnValue = sqlEx.Number > 50000 ? false : true; 
            }
            return returnValue;
        }
    
        public static bool HandleException(System.Exception ex, string policyName)
        {
            throw new NotImplementedException();
        }
    }
                
  4. Add FaultContracts

    Add FaultContracts to OperationContract.

                
    [ServiceContract]
    public interface IProducts
    {
        [OperationContract]
                [FaultContract(typeof(ServiceException))]
        string GetProductName(int productID);
    
        [OperationContract]
                [FaultContract(typeof(ServiceException))]
        int GetProductQty(int productID);
    
        [OperationContract]
                [FaultContract(typeof(ServiceException))]
        string GetCategoryName(int productID);
    }       
                            
                
  5. FaultContract Implementation

    Add implementation for FaultContract. If any error occurs, an error will be handled by ExceptionManager class and a custom message will be returning to clients.

                
    public class ProductService : IProducts
    {
    public string GetProductName(int productID)
    {
        string productName = string.Empty;
    
        try
        {
            XDocument doc = XDocument.Load("products.xml");
    
            productName =
                (from result in doc.Descendants("DocumentElement")
                .Descendants("Products")
                where result.Element("productID").Value == productID.ToString()
                select result.Element("productname").Value)
                .FirstOrDefault<string>();
    
                if (string.IsNullOrEmpty(productName)) 
                    throw new FaultException<ServiceException>(ExceptionManager.
                        HandleException("Product ID does not exist in system.")); 
        }catch (Exception ex) 
        { 
            throw new FaultException<ServiceException> 
                (ExceptionManager.HandleException(ex));
        }
    
        return productName;
    }
            
    public int GetProductQty(int productID)
    {   
        int ProductQty = 0;
    
        try
        {
            XDocument doc = XDocument.Load("products.xml");
            string strProductQty =
                (from result in doc.Descendants("DocumentElement")
                .Descendants("Products")
                where result.Element("productID").Value == productID.ToString()
                select result.Element("UnitsInStock").Value)
                .FirstOrDefault<string>();
    
                 if (string.IsNullOrEmpty(strProductQty)) 
                    throw new FaultException<ServiceException>(ExceptionManager.
                        HandleException("Product ID does not exist in system.")); 
    
            int.TryParse(strProductQty, out ProductQty);
        }catch (Exception ex) 
        { 
            throw new FaultException<ServiceException>
                    (ExceptionManager.HandleException(ex));
        } 
        return ProductQty;
    }
    
    public string GetCategoryName(int productID)
    {
        string categoryName = string.Empty;
        try
        {
            XDocument doc = XDocument.Load("products.xml");
    
            categoryName =
                (from result in doc.Descendants("DocumentElement")
                .Descendants("Products")
                where result.Element("productID").Value == productID.ToString()
                select result.Element("CategoryName").Value)
                .FirstOrDefault<string>();
    
                if (string.IsNullOrEmpty(categoryName)) 
                throw new FaultException<ServiceException>(ExceptionManager.
                    HandleException("Product ID does not exist in system.")); 
        }catch (Exception ex) 
        { 
            throw new FaultException<ServiceException> 
                (ExceptionManager.HandleException(ex));
        }
    
        return categoryName;
    }
    }
                
  6. Hosting Service

    Host this WCF service in IIS or in Windows service.

  7. Add Client Application

    Now add a console application to the solution. Name it as NorthwindApp.

  8. Add Service Reference

    Add service reference to NorthwindApp by right click on NorthwindApp project and click on Add Service Reference. Below the window should appear. Enter the address of the service endpoint which you have added in the service app. config.

    WCF Client Application

    Enter ProductServiceRef for Namespace and click on OK. Service reference is added to your client application.

    WCF Client Application

    Check Client application's App.config, it should have service endpoint and client details.

  9. Client Implementation

    Service reference is now available for Northwind. We can call service operations and implement error handling using FaultContracts. Add the below code to NorthwindApp -> Program.cs file.

                
    class Program
    {
    static void Main(string[] args)
    {
        ShowOperations();
    }
    
    private static  void ShowOperations()
    {
        ConsoleKeyInfo cki;
    
        do
        {
            Console.WriteLine(); 
            Console.WriteLine("Enter Product ID or press Esc to quit : ");
            cki = Console.ReadKey();
            if (!char.IsNumber(cki.KeyChar))
            {
                Console.WriteLine("  Invalid number");
            }
            else
            {
                Int32 number;
                if (Int32.TryParse(cki.KeyChar.ToString(), out number))
                {
                    Console.WriteLine(); 
                    GetProductName(number);
                    GetProductQty(number);
                    GetCategoryName(number);  
                }
                else
                {
                    Console.WriteLine("Unable to parse input");
                }
            }
        } while (cki.Key != ConsoleKey.Escape);
    
        Console.Read(); 
    }
    
    private static void GetProductName(int ProductID)
    {   
        try
        {
            ProductsClient client = new ProductsClient();
            string ProductName = client.GetProductName(ProductID);
            Console.WriteLine(string.Format("Product name {0} for ProductID {1}",
                    ProductName, ProductID));
        }
                catch(FaultException<ServiceException> ex)
        { 
            Console.WriteLine(string.Format("Errors
                occured in service : {0} ", ex.Detail.Message)); 
        }
    }
    
    private static void GetProductQty(int ProductID)
    {   
        try
        {
            ProductsClient client = new ProductsClient();
            int ProductQty = client.GetProductQty(ProductID);
            Console.WriteLine(string.Format("Product qty {0} for Product ID {1}",
                    ProductQty, ProductID));
        }
                 catch(FaultException<ServiceException> ex) 
        { 
            Console.WriteLine(string.Format("Errors
                occured in service : {0} ", ex.Detail.Message)); 
        }
    }
    
    private static void GetCategoryName(int ProductID)
    {
        try
        {
            ProductsClient client = new ProductsClient();
            string CategoryName = client.GetCategoryName(ProductID);
            Console.WriteLine(string.Format("Category name{0} for ProductID {1}",
                    CategoryName,ProductID));
        }
                catch(FaultException<ServiceException> ex) 
        { 
            Console.WriteLine(string.Format("Errors
                occured in service : {0} ", ex.Detail.Message)); 
        }
    }
    }
                

    See the implementation of ServiceException in bold.

While working with code from this article do not forget to mention appropriate path of data store which can be Products.xml download here.

Source code on Git hub Source Code on Github

Speak your mind
Please login to post your comment!


Blog Search





If you like my content please feel free to buy me coffee. Buy Me A Coffee