Implementing a Three-layer WCF Service - WCF Multi-layer Services Development with Entity Framework Fourth Edition (2014)

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

Chapter 5. Implementing a Three-layer WCF Service

In the previous chapters, we created a basic WCF service. The WCF service we have created, HelloWorldService, has only one method, named GetMessage. As this was just an example, we implemented this WCF service in one layer only. Both the service interface and implementation are within one deployable component.

In the following two chapters, we will implement a WCF service, which will be called LayerNorthwindService, to reflect a real-world service. In this chapter, we will separate the service interface layer from the business logic layer, and in the following chapter, we will add a data access layer to the service.

Note that the service we will create in the next two chapters is only a simplified version of a real-world WCF service. In a real-world situation, there is no doubt that the WCF service will contain more custom scenarios, business logic, and data constraints. For learning purposes, we will just create a WCF service in three layers, with minimum business logic and some basic functionality. After you have acquired the basic skills to create the framework for a layered WCF service, you can customize the solution according to your own needs.

In this chapter, we will create and test the WCF service by performing the following steps:

· Creating the WCF service project using the built-in Visual Studio WCF service template

· Creating the service operation contracts

· Creating the data contracts

· Adding a business domain object (BDO) project

· Adding a business logic layer project

· Calling the business logic layer from the service interface layer

· Testing the service

Why layer a service?

An important aspect of Service-oriented Architecture (SOA) design is that service boundaries should be explicit (being technology-neutral or agnostic), which means hiding all the details of the implementation behind the service boundary. This includes not revealing or dictating which particular technology is used.

Furthermore, inside the implementation of a service, the code responsible for the data manipulation should be separated from the code responsible for the business logic. Therefore, in the real world, it is always a good practice to implement a WCF service in three or more layers. The three layers are the service interface layer, the business logic layer, and the data access layer.

· Service interface layer: This layer will include the service contracts and operation contracts used to define the service interfaces that will be exposed at the service boundary. Data contracts are also defined to pass data in and out of the service. Fault contracts are defined if any exceptions are expected to be thrown outside the service.

· Business logic layer: This layer will apply the actual business logic to the service operations. It will check the preconditions of each operation, perform business activities, and return any necessary results to the interface layer of the service.

· Data access layer: This layer will take care of all the tasks needed to access the underlying databases. It will use a specific data adapter to query and update the databases. This layer will handle connections to databases, transaction processing, and concurrency controlling. Neither the service interface layer nor the business logic layer needs to worry about these things.

The service interface layer will be compiled into a separate class assembly and hosted in a service host environment. Only the outside world will know about, and have access to, this layer. Whenever a request is received by the service interface layer on the hosting server, the request will be dispatched to the business logic layer, and the business logic layer will get the actual work done. If any database support is needed by the business logic layer, it will always go through the data access layer.

Creating a new solution and project using the built-in WCF service template

To start with, we will first create a new solution for the layered service and then add a new WCF service project to this solution. This time we will use the built-in Visual Studio WCF template for the new project.

Creating the WCF service project

There are a few built-in WCF service templates within Visual Studio. In this section, we will use the service library template to create our WCF service project. If you are interested in other templates, you can try each one of them yourself and choose the appropriate one for your own developments.

Follow these steps to create the LayerNorthwind solution and the project using the service library template:

1. Start Visual Studio, go to FILE | New | Project…, and you will see the New Project dialog box. Don't open the HelloWorld solution (from the previous chapter) as from this point onwards, we will create a completely new solution and save it in a different location.

2. In the New Project window, specify WCF Service Library as the project template from Visual C# | WCF, LayerNorthwindService as the (project) name, C:\SOAwithWCFandEF\Projects\ as the Location, and change the solution name from the defaultedLayerNorthwindService to LayerNorthwind. Make sure that the Create directory for solution checkbox is selected.

Creating the WCF service project

3. Click on the OK button, and the solution is created with a WCF project inside it. The project already has an IService1.cs file to define a service interface and Service1.cs to implement the service. It also has an App.config file, which we will cover shortly.

Now we have created the WCF service project. This project actually contains an application containing a WCF service, a hosting application (WcfSvcHost), and a WCF Test Client. This means that we don't need to write any other code to host it, and as soon as we have implemented our service, we can use the built-in WCF Test Client to invoke it. This makes it very convenient for WCF development.

