Adding Database Support and Exception Handling - WCF Multi-layer Services Development with Entity Framework Fourth Edition (2014)

WCF Multi-layer Services Development with Entity Framework Fourth Edition (2014)

Chapter 6. Adding Database Support and Exception Handling

In the previous chapter, we created a WCF service with two layers. We didn't add the third layer, that is, the data access layer. Therefore, all of the service operations just returned a fake result from the business logic layer.

In this chapter, we will add the third layer to the WCF service. We will also introduce fault contracts for service error handling.

We will accomplish the following tasks in this chapter:

· Creating the data access layer project

· Calling the data access layer from the business logic layer

· Preparing the Northwind database for the service

· Adding the connection string to the configuration file

· Querying the database using GetProduct

· Testing the GetProduct method

· Updating the database using UpdateProduct

· Testing the UpdateProduct method

· Adding a fault contract to the service

· Throwing a fault contract exception to the client

· Updating the client program to catch the fault exception

· Changing exception options

· Testing the service fault contract

Adding a data access layer

We have two layers, the service interface layer and the business logic layer, in our solution now. We don't have any code to interact with a data store to manipulate the underlying data. As said in the previous chapter, such code should be separated from these two layers, so now we will add one more layer, the data access layer, for this purpose. Within this third layer, we will query a real database to get the product information and update the database for a given product.

Creating the data access layer project

First, we will create the project for the data access layer. As we did for the business logic layer, what we need to do is add a C# class library project named LayerNorthwindDAL (where DAL stands for Data Access Layer) to the solution. Then, we need to change the default class file to be our Data Access Object (DAO) file.

Now, modify the Class1.cs file as follows:

1. Rename it to ProductDAO.cs. This will also rename the class name and all related places in the project.

2. Add a LayerNorthwindBDO reference to the project.

Next, let's modify ProductDAO.cs for our product service:

1. Add the following using statement:

using LayerNorthwindBDO;

2. Add two new methods to the ProductDAO class. The first method is GetProduct, which should be as follows:

3. public ProductBDO GetProduct(int id)

4. {

5. // TODO: connect to DB to retrieve product

6. var p = new ProductBDO();

7. p.ProductID = id;

8. p.ProductName =

9. "fake product name from data access layer";

10. p.UnitPrice = 30.00m;

11. p.QuantityPerUnit = "fake QPU";

12. return p;

}

In this method, all the product information is still hardcoded though we have changed the product name to be specific to the data access layer. We will soon modify this method to retrieve the actual product information from a real Northwind database.

3. Add the second method, UpdateProduct, as shown in the following code snippet:

4. public bool UpdateProduct(ProductBDO product, ref string message)

5. {

6. // TODO: connect to DB to update product

7. message = "product updated successfully";

8. return true;

}

Again, we didn't update any database in this method. We will also modify this method soon to update to the real Northwind database.

The content of the ProductDAO.cs file should now be as follows:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using LayerNorthwindBDO;

namespace LayerNorthwindDAL

{

public class ProductDAO

{

public ProductBDO GetProduct(int id)

{

// TODO: connect to DB to retrieve product

var p = new ProductBDO();

p.ProductID = id;

p.ProductName =

"fake product name from data access layer";

p.UnitPrice = 30.00m;

p.QuantityPerUnit = "fake QPU";

return p;

}

public bool UpdateProduct(ProductBDO product,ref string message)

{

// TODO: connect to DB to update product

message = "product updated successfully";

return true;

}

}

}

Calling the data access layer from the business logic layer

Before we modify these two methods to interact with a real database, we will first modify the business logic layer to call them so that we know that the three-layer framework is working:

1. Add a reference of this new layer to the business logic layer project. From Solution Explorer, just right-click on the LayerNorthwindLogic project item, select Add | Reference… from the context menu, select LayerNorthwindDAL from Projects under the Solutions tab, and then click on the OK button.

2. Open the ProductLogic.cs file under the LayerNorthwindLogic project and add a using statement:

using LayerNorthwindDAL;

3. Add a new class member:

ProductDAO productDAO = new ProductDAO();

4. Modify the GetProduct method to contain only the following line:

return productDAO.GetProduct(id);

