It’s common scenario, a call to WCF service to fail. The service call can fail for thousands of reasons such as network problems, database crushes or even runtime logic errors. In these cases, the client called the service would appreciate if he knew the reason why the call failed and maybe he could take some action to handle it’s function respectively. WCF library provides the FaultException class from System.ServiceModel namespace that can handle that kind of failures. When the service detect an error, a FaultException is raised and can be sent back to the client, providing him with several information. This article will give you an example of how to implement this functionality. We will modify the project in this post.
First we are going to create the Fault DataContracts, the Fault type of exception that is going to be raised and return back to the client. In the “AdventureWorksService” project, where the service is implemented, open the IAdventureWorksService and add two Fault Contracts. As you will see, these classes are just like the ProductData class, that is are amended with the attributes [DataContract] for the class plus the [DataMember] for their properties. The reason is very simple. The information of these classes must be serializable so it can be sent back to client. We are going to add two types of Faults. One for Logic WCF exceptions and one for Database exception. The code for the IAdventureSowrksService.cs file is this.
namespace AdventureWorks { // Data contract describing the details of a product passed to client applications [DataContract] public class ProductData { [DataMember] public string Name; [DataMember] public string ProductNumber; [DataMember] public string Color; [DataMember] public decimal ListPrice; } // Fault Contracts [DataContract] public class LogicFault { [DataMember] public string Reason { get; set; } [DataMember] public string Message { get; set; } } [DataContract] public class DatabaseFault { [DataMember] public string Reason { get; set; } [DataMember] public string Message { get; set; } } // Service contract describing the operations provided by the WCF service [ServiceContract] public interface AdventureWorksService { // Get the product number of every product [FaultContract(typeof(LogicFault))] [FaultContract(typeof(DatabaseFault))] [OperationContract] List<string> ListProducts(); // Get the details of a single product [OperationContract] ProductData GetProduct(string productNumber); } }
We need to enable ListProducts() operation to generate SOAP Faults so we simply added two [FaultContract] attributes before the operation’s declaration.
Edit the AdventureWorksService.cs code to catch an Exception and if so, check the type: If it is an Sql.Exception then throw a DatabaseFault FaultException else throw a LogicFault FaultException. The DatabaseFault exception occurs in cases such as database connection rejections and LogicFault Exceptions occurs in cases such as when try to iterate a List object while it is null. We ‘ll see how this work later.
namespace AdventureWorks { public class AdventureWorksServiceImpl : AdventureWorksService { public List<string> ListProducts() { // Create a list for holding product numbers List<string> productsList = new List<string>(); try { // Connect to the AdventureWorks database by using the Entity Framework using (AdventureWorks2012Entities database = new AdventureWorks2012Entities()) { // Fetch the product number of every product in the database var products = from product in database.Products select product.ProductNumber; productsList = products.ToList(); } } catch(Exception e) { if (e.InnerException is System.Data.SqlClient.SqlException) { DatabaseFault fault = new DatabaseFault { Reason = "Exception accessing database", Message = e.InnerException.Message }; throw new FaultException<DatabaseFault>(fault); } else { LogicFault fault = new LogicFault { Reason = "Exception retrieving products", Message = e.Message }; throw new FaultException<LogicFault>(fault); } } // Return the list of product numbers return productsList; } //GetProduct code ommitted } }
Build the solution. The current website hosted in IIS has the old implementation so we need to re-publish the service site again.Right click the AdventureWorksService in the solution, select Publish Website, select Local IIS and put the same address you had put in the previous
posts (http://localhost/AdventureWorksService). If asked to replace existing files, just confirm. The service should run in the same way but with new service implementation at this time.
In the AdventureWorksClient project, right click “AdventureWorksService” service file in the Service References folder, and select “update service reference”. The proxy will now be based on the new service implementation and the client should be able to catch Fault Exceptions. Modify the main method in the Program.cs file to catch Fault Exceptions as follows.
namespace AdventureWorksClient { class Program { static void Main(string[] args) { // Create a proxy object and connect to the service AdventureWorksServiceClient proxy = new AdventureWorksServiceClient("BasicHttpBinding_AdventureWorksService"); try { // Obtain a list of all products Console.WriteLine("Test 1: List all products"); string[] productNumbers = proxy.ListProducts(); foreach (string productNumber in productNumbers) { Console.WriteLine("Number: {0}", productNumber); } Console.WriteLine(); Console.WriteLine("Test 2: Display the details of a product"); ProductData product = proxy.GetProduct("SA-M198"); Console.WriteLine("Number: {0}", product.ProductNumber); Console.WriteLine("Name: {0}", product.Name); Console.WriteLine("Color: {0}", product.Color); Console.WriteLine("Price: {0}", product.ListPrice); Console.WriteLine(); // Disconnect from the service proxy.Close(); } catch (FaultException<LogicFault> sf) { Console.WriteLine("SystemFault {0}: {1}\n", sf.Detail.Message,sf.Detail.Reason); } catch (FaultException<DatabaseFault> dbf) { Console.WriteLine("DatabaseFault {0}: {1}\n", dbf.Detail.Message, dbf.Detail.Reason); } Console.WriteLine("Press ENTER to finish"); Console.ReadLine(); } } }
Build solution, set client’s project as the startup and run it. Is should run without errors. In order to test the Database FaultException, simply change the connection string element in the Web.config file, of the AdventureWorkService website hosted in IIS. Note that you should change the file in the “C:\inetpub\wwwroot\AdventureWorksService” location, not the one in you Visual Studio! Change the Initial Catalog attribute to point to a fake database that it doesn’t exist. (If IIS doesn’t allow you to change the attribute because apparently it’s being used at the moment, just cut the Web.config file, paste it in your desktop folder, make the above change and put it back where it was.
initial catalog=failDB;
Try to run again the client project. You should take a message like “System cannot open database “failDb” requested by login”. A DatabaseFault exception has been caught.
Change again the Web.config file and make it to point the right database. You can test the LogicFault exception by modifying the AdventureWorksService.cs file, where the implementation of the service exist. Simply modify the first line of the ListProducts() operation by assigning the ProductList to null. Also later, try to clear the list items. The clear method, should fail (list is null) and an Exception will raise. This time though, it isn’t an Sql.Exception so a LogicFault exception will be through.
public List<string> ListProducts() { // Create a list for holding product numbers List<string> productsList = null; try { // Connect to the AdventureWorks database by using the Entity Framework using (AdventureWorks2012Entities database = new AdventureWorks2012Entities()) { // Fetch the product number of every product in the database var products = from product in database.Products select product.ProductNumber; productsList.Clear(); productsList = products.ToList(); } } //Other code omitted
Build web site, re-publish it in IIS and run the client project again. This time, you should get a LogicFault exception.
Categories: WCF
Leave a Reply