Creating the service interface layer

In the previous section, we have created a WCF project using the WCF Service Library template. In this section, we will create the service interface layer contracts.

As two sample files have already been created for us, we will try to reuse them as much as possible. Then, we will start customizing these two files to create the service contracts.

Creating service interfaces

To create the service interfaces, we need to do the following to the IService1.cs file:

1. Change the filename from IService1.cs to IProductService.cs. This will also change the interface name from all related places inside the project.

2. Change the first operation contract definition. Consider the following line:

string GetData(int value);

Change it to this line:

Product GetProduct(int id);

3. Change the second operation contract definition. Refer to the following line:

CompositeType GetDataUsingDataContract(CompositeType composite);

Change it to this line:

bool UpdateProduct(Product product, ref string message);

With these changes, we have defined two service contracts. The first one will be used to get the product details for a specific product ID, while the second one will be used to update a specific product. However, the product type, which we have used to define these service contracts, is still not defined. We will define it right after this section.

The changed part of the service interface should now look 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]

Product GetProduct(int id);

[OperationContract]

bool UpdateProduct(Product product, ref string message);

// TODO: Add your service operations here

}

//Unchanged part omitted

}

This is not the whole content of the IProductService.cs file. The bottom part of this file should still have the CompositeType class, which we will change to our product type in the next section.


Creating data contracts

Another important aspect of the SOA design is that you should not assume that the application supports a complex object model. One part of the service boundary definition is the data contract definition for the complex types that will be passed as operation parameters or return values.

For maximum interoperability and alignment with the SOA principles, you should not pass any .NET-specific types, such as DataSet or Exceptions across the service boundary, as your service might be called by clients who might not understand the .NET-specific types. You should stick to fairly simple data structure objects, with only primitive properties. You can pass objects that have nested complex types, such as Customer with an Order collection. However, you should not make any assumption about the consumer being able to support object-oriented constructs, such as inheritance or base classes for interoperable web services.

In our example, we will create a complex data type to represent a product object. This data contract will have five properties:

· ProductID

· ProductName

· QuantityPerUnit

· UnitPrice

· Discontinued

These will be used to communicate with the client applications. For example, a supplier may call the web service to update the price of a particular product or to mark a product for discontinuation.

It is preferable to put data contracts in separate files within a separate assembly, but to simplify our example, we will put the data contract in the same file as the service contract. We will have to modify the IProductService.cs file, as follows:

1. Open the IProductService.cs file if it is not open.

2. Delete the existing CompositeType class.

3. Add a new DataContract class called Product.

The data contract part of the finished service contract file, IProductService.cs, should now look as follows:

[DataContract]

public class Product

{

[DataMember]

public int ProductID { get; set; }

[DataMember]

public string ProductName { get; set; }

[DataMember]

public string QuantityPerUnit { get; set; }

[DataMember]

public decimal UnitPrice { get; set; }

[DataMember]

public bool Discontinued { get; set; }

}

Implementing the service contracts

To implement the two service interfaces that we defined in the previous section, we need to do the following to the Service1.cs file:

1. Change the filename from Service1.cs to ProductService.cs.

2. Delete the GetData and GetDataUsingDataContract methods.

3. Add the following method to get a product (you can also right-click on the IProductService interface, select Implement Interface to add the skeleton methods, and then customize the methods):

4. public Product GetProduct(int id)

5. {

6. // TODO: call business logic layer to retrieve product

7. var product = new Product();

8. product.ProductID = id;

9. product.ProductName =

10. "fake product name from service layer";

11. product.UnitPrice = 10.0m;

12. product.QuantityPerUnit = "fake QPU";

13. return product;

}

Note

In this method, we created a fake product and returned it to the client. Later, we will remove the hardcoded product from this method and call the business logic to get the real product.

4. Add the following method to update a product:

5. public bool UpdateProduct(Product product,

6. ref string message)