We will use the data access layer to retrieve the product information. At this point, we will not add any business logic to this method.

5. Modify the last two lines of the UpdateProduct method to call the data access layer. The method call should look as follows:

return productDAO.UpdateProduct(product, ref message);

In this method, we replaced the last return statement to call the data access layer method, UpdateProduct. This means that all of the business logic is still enclosed in the business logic layer and the data access layer should be used only to update the product in the database.

Here is the full content of the ProductLogic.cs file:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using LayerNorthwindBDO;

using LayerNorthwindDAL;

namespace LayerNorthwindLogic

{

public class ProductLogic

{

ProductDAO productDAO = new ProductDAO();

public ProductBDO GetProduct(int id)

{

return productDAO.GetProduct(id);

}

public bool UpdateProduct(ProductBDO

product, ref string message)

{

var productInDB =

GetProduct(product.ProductID);

// invalid product to update

if (productInDB == null)

{

message = "cannot get product for this ID";

return false;

}

// a product can't be discontinued

// if there are non-fulfilled orders

if (product.Discontinued == true

&& productInDB.UnitsOnOrder > 0)

{

message = "cannot discontinue this product";

return false;

}

else

{

return productDAO.UpdateProduct(product,

ref message);

}

}

}

}

If you run the program and test it using the WCF Test Client, you will get exactly the same result as before, although now it is a three-layer application and you will see a different, but obviously still fake, product name.

Preparing the database

As we have the three-layer framework ready, we will now implement the data access layer to actually communicate with a real database.

In this book, we will use Microsoft's sample Northwind database. This database is not installed by default in SQL Server, so we need to install it first (you can use any version of Microsoft SQL Server, but in this book, we will use SQL Server 2014):

1. Download the database package. Just search for Northwind Sample Databases download on the Internet or go to http://www.microsoft.com/download/en/details.aspx?id=23654.

Note

This sample database was designed for SQL Server 2000, but it can also be used with more recent versions of SQL Server.

2. Install (extract) the package to C:\SQL Server 2000 Sample Databases.

3. Install the Northwind database. You can attach the database files to your SQL Server or run the scripts to create the databases. For detailed instructions, you can have a look at the following link: http://msdn.microsoft.com/en-us/library/8b6y4c7s.aspx.

Adding the connection string to the configuration file

Now that we have the Northwind database installed, we will modify our data access layer to use this actual database. At this point, we will use a raw SqlClient adapter to do the database work. We will replace this layer with LINQ to Entities in a later chapter.

Before we start coding, we need to add a connection string to the configuration file. We don't want to hardcode the connection string in our project. Instead, we will set it in the App.config file so that it can be changed on the fly.

You can open the App.config file under the LayerNorthwindService project and add a new configuration node, connectionStrings, as a child node of the root configuration node, configuration. You can choose Windows integrated security or SQL Server login as the security mode for your connection.

The following code snippet shows my connection string with the integrated security class (note that the add node should be in one line in Visual Studio; we have broken them into three lines just for printing purposes):

<connectionStrings>

<add name ="NorthwindConnectionString"

connectionString="server=localhost;Integrated Security=SSPI;database=Northwind" />

</connectionStrings>

Querying the database using GetProduct

As we have added the connection string as a new key to the configuration file, we need to retrieve this key in the DAO class so that we can use it when we want to connect to the database. Follow these steps to get and use this new key from within the DAO class:

1. Add a reference to System.Configuration from the framework to the LayerNorthwindDAL project. We need this reference to read the connection string in the configuration file.

2. Open the ProductDAO.cs file in the LayerNorthwindDAL project and first add the following two using statements:

3. using System.Data.SqlClient;

using System.Configuration;

3. Add a new class member to the ProductDAO class:

4. string connectionString = ConfigurationManager.

5. ConnectionStrings["NorthwindConnectionString"].

ConnectionString;

We will use this connection string to connect to the Northwind database for both the GetProduct and UpdateProduct methods.

4. Modify the GetProduct method to get the product from the database as follows:

5. public ProductBDO GetProduct(int id)

6. {

7. ProductBDO p = null;

8. using (SqlConnection conn =

9. new SqlConnection(connectionString))

10. {

11. using (SqlCommand cmd = new SqlCommand())

12. {

13. cmd.CommandText =

14. "select * from Products where ProductID=@id";

15. cmd.Parameters.AddWithValue("@id", id);

16. cmd.Connection = conn;

17. conn.Open();

18. using (SqlDataReader reader = cmd.ExecuteReader())

19. {

20. if (reader.HasRows)

21. {

22. reader.Read();

23. p = new ProductBDO();

24. p.ProductID = id;

25. p.ProductName =

26. (string)reader["ProductName"];

27. p.QuantityPerUnit =

28. (string)reader["QuantityPerUnit"];

29. p.UnitPrice =

30. (decimal)reader["UnitPrice"];

31. p.UnitsInStock =

32. (short)reader["UnitsInStock"];

33. p.UnitsOnOrder =

34. (short)reader["UnitsOnOrder"];

35. p.ReorderLevel =

36. (short)reader["ReorderLevel"];

37. p.Discontinued =

38. (bool)reader["Discontinued"];

39. }

40. }

41. }

42. }

43. return p;

}

In this method, we first create a SQL connection (SqlConnection) to the Northwind database and then issue a SQL query to get the product details for the given product ID. Remember that we are going to change this ADO.NET code to LINQ to Entities in a later chapter.

Testing the GetProduct method

If you now set LayerNorthwindService as the startup project and run the application, you can get the actual product information from the database, as seen in the following screenshot:

Testing the GetProduct method

If you get an error screen, it is probably because you have set your connection string incorrectly. Double-check the new connection string node in your App.config file and try again until you can connect to your database.

If your connection string is correct, double-check the column names to make sure that they are not misspelled. You might also need to adjust your connection string depending on the way you are connecting to the database.

Instead of the connection error message, you might see the following error message:

Testing the GetProduct method

This error will occur when you try to get the product information for a product with a product ID of 0. The error message does not give much detail about what went wrong here because we did not let the server reveal the details of any error. Let's follow the instructions in the error message to change the setting includeExceptionDetailInFaults to True in the App.config file and run it again. Now, you will see that the error detail has changed to Object reference not set to an instance of an object.

A little investigation will tell us that there is a bug in our ProductService class. Inside the ProductService.GetProduct method, after we call the business logic layer to get the product detail for an ID, we will get a null product if the ID is not a valid product ID in the database. When we pass this null object to the next method (TranslateProductBDOToProductDTO), we get the error message shown in the preceding screenshot. Actually, this will happen whenever you enter a product ID outside the range of 1 to 77. This is because, in the sample Northwind database, there are only 77 products, with product IDs ranging from 1 to 77. To fix this problem, we can add the following statement inside the GetProduct method in the ProductService class right after the call to the business logic layer:

if (productBDO == null)

throw new Exception("No product found for id " + id);

In the ProductService.cs file, the GetProduct method will now be as follows:

public Product GetProduct(int id)

{

var productBDO = productLogic.GetProduct(id);

if (productBDO == null)

throw new Exception("No product found for id " + id);

var product = new Product();

TranslateProductBDOToProductDTO(productBDO, product);

return product;

}

For now, we will raise an exception if an invalid product ID is entered. Later, we will convert this exception to a FaultContract type so that the caller will be able to catch and process the fault properly.

Now run the application again, and if you enter an invalid product ID, say 0, you will get an error message No product found for id 0. This is much clearer than the previous Object reference not set to an instance of an object error message.

Updating the database using UpdateProduct

Next, we will modify the UpdateProduct method to update the product record in the database. The UpdateProduct method in the LayerNorthwindDAL project should be modified as follows:

public bool UpdateProduct(ProductBDO product,

ref string message)

{

message = "product updated successfully";

var ret = true;

using (SqlConnection conn =

new SqlConnection(connectionString))

{

var cmdStr = @"UPDATE products

SET ProductName=@name,

QuantityPerUnit=@unit,

UnitPrice=@price,

Discontinued=@discontinued

WHERE ProductID=@id";

using (SqlCommand cmd = new SqlCommand(cmdStr, conn))

{

cmd.Parameters.AddWithValue("@name",

product.ProductName);

cmd.Parameters.AddWithValue("@unit",

product.QuantityPerUnit);

cmd.Parameters.AddWithValue("@price",

product.UnitPrice);

cmd.Parameters.AddWithValue("@discontinued",

product.Discontinued);

cmd.Parameters.AddWithValue("@id",

product.ProductID);

conn.Open();

if (cmd.ExecuteNonQuery() != 1)

{

message = "no product was updated";

ret = false;

}

}

}

return ret;

}

