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.
Exceptions have limitations and security risks.
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.
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.
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();
}
}
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);
}
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;
}
}
Host this WCF service in IIS or in Windows service.
Now add a console application to the solution. Name it as NorthwindApp.
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.
Enter ProductServiceRef for Namespace and click on OK. Service reference is added to your client application.
Check Client application's App.config, it should have service endpoint and client details.
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.