7. {

8. var result = true;

9.

10. // first check to see if it is a valid price

11. if (product.UnitPrice <= 0)

12. {

13. message = "Price cannot be <= 0";

14. result = false;

15. }

16. // ProductName can't be empty

17. else if (string.IsNullOrEmpty(product.ProductName))

18. {

19. message = "Product name cannot be empty";

20. result = false;

21. }

22. // QuantityPerUnit can't be empty

23. else if (string.IsNullOrEmpty(product.QuantityPerUnit))

24. {

25. message = "Quantity cannot be empty";

26. result = false;

27. }

28. else

29. {

30. // TODO: call business logic layer to update product

31. message = "Product updated successfully";

32. result = true;

33. }

34.

35. return result;

}

In the preceding method, we don't update anything. Instead, it always returns a true value if a valid product is passed in. In one of the following sections, we will implement the business logic to update the product and apply some business logic for the update.

Now, we have finished implementing the service interface. The content of the ProductService.cs file should look as follows:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

namespace LayerNorthwindService

{

public class ProductService : IProductService

{

public Product GetProduct(int id)

{

// TODO: call business logic layer to retrieve // product

var product = new Product();

product.ProductID = id;

product.ProductName =

"fake product name from service layer";

product.UnitPrice = 10.0m;

product.QuantityPerUnit = "fake QPU";

return product;

}

public bool UpdateProduct(Product product,

ref string message)

{

var result = true;

// first check to see if it is a valid price

if (product.UnitPrice <= 0)

{

message = "Price cannot be <= 0";

result = false;

}

// ProductName can't be empty

else if (string.IsNullOrEmpty(product.ProductName))

{

message = "Product name cannot be empty";

result = false;

}

// QuantityPerUnit can't be empty

else if (string.IsNullOrEmpty(product.QuantityPerUnit))

{

message = "Quantity cannot be empty";

result = false;

}

else

{

// TODO: call business logic layer to update // product

message = "Product updated successfully";

result = true;

}

return result;

}

}

}

Modifying the App.config file

As we have changed the service name, we have to make appropriate changes to the configuration file.

Follow these steps to change the configuration file:

1. Open the App.config file from the Solution Explorer.

2. Change the Service1 string in the baseaddress node to ProductService.

3. Change the service address port from the default one to 8080. This setting is done for the client application, which we will create soon.

4. Remove the Design_Time_Addresses/ part from the baseAddress of the service.

The content of the App.config file should now look as follows (all the comments are removed):

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<appSettings>

<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />

</appSettings>

<system.web>

<compilation debug="true" />

</system.web>

<system.serviceModel>

<services>

<service name="LayerNorthwindService.ProductService">

<endpoint address="" binding="basicHttpBinding"

contract= "LayerNorthwindService.IProductService">

<identity>

<dns value="localhost"/>

</identity>

</endpoint>

<endpoint address="mex" binding="mexHttpBinding"

contract="IMetadataExchange"/>

<host>

<baseAddresses>

<add baseAddress = "http://localhost:8080/LayerNorthwindService/ProductService/" />

</baseAddresses>

</host>

</service>

</services>

<behaviors>

<serviceBehaviors>

<behavior>

<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>

<serviceDebug includeExceptionDetailInFaults="False" />

</behavior>

</serviceBehaviors>

</behaviors>

</system.serviceModel>

</configuration>

Testing the service using the WCF Test Client

As we are using the WCF Service Library template in this example, we are now ready to test the web service. As we pointed out when creating the project, the service will be hosted in the Visual Studio WCF Service Host environment.

To start the service, press F5 or Ctrl + F5. The WcfSvcHost application will be started, and the WCF Test Client will also start. This is a Visual Studio built-in test client for the WCF Service Library projects.

Note

In order to run the WCF Test Client, you have to log in to your machine as a local administrator. You also have to start Visual Studio as an administrator because we have changed the service port from the default one to 8080, and running Visual Studio as an administrator will automatically register this port.

You will see the WCF Test Client page, as shown in the following screenshot:

Testing the service using the WCF Test Client

If you get an Access is denied error, make sure you run Visual Studio as an administrator. Ignore the errors for the async methods, as they are not supported by WCF Test Client.

Now, from the WCF Test Client window, we can double-click on an operation to test it. First, let's test the GetProduct operation:

1. In the left panel of the client, double-click on the GetProduct() operation; the GetProduct request will be shown in the panel on the right-hand side.