Inside this method, we have used parameters to specify arguments to the update command. This is a good practice because it will prevent SQL Injection attacks, as the SQL statement is precompiled instead of being dynamically built.

Testing the UpdateProduct method

We can follow these steps to test the UpdateProduct method:

1. Start the WCF service by pressing Ctrl + F5 or just F5. The WCF Test Client should also start automatically.

2. Double-click on the UpdateProduct() operation in the WCF Test Client.

3. Select LayerNorthwindService.Product from the product's dropdown, then enter a valid product ID, name, price, and quantity per unit.

4. Click on Invoke.

You should get a True response. To check this, just go to the GetProduct() page, enter the same product ID, click on Invoke, and you will see that all of your updates have been saved to the database.

The content of the ProductDAO.cs file is now as follows:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using LayerNorthwindBDO;

using System.Data.SqlClient;

using System.Configuration;

namespace LayerNorthwindDAL

{

public class ProductDAO

{

//Note put following three lines in one line in your code

string connectionString = ConfigurationManager.

ConnectionStrings["NorthwindConnectionString"].

ConnectionString;

public ProductBDO GetProduct(int id)

{

ProductBDO p = null;

using (SqlConnection conn =

new SqlConnection(connectionString))

{

using(SqlCommand cmd = new SqlCommand())

{

cmd.CommandText =

"select * from Products where ProductID=@id";

cmd.Parameters.AddWithValue("@id", id);

cmd.Connection = conn;

conn.Open();

using(SqlDataReader reader = cmd.ExecuteReader())

{

if (reader.HasRows)

{

reader.Read();

p = new ProductBDO();

p.ProductID = id;

p.ProductName =

(string)reader["ProductName"];

p.QuantityPerUnit =

(string)reader["QuantityPerUnit"];

p.UnitPrice =

(decimal)reader["UnitPrice"];

p.UnitsInStock =

(short)reader["UnitsInStock"];

p.UnitsOnOrder =

(short)reader["UnitsOnOrder"];

p.ReorderLevel =

(short)reader["ReorderLevel"];

p.Discontinued =

(bool)reader["Discontinued"];

}

}

}

}

return p;

}

public bool UpdateProduct(ProductBDO product,

ref string message)

{

message = "product updated successfully";

var ret = true;

using (SqlConnection conn =

new SqlConnection(connectionString))

{

var cmdStr = @"UPDATE products

SET ProductName=@name,

QuantityPerUnit=@unit,

UnitPrice=@price,

Discontinued=@discontinued

WHERE ProductID=@id";

using(SqlCommand cmd = new SqlCommand(cmdStr, conn))

{

cmd.Parameters.AddWithValue("@name",

product.ProductName);

cmd.Parameters.AddWithValue("@unit",

product.QuantityPerUnit);

cmd.Parameters.AddWithValue("@price",

product.UnitPrice);

cmd.Parameters.AddWithValue("@discontinued",

product.Discontinued);

cmd.Parameters.AddWithValue("@id",

product.ProductID);

conn.Open();

if (cmd.ExecuteNonQuery() != 1)

{

message = "no product is updated";

ret = false;

}

}

}

return ret;

}

}

}

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. Thus, for WCF services, we should not throw normal exceptions.

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

The service interface layer operations that might throw fault exceptions must be decorated with one or more FaultContract attributes, defining the exact fault exception.

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

Adding a fault contract

We will now wrap the exception in the GetProduct operation with a FaultContract type.

Before we implement our first FaultContract type, we need to modify the App.config file in the LayerNorthwindService project. We will change the includeExceptionDetailInFaults setting back to False so that every unhandled, faultless exception will be a violation. In this way, client applications won't know the details of those exceptions so that the service technology and the hosting environment won't be revealed to them.

Note

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

Open the App.config file in the LayerNorthwindService project, change includeExceptionDetailInFaults from True to False, and save it.

Next, we will define a FaultContract type. For simplicity, we will define only one FaultContract type and leave it inside the IProductService.cs file, 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. TheProductFault fault contract is defined as follows:

[DataContract]

public class ProductFault

{

public ProductFault(string msg)

{

FaultMessage = msg;

}

[DataMember]

public string FaultMessage;

}

We then decorate the service operations, GetProduct and UpdateProduct, with the following attribute:

[FaultContract(typeof(ProductFault))]

This is to tell the service consumers that these operations might throw a fault of the type, ProductFault.

The content of IProductService.cs should now be as follows:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

namespace LayerNorthwindService

{

[ServiceContract]

public interface IProductService

{

[OperationContract]

[FaultContract(typeof(ProductFault))]

Product GetProduct(int id);

[OperationContract]

[FaultContract(typeof(ProductFault))]

bool UpdateProduct(Product product, ref string message);

}

[DataContract]

public class Product

{

[DataMember]

public int ProductID;

[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 contract exception

Once we have modified the interface, we need to modify the implementation. Open the ProductService.cs file and change GetProduct to be as follows:

public Product GetProduct(int id)

{

ProductBDO productBDO = null;

try

{

productBDO = productLogic.GetProduct(id);

}

catch (Exception e)

{

var msg = e.Message;

var reason = "GetProduct Exception";

throw new FaultException<ProductFault>

(new ProductFault(msg), reason);

}

if (productBDO == null)

{

var msg =

string.Format("No product found for id {0}",

id);

var reason = "GetProduct Empty Product";

if (id == 999)

{

throw new Exception(msg);

}

else

{

throw new FaultException<ProductFault>

(new ProductFault(msg), reason);

}

}

var product = new Product();

TranslateProductBDOToProductDTO(productBDO, product);

return product;

}

In this modified method, we have wrapped the call to the business logic method, hence to the data access layer method, in a try-catch block. If anything goes wrong when the product is being retrieved from the database, such as the login is invalid, the database server is down, or the network is broken, we will throw a fault exception, so the client will be able to catch the exception and display a proper message to the end user, should they wish to.

We will also throw a ProductFault exception if an invalid ID is passed into 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 compare a normal C# exception with a fault exception.

For the UpdateProduct method, we need to do the same thing as with the GetProduct method, that is, wrapping the call to the business logic method within a try-catch block. In a sum, consider the following code in the UpdateProduct method:

return productLogic.UpdateProduct(

productBDO, ref message);

Change the preceding code to look like the following code:

try

{

result = productLogic.UpdateProduct(

productBDO, ref message);

}

catch (Exception e)

{

var msg = e.Message;

var reason = "UpdateProduct Exception";

throw new FaultException<ProductFault>

(new ProductFault(msg), reason);

}

Now, we build the LayerNorthwindService project. After it has been successfully built, we will use the client that we built earlier to test this service. We will examine the fault message and the exception details after a normal exception and a fault exception have been thrown.

Updating the 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 LayerNorthwindClient 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 start automatically, and the updated metadata information will be downloaded to the client side. The proxy code will be updated with modified and new service contracts.

2. Then, open Program.cs under the LayerNorthwindClient project and add the following using statement:

using System.ServiceModel;

3. Add the following method to the Program class:

4. static void TestException(ProductServiceClient client,

5. int id)

6. {

7. if (id != 999)

8. Console.WriteLine("\n\nTest Fault Exception");

9. else

10. Console.WriteLine("\n\nTest normal Exception");

11.

12. try

13. {

14. var product = client.GetProduct(id);

15. }

16. catch (TimeoutException ex)

17. {

18. Console.WriteLine("Timeout exception");

19. }

20. catch (FaultException<ProductFault> ex)

21. {

22. Console.WriteLine("ProductFault.");

23. Console.WriteLine("\tFault reason: " +

24. ex.Reason);

25. Console.WriteLine("\tFault message: " +

26. ex.Detail.FaultMessage);

27. }

28. catch (FaultException ex)

29. {

30. Console.WriteLine("Unknown Fault");

31. Console.WriteLine(ex.Message);

32. }

33. catch (CommunicationException ex)

34. {

35. Console.WriteLine("Communication exception");

36. }

37. catch (Exception ex)

38. {

39. Console.WriteLine("Unknown exception");

40. }

}

4. Inside this method, we first call GetProduct with an ID as the argument. 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 will print the reason and the message of 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 is very important and should not be changed.

5. If 999 is passed to this method as the ID, the service will throw a normal C# exception instead of a fault exception. As you can see, with this exception we don't have a way to find the reason and details of the exception, so we will just print out the raw exception message.

6. Now, add the following statements to the end of the Main function in this class:

7. // FaultException

8. TestException(client, 0);

9. // regular C# exception

TestException(client, 999);

So, we will first test the ProductFault exception and then the regular C# exception.

7. Finally, set the solution to start with the projects, LayerNorthwindService and LayerNorthwindClient (make sure that the service project is started before the client project).

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

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using LayerNorthwindClient.ProductServiceRef;

using System.ServiceModel;

namespace LayerNorthwindClient

{

class Program

{

static void Main(string[] args)

{

var client = new ProductServiceClient();

var product = client.GetProduct(23);

Console.WriteLine("product name is " +

product.ProductName);

Console.WriteLine("product price is " +

product.UnitPrice.ToString());

product.UnitPrice = 20.0m;

var message = "";

var result = client.UpdateProduct(product,

ref message);

Console.WriteLine("Update result is " +

result.ToString());

Console.WriteLine("Update message is " +

message);

// FaultException

TestException(client, 0);

// regular C# exception

TestException(client, 999);

Console.WriteLine("Press any key to continue ...");

Console.ReadLine();

}

static void TestException(ProductServiceClient client,

int id)

{

if (id != 999)

Console.WriteLine("\n\nTest Fault Exception");

else

Console.WriteLine("\n\nTest normal Exception");

try

{

var product = client.GetProduct(id);

}

catch (TimeoutException ex)

{

Console.WriteLine("Timeout exception");

}

catch (FaultException<ProductFault> ex)

{

Console.WriteLine("ProductFault. ");

Console.WriteLine("\tFault reason:" +

ex.Reason);

Console.WriteLine("\tFault message:" +

ex.Detail.FaultMessage);

}

catch (FaultException ex)

{

Console.WriteLine("Unknown Fault");

Console.WriteLine(ex.Message);

}

catch (CommunicationException ex)

{

Console.WriteLine("Communication exception");

}

catch (Exception ex)

{

Console.WriteLine("Unknown exception");

}

}

}

}

Changing the exception options

We can test the program now. However, if you run the program in the debugging mode, it will break right after the exception is thrown inside the service code, with an error message An exception of type 'System.ServiceModel.FaultException'1' occurred in LayerNorthwindService.dll but was not handled in user code. Your screen will look like this:

Changing the exception options

You can press F5 to continue your testing but this will happen every time when you debug this program. To avoid this, you can first make sure that the debugging option, Enable Just My Code (TOOLS | Options | Debugging), is checked. Then, change the exception option,System.ServiceModel.FaultException'1 (DEBUG | Exceptions | Common Language Runtime Exceptions | System.ServiceModel), to not break when it is User-unhandled (uncheck this option). The following is the screenshot to set the exception option:

Changing the exception options

Testing the fault exception

Now, you can run the client program to test the fault exceptions (remember to set LayerNorthwindService and LayerNorthwindClient to be the startup projects). You will get the output shown in the following screenshot:

Testing the fault exception

As you can see from the output, the client has the full details of the customized fault exception, such as fault reason and fault message. On the other hand, for the regular C# exception, the client does not have the fault details, except the raw fault message, which is a generic error message to all unhandled service faults.

If you turn on the flag, like the generic fault error message suggested, the client will be able to get the exception messages from the service side, but meanwhile, a lot of other details will also be revealed, such as the call stack and language specification of the service. As we said earlier, this is against the SOA principal, and it is not recommended for production.

Summary

In this chapter, we added the third layer, the data access layer, to LayerNorthwindService. We have also added exception handling to the service. Now, we have finished implementing the three-layer WCF service, according to the WCF service development best practices.

In the next chapters, we will learn LINQ and Entity Framework and then apply LINQ to Entities to our WCF service.