Adding error handling to the service

In the previous sections, when we were trying to retrieve a product but the product ID passed in was not a valid one, we just threw an exception. Exceptions are technology-specific and, therefore, are not suitable for crossing the service boundary of SOA compliant services. All exceptions generate a fault on the communication channel, resulting in unhappy proxies, as a recover and retry is not possible. Thus, for WCF services, we should not throw normal exceptions.

What we need are SOAP faults that meet industry standards for seamless interoperability.

In the service interface layer, operations that may throw a FaultExceptions must be decorated with one or more FaultContract attributes, defining the exact FaultException.

On the other hand, the service consumer should catch specific FaultExceptions to be in a position to handle the specified exceptions.

Adding a fault contract

We will now change the exception in the GetProduct operation to a FaultContract.

But before we implement our first FaultContract, we need to modify the App.config file in the RealNorthwindService project. We will change the setting includeExceptionDetailInFaults back to False, so that every unhandled, non-Fault exception will be a violation. Client applications won't know the details of those exceptions.

Note

You can definitely set includeExceptionDetailInFaults to True when debugging, as this will be very helpful in diagnosing problems during the development stage. But in production, it should always be set to False.

So, open the App.config file in the RealNorthwindService project, change includeExceptionDetailInFaults from True to False, and save it.

Next, we will define the FaultContract. For simplicity, we will define only one FaultContract, and leave it inside the file IProductService.cs, although in a real system you can have as many Fault Contracts as you want, and they should also normally be in their own files.

The FaultContract will be as follows:

[DataContract]
public class ProductFault
{
public ProductFault(string msg)
{
FaultMessage = msg;
}
[DataMember]
public string FaultMessage;
}

We then decorate the service operation GetProduct with the following attribute:

[FaultContract(typeof(ProductFault))]

This is to tell the service consumers that this operation may throw a fault of the type ProductFault.

The content of IProductService.cs should now be:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace MyWCFServices.RealNorthwindService
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
[ServiceContract]
public interface IProductService
{
[OperationContract]
[FaultContract(typeof(ProductFault))]
Product GetProduct(int id);
[OperationContract]
bool UpdateProduct(Product product);
// TODO: Add your service operations here
}
[DataContract]
public class Product
{
[DataMember]
public int ProductID;
error handling, adding to servicefault contract, adding[DataMember]
public string ProductName;
[DataMember]
public string QuantityPerUnit;
[DataMember]
public decimal UnitPrice;
[DataMember]
public bool Discontinued;
}
[DataContract]
public class ProductFault
{
public ProductFault(string msg)
{
FaultMessage = msg;
}
[DataMember]
public string FaultMessage;
}
}

Throwing a fault exception

Once we have modified the interface, we need to modify the implementation. Open the ProductService.cs file, and change the following lines:

if (productEntity == null)
throw new Exception("No product found with id " + id);

to these lines:

if (productEntity == null)
{
//throw new Exception("No product found with id " + id);
if (id != 999)
throw new FaultException<ProductFault>(new ProductFault(
"No product found with id " + id), "Product Fault");
else
throw new Exception("Test Exception");
}

This will throw a ProductFault exception if an invalid ID is passed to the GetProduct operation. However, we will throw a normal C# exception if the passed ID is 999. Later, we will use this special ID to do an extra test.

Now, build the RealNorthwindService project. After has been successfully built, we will use the client that we built earlier to test this service. We will examine the channel status after an exception has been thrown. We can't do this with the WCF Service Test Client, because in WCF Test Client, each request will create a new channel, and we don't have a way to examine the channel state after the service call.

Updating client program to catch the fault exception