2. In the Request panel, specify an integer for the product ID and click on the Invoke button to let the client call the service. You might get a dialog box that warns you about the security of sending information over the network. Click on the OK button to acknowledge this warning (you can check the In the future, do not show this message option so that it is not displayed again).

Now, the message Invoking Service… will be displayed in the status bar as the client is trying to connect to the server. It might take a while for this initial connection to be made, as several things need to be done in the background. Once the connection has been established, a channel will be created and the client will call the service to perform the requested operation. Once the operation has been completed on the server side, the response package will be sent back to the client, and the WCF Test Client will display this response in the panel at the bottom:

Testing the service using the WCF Test Client

If you start the test client in the debugging mode (by pressing F5), you can set a breakpoint at a line inside the GetProduct method, in the LayerNorthwindService.cs file. Moreover, when the Invoke button is clicked, the breakpoint will be hit so that you can debug the service, as we learned earlier.

Note that the response is always the same, no matter what product ID you use to retrieve the product. Specifically, the product name is hardcoded for testing purposes, as shown in the preceding screenshot.

Also, because the product ID is an integer value from the WCF Test Client, you can only enter an integer for it. If a noninteger value is entered, when you click on the Invoke button, you will get an error message which warns you that you have entered a value with the wrong type.

Now, let's test the UpdateProduct operation:

1. Double-click on the UpdateProduct() operation in the left panel, and UpdateProduct will be shown in the panel on the right-hand side in a new tab.

2. Select LayerNorthwindService.Product for the product dropdown, enter a value for each input parameter, and then click on the Invoke button to test it. Depending on the value you enter in the UnitPrice column, you will get a True or False response package back.

Testing the service using the WCF Test Client

Note

Click on the arrow key of the product value item to expand the product value so that you can enter values for the attributes of the product input.

The Request and Response packages are displayed in grids by default, but you have the option to display them in the XML format. Just select the XML tab at the bottom of the right-hand side panel, and you will see the XML-formatted Request and Response packages.

From these XML strings, you can see that they are actually SOAP messages:

Testing the service using the WCF Test Client

Besides testing operations, you can also take a look at the configuration settings of the web service. Just double-click on Config File in the panel on the left-hand side and the configuration file will be displayed in the panel on the right-hand side. This will show you the bindings for the service, the addresses of the service, and the contract for the service, as follows:

Testing the service using the WCF Test Client

If you are satisfied with the test results, just close the WCF Test Client, and you will go back to the Visual Studio IDE. Note that as soon as you close the client, the WCF Service Host is stopped. This is different from hosting a service inside IIS Express, where IIS Express stays active even after you close the client.

Testing the service using your own client

It is very convenient to test a WCF service using the built-in WCF Test Client, but sometimes, it is desirable to test a WCF service using your own test client. The built-in WCF Test Client is limited to test only simple WCF services. For complex WCF services, for example, a service where the parameter is a more complex object, we have to create our own test client.

To create our own client in order to consume the WCF service, we first need to host the WCF service in a host application. For this purpose, we can use the methods that we learned in the previous chapter to host the WCF service in IIS, IIS Express, or a managed .NET application.

In addition to the previous methods we learned, we can also use the built-in WCF Service Host to host the WCF service. So, we don't need to create a host application, but we just need to create a client. In this section, we will use this hosting method to save us some time.

First, let's find a way to get the metadata for the service. From Visual Studio's built-in WCF Test Client, you cannot examine the Web Services Description Language (WSDL) of the service, although the client itself must have used the WSDL to communicate with the service. To see the WSDL outside the WCF Service Test Client, just copy the address of the service from the configuration file and paste it in a web browser. In our example, the address of the service is http://localhost:8080/LayerNorthwindService/ProductService/. So, copy and paste this address to a web browser, and we will see the WSDL of the service, just as we have seen many times before.

Testing the service using your own client

Note

To get the metadata for the service, the service host application must run. The easiest way to start LayerNorthwindService in the WCF Service Host is to start the WCF Test Client and leave it running.

Now that we know how to get the metadata for our service, we can start building the test client. We can leave the host application running and manually generate the proxy classes by using the same method that we used earlier. However, this time, we will let Visual Studio do it for us.

Follow these steps to build your own client to test the WCF service:

1. Add a new Console Application project to the LayerNorthwind solution. Let's call it LayerNorthwindClient.

2. Add a reference to the WCF service. In the Visual Studio Solution Explorer, right-click on the LayerNorthwindClient project, select Add | Service Reference… from the context menu, and you will see the Add Service Reference dialog box.

3. In the Add Service Reference dialog box, type the address http://localhost:8080/LayerNorthwindService/ProductService/ in the Address box, and then click on the Go button to connect to the service.

4. Also, you can simply click on the Discover button (or click on the little arrow next to the Discover button and select Services from the Solution Explorer) to find this service.

Note

In order to connect to or discover a service in the same solution, you don't have to start the host application for the service. The WCF Service Host will automatically start for this purpose. However, if it has not started in advance, it might take a while for the Add Service Reference window to download the required metadata information for the service.

Testing the service using your own client

5. The ProductService option should now be listed on the left-hand side of the window. You can expand it and select the service contract to view its details.

6. Next, let's change the namespace of this service from ServiceReference1 to ProductServiceRef. This will make the reference meaningful in the code.

7. Now, click on the OK button in the Add Service Reference dialog box to add the service reference. You will see that a new folder named ProductServiceRef is created under Service References in the Solution Explorer for the LayerNorthwindClient project. This folder contains lots of files, including the WSDL file, the service map, and the actual proxy code. If you can't see them, click on Show All Files in the Solution Explorer.

The App.config config file is also modified to include the service details.

At this point, the proxy code to connect to the WCF service has been created and added to the project for us without us having to enter a single line of code. What we need to do next is write just a few lines of code to call the service.

Just as we did earlier, we will modify the Program.cs file to call the WCF service:

1. First, open the Program.cs file and add the following using line to the file:

using LayerNorthwindClient.ProductServiceRef;

2. Then, inside the Main method, add the following line of code to create a client object:

var client = new ProductServiceClient();

3. Finally, add the following lines of code to the file in order to call the WCF service to get and update a product:

4. var product = client.GetProduct(23);

5. Console.WriteLine("product name is " + product.ProductName);

6. Console.WriteLine("product price is " + product.UnitPrice.ToString());

7. product.UnitPrice = 20.0m;

8. var message = "";

9. var result = client.UpdateProduct(product, ref message);

10.Console.WriteLine("Update result is " + result.ToString());

11.Console.WriteLine("Update message is " + message);

Console.ReadLine();

Now, you can run the client application to test the service. Remember that you need to run Visual Studio as an administrator. Make a note of the following:

· If you want to start the application in the debugging mode (F5), you need to add a Console.ReadLine(); statement at the end of the program so that you can see the output of the program. Also, remember to set the LayerNorthwindClient application as the startup project. The WCF Service Host application will be started automatically before the client is started (but the WCF Test Client won't be started).

· If you want to start the client application in the nondebugging mode (Ctrl + F5), you need to start the WCF Service Host application and the WCF Test Client application in advance, as there will be no service for the client to call otherwise. You can start the WCF Service Host application and the WCF Test Client from another Visual Studio IDE instance, or you can set LayerNorthwindService as the startup project, start it in the nondebugging mode (Ctrl + F5), and leave it running. Then, you can change LayerNorthwindClient to be the startup project and start it in the nondebugging mode. Also, you can set the solution to start with multiple projects, with LayerNorthwindService as the first project to be run and LayerNorthwindClient as the second project to be run.

· You can also start the WCF service in the debugging mode, then right-click on the client project, and go to Debug | Start new instance in order to start the client program in the debugging mode.

Note

In my environment, I've set the solution to start with multiple projects, so I am sure that the WCF service is always started before the client application, no matter whether it is in the debugging mode or not.

The output of this client program is as shown in the following screenshot:

Testing the service using your own client

Adding a business logic layer

Until now, the WCF service contained only one layer. In this section, we will add a business logic layer and define some business rules in this layer.

Adding the business domain object project

Before we add the business logic layer, we need to add a project for the business domain objects. The business domain object project will hold definitions such as products, customers, and orders. These domain objects will be used across the business logic layer, the data access layer, and the service layer. They will be very similar to the data contracts that we defined in the previous section but will not be seen outside the service. The product business domain object will have the same properties as the product's contract data, plus some extra properties such asUnitsInStock and ReorderLevel. These properties will be used internally and shared by all the layers of the service. For example, when an order is placed, UnitsInStock should be updated as well. Also, if the updated UnitsInStock is less than ReorderLevel, an event should be raised to trigger the reordering process.

The business domain data objects by themselves do not act as a layer. They are just pure C# classes (also called POCO classes), representing internal data within the service implementations. There is no logic inside these business domain objects. In reality, the business domain object classes can be very different from the data contracts, property names, property types, and data structures.

As with the data contracts, the business domain object classes should be in their own assembly. Therefore, we first need to create a project for them. Just add a new C# class library, LayerNorthwindBDO, to the solution. Then, modify the Class1.cs file, which has been autocreated by Visual Studio, as follows:

1. Rename the Class1.cs file to ProductBDO.cs.

2. Add the following properties to this class:

o ProductID

o ProductName

o QuantityPerUnit

o UnitPrice

o Discontinued

o UnitsInStock

o UnitsOnOrder

o ReorderLevel

Note

Five of the preceding properties are also present in the product service data contract. The last three properties are used inside the service implementations. For example, UnitsOnOrder might be used to trigger business logic when discontinuing a product.

The following is the code list of the ProductBDO class:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace LayerNorthwindBDO

{

public class ProductBDO

{

public int ProductID { get; set; }

public string ProductName { get; set; }

public string QuantityPerUnit { get; set; }

public decimal UnitPrice { get; set; }

public int UnitsInStock { get; set; }

public int ReorderLevel { get; set; }

public int UnitsOnOrder { get; set; }

public bool Discontinued { get; set; }

}

}

Adding the business logic project

Next, let's create the business logic layer project. Again, we just need to add a new C# class library project, LayerNorthwindLogic, to the solution. Then, modify the Class1.cs file as follows:

1. Rename the file from Class1.cs to ProductLogic.cs.

2. Add a reference to the LayerNorthwindBDO project.

Now, we need to add some code to the ProductLogic class:

1. Add the following using line:

using LayerNorthwindBDO;

2. Add the GetProduct method. It should look as follows:

3. public ProductBDO GetProduct(int id)

4. {

5. // TODO: call data access layer to retrieve product

6. var p = new ProductBDO();

7. p.ProductID = id;

8. p.ProductName =

9. "fake product name from business logic layer";

10. p.UnitPrice = 20.00m;

11. p.QuantityPerUnit = "fake QPU";

12. return p;

}

In this method, we create a ProductBDO object, assign values to some of its properties, and return the object to the caller. Everything is still hardcoded so far.

Note

We hardcode the product name as fake product name from business logic layer so that we know that this is a different product from the one that is returned directly by the service layer.

3. Add the UpdateProduct method, as follows:

4. public bool UpdateProduct(ProductBDO product,

5. ref string message)

6. {

7. var productInDB =

8. GetProduct(product.ProductID);

9. // invalid product to update

10. if (productInDB == null)

11. {

12. message = "cannot get product for this ID";

13. return false;

14. }

15. // a product can't be discontinued

16. // if there are non-fulfilled orders

17. if (product.Discontinued == true

18. && productInDB.UnitsOnOrder > 0)

19. {

20. message = "cannot discontinue this product";

21. return false;

22. }

23. else

24. {

25. // TODO: call data access layer to update product

26. message = "Product updated successfully";

27. return true;

28. }

}

Within this method, we still haven't updated anything in the database, but this time, we added several pieces of logic to the UpdateProduct method. First, we try to retrieve the product to see if it is a valid product to update; if not, we will return false and stop. We also added logic to check whether it is okay to discontinue a product, and if not, we will return false and stop.

4. Add test logic to the GetProduct method.

As said earlier, we added a check to make sure that a supplier cannot discontinue a product if there are unfulfilled orders for this product. However, at this stage, we cannot truly enforce this logic. The reason behind this is when we check the UnitsOnOrder property of a product, it is always 0 as we haven't assigned a value to it in the GetProduct method.

For test purposes, we can change the GetProduct method to include the following line of code:

if(id > 50) p.UnitsOnOrder = 30;

Now, when we test the service, we can select a product with an ID that is greater than 50, and try to update its Discontinued property to see what result we will get.

After you put all of this together, the content of the ProductLogic.cs file should be as follows:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using LayerNorthwindBDO;

namespace LayerNorthwindLogic

{

public class ProductLogic

{

public ProductBDO GetProduct(int id)

{

// TODO: call data access layer to retrieve product

var p = new ProductBDO();

p.ProductID = id;

p.ProductName =

"fake product name from business logic layer";

p.UnitPrice = 20.00m;

p.QuantityPerUnit = "fake QPU";

if (id > 50) p.UnitsOnOrder = 30;

return p;

}

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

{

// TODO: call data access layer to update product

message = "Product updated successfully";

return true;

}

}

}

}

Calling the business logic layer from the service interface layer

We now have the business logic layer ready and can modify the service contracts to call this layer so that we can enforce some business logic.

First, we want to make it very clear that we are going to change the service implementations and not the interfaces. Therefore, we will only change the ProductService.cs file.

We will not touch the IProductService.cs file. All the existing clients (if there are any) that are referencing our service will not notice that we are changing the implementation.

Follow these steps to customize the service interface layer (the LayerNorthwindService project):

1. From the Solution Explorer, right-click on the LayerNorthwindService project and select Add | Reference…. Then, add references to the LayerNorthwindLogic and LayerNorthwindBDO projects.

2. Now, we have added two references. We then add the following two using statements to the ProductService.cs file:

3. using LayerNorthwindBDO;

using LayerNorthwindLogic;

3. Next, inside the GetProduct method, we can use the following statements to get the product from our business logic layer:

4. var productLogic = new ProductLogic();

var productBDO = productLogic.GetProduct(id);

4. However, we cannot return this product to the caller because this product is of the ProductBDO type, which is not the type that the caller is expecting. The caller is expecting a return value of the type Product, which is a data contract defined within the service interface (sometimes called a data transfer object or a DTO). We need to translate this ProductBDO object to a ProductDTO object. To do this, we add the following new method to the ProductService class:

5. private void TranslateProductBDOToProductDTO(

6. ProductBDO productBDO,

7. Product product)

8. {

9. product.ProductID = productBDO.ProductID;

10. product.ProductName = productBDO.ProductName;

11. product.QuantityPerUnit = productBDO.QuantityPerUnit;

12. product.UnitPrice = productBDO.UnitPrice;

13. product.Discontinued = productBDO.Discontinued;

}

5. Inside the preceding translation method, we copy all the properties from the ProductBDO object to the service contract data object, but not the last three properties—UnitsInStock, UnitsOnOrder, and ReorderLevel. These three properties are used only inside the service implementations. External callers cannot see them at all.

6. The GetProduct method should now look as follows:

7. public Product GetProduct(int id)

8. {

9. var productLogic = new ProductLogic();

10. var productBDO = productLogic.GetProduct(id);

11. var product = new Product();

12. TranslateProductBDOToProductDTO(productBDO, product);

13. return product;

}

7. We can modify the UpdateProduct method in the same way, making it look as shown in the following code snippet:

8. public bool UpdateProduct(Product product,

9. ref string message)

10.{

11. var result = true;

12.

13. // first check to see if it is a valid price

14. if (product.UnitPrice <= 0)

15. {

16. message = "Price cannot be <= 0";

17. result = false;

18. }

19. // ProductName can't be empty

20. else if (string.IsNullOrEmpty(product.ProductName))

21. {

22. message = "Product name cannot be empty";

23. result = false;

24. }

25. // QuantityPerUnit can't be empty

26. else if (string.IsNullOrEmpty(product.QuantityPerUnit))

27. {

28. message = "Quantity cannot be empty";

29. result = false;

30. }

31. else

32. {

33. var productLogic = new ProductLogic();

34. var productBDO = new ProductBDO();

35. TranslateProductDTOToProductBDO(product, productBDO);

36.

37. return productLogic.UpdateProduct(productBDO, ref message);

38. }

39. return result;

}

8. Note that we have to create a new method in order to transform a product contract data object into a ProductBDO object. During the transformation, we leave the three extra properties unassigned in the ProductBDO object because we know that a supplier won't update these properties.

9. Since we have to create a ProductLogic variable in both the methods, let's make it a class member:

ProductLogic productLogic = new ProductLogic();

The final content of the ProductService.cs file is as follows:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using LayerNorthwindBDO;

using LayerNorthwindLogic;

namespace LayerNorthwindService

{

public class ProductService : IProductService

{

ProductLogic productLogic = new ProductLogic();

public Product GetProduct(int id)

{

var productBDO = productLogic.GetProduct(id);

var product = new Product();

TranslateProductBDOToProductDTO(productBDO, product);

return product;

}

public bool UpdateProduct(Product product,

ref string message)

{

var result = true;

// first check to see if it is a valid price

if (product.UnitPrice <= 0)

{

message = "Price cannot be <= 0";

result = false;

}

// ProductName can't be empty

else if (string.IsNullOrEmpty(product.ProductName))

{

message = "Product name cannot be empty";

result = false;

}

// QuantityPerUnit can't be empty

else if (string.IsNullOrEmpty(product.QuantityPerUnit))

{

message = "Quantity cannot be empty";

result = false;

}

else

{

var productBDO = new ProductBDO();

TranslateProductDTOToProductBDO(product, productBDO);

return productLogic.UpdateProduct(

productBDO, ref message);

}

return result;

}

private void TranslateProductBDOToProductDTO(

ProductBDO productBDO,

Product product)

{

product.ProductID = productBDO.ProductID;

product.ProductName = productBDO.ProductName;

product.QuantityPerUnit = productBDO.QuantityPerUnit;

product.UnitPrice = productBDO.UnitPrice;

product.Discontinued = productBDO.Discontinued;

}

private void TranslateProductDTOToProductBDO(

Product product,

ProductBDO productBDO)

{

productBDO.ProductID = product.ProductID;

productBDO.ProductName = product.ProductName;

productBDO.QuantityPerUnit = product.QuantityPerUnit;

productBDO.UnitPrice = product.UnitPrice;

productBDO.Discontinued = product.Discontinued;

}

}

}

Testing the WCF service with a business logic layer

We can now compile and test the new WCF service with a business logic layer. We will use the WCF Test Client to simplify the process:

1. Set the LayerNorthwindService project as the startup project.

2. Start the WCF Service Host application and WCF Service Test Client by pressing F5 or Ctrl + F5.

3. In the WCF Service Test Client, double-click on the GetProduct() operation to bring up the GetProduct test screen.

4. Enter a value of 56 for the ID field and then click on the Invoke button.

5. You will see that this time, the product is returned from the business logic layer, instead of the service layer. Also, note that the UnitsOnOrder property is not displayed as it is not part of the service contract data type. However, we know that a product has a UnitsOnOrder property, and we will use this for our next test.

Testing the WCF service with a business logic layer

Now, let's try to update a product:

1. In the WCF Service Test Client, double-click on the UpdateProduct() operation to bring up the UpdateProduct test screen.

2. Enter -10 as the price and click on the Invoke button. You will see that the Response result is False.

3. Enter a valid price, say 25.6, a name, and a quantity per unit, and leave the Discontinued property set to False. Then, click on the Invoke button. You will see that the Response result is now True.

4. Change the Discontinued value from False to True and click on the Invoke button again. The Response result is still True. This is because we didn't change the product ID, and it has been defaulted to 0. Remember, in our business logic layer in the GetProduct operation, for a product with an ID less than or equal to 50, we didn't set the UnitsOnOrder property; thus, it defaults to 0. In our business logic for the UpdateProduct operation, it is OK to set the Discontinued property to True if UnitsOnOrder is less than or equal to 0.

5. Change the product ID to 51, leave the Discontinued value as True and the product price as 25.6, and click on the Invoke button again. This time, you will see that the Response result is False. This is because the business logic layer has checked the UnitsOnOrder andDiscontinued properties and didn't allow us to make the update.

Testing the WCF service with a business logic layer

Summary

In this chapter, we created a real-world WCF service that has a service contract layer and a business logic layer. We used the built-in WCF Service Library template to create the service and followed the WCF service development's best practices to separate the service interfaces from the business logic.

In the next chapter, we will add one more layer, the data access layer, to the service and add error handling to the service.