Now, let's update the client program, so that the fault exception is handled.

  1. First, we need to update the service reference, because we have changed the contracts for the service. From the RealNorthwindClient project, expand the Service References node and right-click on ProductServiceRef. Select Update Service Reference from the context menu, and the Updating Service Reference dialog box will pop up. The WCF Service Host will be started automatically, and the updated metadata information will be downloaded to the client side. Proxy code will be updated with modified and new service contracts.
    Updating client program to catch the fault exception
  2. Then, open Program.cs under RealNorthwindClient project, and add the following method to the class Program:
    static void TestException(ProductServiceClient client, int id)
    error handling, adding to serviceclient program, updating{
    Console.WriteLine("
    
    Test {0} Fault Exception for product id {1}...", (id != 999)?"handled":"unhandled", id);
    try
    {
    Product product = client.GetProduct(id);
    }
    catch (TimeoutException ex)
    {
    Console.WriteLine("The service operation timed out. " + ex.Message);
    }
    catch (FaultException<ProductFault> ex)
    {
    Console.WriteLine("ProductFault: " + ex.ToString());
    }
    catch (FaultException ex)
    {
    Console.WriteLine("Unknown Fault: " + ex.ToString());
    }
    catch (CommunicationException ex)
    {
    Console.WriteLine("There was a communication problem. " + ex.Message + ex.StackTrace);
    }
    Console.WriteLine("
    
    Channel Status after the exception: " + client.InnerChannel.State.ToString());
    Console.WriteLine("Press any key to continue ...");
    Console.ReadKey();
    }
    
    • Inside this method, we first call GetProduct with a passed-in ID. If the ID is an invalid product ID, the service will throw a ProductFault exception. So we have to add the catch statement to catch the ProductFault exception. We examine the channel status after the fault exception. We have also added several other exceptions, such as timeout exception, communication exception, and general fault exception, so that we can handle every situation. Note that the order of the catch statements are very important and shouldn't be changed.

      If 999 is passed to this method as the ID, the service will throw an exception, instead of a fault exception. We will also examine the channel status of this unhandled exception.

  3. Now, add the following statements to the end of the function Main in this class:
TestException(client, 0); // channel is still open after a FaultException
TestException(client, 999); // channel is Faulted after a non handled fault exception
Console.WriteLine("

Test Faulted client ...");
product = client.GetProduct(20); // can't use a client with a Faulted channel
Console.WriteLine("Press any key to continue ...");
Console.ReadLine();
  • So we will first test the ProductFault exception, followed by the regular C# exception, and finally we will try to use the faulted channel.

The full content of the Program.cs is now as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RealNorthwindClient.ProductServiceRef;
using System.ServiceModel;
namespace RealNorthwindClient
{
class Program
{
static void Main(string[] args)
{
ProductServiceClient client = new ProductServiceClient();
Product product = client.GetProduct(23);
Console.WriteLine("product name is " + product. ProductName);
Console.WriteLine("product price is " + product.UnitPrice. ToString());
product.UnitPrice = (decimal)20.0;
bool result = client.UpdateProduct(product);
Console.WriteLine("Update result is " + result. ToString());
TestException(client, 0); // channel is still open after a FaultException
TestException(client, 999); // channel is Faulted after a non handled fault exception
Console.WriteLine("

Test Faulted client ...");
product = client.GetProduct(20); // can't use a client with a Faulted channel
Console.WriteLine("Press any key to continue ...");
Console.ReadLine();
}
static void TestException(ProductServiceClient client, int id)
{
Console.WriteLine("

Test {0} Fault Exception for product id {1}...", (id != 999)?"handled":"unhandled", id);
try
{
Product product = client.GetProduct(id);
}
catch (TimeoutException ex)
{
Console.WriteLine("The service operation timed out. " + ex.Message);
}
catch (FaultException<ProductFault> ex)
{
Console.WriteLine("ProductFault: " + ex.ToString());
}
catch (FaultException ex)
{
Console.WriteLine("Unknown Fault: " + ex.ToString());
}
catch (CommunicationException ex)
{
Console.WriteLine("There was a communication problem. " + ex.Message + ex.StackTrace);
}
Console.WriteLine("

Channel Status after the exception: " + client.InnerChannel.State.ToString());
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();
}
}
}

Disabling the Just My Code debugging option

Before we run the program, we need to change a debugging setting. Select menu option Tools | Options, go to the Debugging | General tab, deselect the Enable Just My Code(Managed only) checkbox, as shown in the Options image below:

Disabling the Just My Code debugging optionerror handling, adding to serviceclient program, updating

Enable Just My Code means that while debugging, you look at only the code you have written, and ignore the third-party code that is inside your application (such as the framework and libraries). Just My Code hides non-user code so that it does not appear in the debugger windows. When you step through the code, the debugger steps through any non-user code but does not stop in it. For example, if you call a .NET Framework API that throws an exception, you're going to break in your code that called the API, rather than farther down in the framework. This becomes particularly useful when user and non-user code call back and forth between each other. You can set the My Code status on a per-function level, to specify whether you want certain code debugged.

In our example, if you don't deselect Enable Just My Code, that is, if Just Debug My Code is selected, what happens is that when you debug the client program, you will get an exception popped up in Visual Studio after:

throw new FaultException<ProductFault>(new ProductFault("No product found with id " + id), "Product Fault");

It complains that the exception is not being handled by the user, as seen in the RealNorthwind (Debugging) - Microsoft Visual Studio image below:

Disabling the Just My Code debugging optionerror handling, adding to serviceclient program, updating

Note that this exception window is pointing to the following statement in Visual Studio:

throw new Exception("Test Exception");

But it is not because this line raised an exception. It is because this line is the line after the one that raised the exception, which is the throw new FaultException line. Actually, if you press F5 to continue, the next time the "throw new Exception" line raises an exception, the popped up window will point to:

Product product = new Product();

We know that the ProductFault exception will be handled by our client program, but now, Visual Studio thinks that it is not. To avoid this annoyance, you can disable the Just My Code option. However, you should be aware that disabling this option might have some side effects. For example, Visual Studio will not complain if there is a real unhandled exception. You may want to enable it after you have completed your testing.

Testing the fault exception

Once you have changed the Just My Code option, you can press F5 to run the client program (remember to set the RealNorthwindClient to be the startup project). You will get the output shown in the following screenshot:

Testing the fault exceptionerror handling, adding to servicejust my code debugging option, disabling

As you can see from the output, the client channel to the service is still open, after the ProductFault is handled in the client program. Next, we will use the same client to get the product details for ID 999.

Press Enter, and more output will be shown, with a fault exception as shown in the image here:

Testing the fault exceptionerror handling, adding to servicejust my code debugging option, disabling

From the output, we know that the channel has now faulted. This means that now the client does not have a valid way to communicate with the service. To prove it, press Enter to try to connect to the service using the same client object, and you will get an unhandled exception "The communication object, System.ServiceModel.Channels.ServiceChannel", cannot be used for communication because it is in the Faulted state", as shown in the RealNorthwind (Debugging) image, below. The program will not continue, so you have to stop it.

Testing the fault exceptionerror handling, adding to servicejust my code debugging option, disabling

In the source code, if we have to call the service again, we have to abort this client, and create a new one for the communication.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset