Implementing a WCF Service in the Real World



Thanks Mike


https://www.packtpub.com/books/content/implementing-wcf-service-real-world






WCF is the acronym for Windows Communication Foundation. It is Microsoft's latest technology that enables applications in a distributed environment to communicate with each other.
In this article by, Mike Liu, author of  WCF 4.0 Multi-tier Services Development with LINQ to Entities, we will create and test the WCF service by following these steps:
  • Create the project using a WCF Service Library template
  • Create the project using a WCF Service Application template
  • Create the Service Operation Contracts
  • Create the Data Contracts
  • Add a Product Entity project
  • Add a business logic layer project
  • Call the business logic layer from the service interface layer
  • Test the service
Here ,In this article, we will learn how to separate the service interface layer from the business logic layer
(Read more interesting articles on WCF 4.0 here.)

Why layer a service?

An important aspect of SOA design is that service boundaries should be explicit, which means hiding all the details of the implementation behind the service boundary. This includes revealing or dictating what particular technology was 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. So in the real world, it is always 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 that are used to define the service interfaces that will be exposed at the service boundary. Data contracts are also defined to pass in and out of the service. If any exception is expected to be thrown outside of the service, then Fault contracts will also be defined at this layer.
  • 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 caller of the service.
  • Data access layer: This layer will take care of all of 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.
Layering provides separation of concerns and better factoring of code, which gives you better maintainability and the ability to split out layers into separate physical tiers for scalability. The data access code should be separated into its own layer that focuses on performing translation services between the databases and the application domain. Services should be placed in a separate service layer that focuses on performing translation services between the service-oriented external world and the application domain.
The service interface layer will be compiled into a separate class assembly and hosted in a service host environment. The outside world will only know about and have access to this layer. Whenever a request is received by the service interface layer, 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 WCF templates

We need to create a new solution for this example and add a new WCF project to this solution. This time we will use the built-in Visual Studio WCF templates for the new project.

Using the C# WCF service library template

There are a few built-in WCF service templates within Visual Studio 2010; two of them are Visual Studio WCF Service Library and Visual Studio WCF Service Application. In this article, we will use the service library template.
Follow these steps to create the RealNorthwind solution and the project using the service library template:
  1. Start Visual Studio 2010, select menu option File New | Project…|, and you will see the New Project dialog box. 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 Visual C# WCF | WCF| Service Library as the project template, RealNorthwindService as the (project) name, and RealNorthwind as the solution name. Make sure that the checkbox Create directory for solution is selected.
  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.

Using the C# WCF service application template

Instead of using the Visual Studio WCF Service Library template to create our new WCF project, we can use the Visual Studio Service Application template to create the new WCF project.
Because we have created the solution, we will add a new project using the Visual Studio WCF Service Application template.
  1. Right-click on the solution item in Solution Explorer, select menu option Add New Project…| from the context menu, and you will see the Add New Project dialog box.
  2. In the Add New Project window, specify Visual C# | WCF Service Application as the project template, RealNorthwindService2 as the (project) name, and leave the default location of C:\SOAWithWCFandLINQ\Projects\RealNorthwind unchanged.
    Implementing a WCF Service in the real world
  3. Click on the OK button and the new project will be added to the solution.The project already has an IService1.cs file to define a service interface, and Service1.svc.cs to implement the service. It also has a Service1.svc file and a web.config file, which are used to host the new WCF service. It has also had the necessary references added to the project such as System.ServiceModel.
You can follow these steps to test this service:
  • Change this new project, RealNorthwindService2, to be the startup project(right-click on it from Solution Explorer and select Set as Startup Project). Then run it (Ctrl + F5 or F5). You will see that it can now run. You will see that ASP.NET Development Server has been started, and a browser is open listing all of the files under the RealNorthwindService2 project folder.Clicking on the Service1.svc file will open the metadata page of the WCF service in this project.
If you have pressed F5 in the previous step to run this project, you might see a warning message box asking you if you want to enable debugging for the WCF service. As we said earlier, you can choose enable debugging or just run in the non-debugging mode.
You may also have noticed that the WCF Service Host is started together with ASP.NET Development Server. This is actually another way of hosting a WCF service in Visual Studio 2010. It has been started at this point because, within the same solution, there is a WCF service project (RealNorthwindService) created using the WCF Service Library template.
So far we have used two different Visual Studio WCF templates to create two projects. The first project, using the C# WCF Service Library template, is a more sophisticated one because this project is actually 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 a service, we can use the built-in WCF Test Client to invoke it. This makes it very convenient for WCF development.
The second project, using the C# WCF Service Application template, is actually a website. This is the hosting application of the WCF service so you don't have to create a separate hosting application for the WCF service. As we have already covered them and you now have a solid understanding of these styles, we will not discuss them further. But keep in mind that you have this option, although in most cases it is better to keep the WCF service as clean as possible, without any hosting functionalities attached to it.
To focus on the WCF service using the WCF Service Library template, we now need to remove the project RealNorthwindService2 from the solution.
In Solution Explorer, right-click on the RealNorthwindService2 project item and select Remove from the context menu. Then you will see a warning message box. Click on the OK button in this message box and the RealNorthwindService2 project will be removed from the solution. Note that all the files of this project are still on your hard drive. You will need to delete them using Windows Explorer.

Creating the service interface layer

In this article, we will create the service interface layer contracts.
Because 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 the service interfaces

To create the service interfaces, we need to open the IService1.cs file and do the following:
  1. Change its namespace from RealNorthwindService to:
    MyWCFServices.RealNorthwindService
  2. Change the interface name from IService1 to IProductService. Don't be worried if you see the warning message before the interface definition line, as we will change the web.config file in one of the following steps.
  3. Change the first operation contract definition from this line:
    string GetData(int value);
    to this line:
    Product GetProduct(int id);
  4. Change the second operation contract definition from this line:
    CompositeType GetDataUsingDataContract(CompositeType composite);
    to this line:
    bool UpdateProduct(Product product);
  5. Change the filename from IService1.cs to IProductService.cs.
With these changes, we have defined two service contracts. The first one can be used to get the product details for a specific product ID, while the second one can be used to update a specific product. The product type, which we used to define these service contracts, is still not defined.
The content of the service interface for RealNorthwindService.ProductService should look like this now:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace MyWCFServices.RealNorthwindService
{
[ServiceContract]
public interface IProductService
{
[OperationContract]
Product GetProduct(int id);
[OperationContract]
bool UpdateProduct(Product product);
// TODO: Add your service operations here
}
}
This is not the whole content of the IProductService.cs file. The bottom part of this file should still have the class, CompositeType.

Creating the data contracts

Another important aspect of SOA design is that you shouldn't assume that the consuming 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 SOA principles, you should not pass any .NET-specific types such as DataSet or Exceptions across the service boundary. You should stick to fairly simple data structure objects such as classes with properties and backing member fields. You can pass objects that have nested complex types such as 'Customer with an Order collection'. However, you shouldn't 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, and Discontinued. These will be used to communicate with 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 DataContract in the same file as the service contract. We will modify the file, IProductService.cs, as follows:
  1. Change the DataContract name from CompositeType to Product.
  2. Change the fields from the following lines:
    bool boolValue = true;
    string stringValue = "Hello ";
    to these seven lines:
    int productID;
    string productName;
    string quantityPerUnit;
    decimal unitPrice;
    bool discontinued;
  3. Delete the old boolValue and StringValue DataMember properties. Then, for each of the above fields, add a DataMember property. For example, for productID, we will have this DataMember property:
    [DataMember]
    public int ProductID
    {
    get { return productID; }
    set { productID = value; }
    }
A better way is to take advantage of the automatic property feature of C#, and add the following ProductID DataMember without defining the productID field:
[DataMember]
public int ProductID { get; set; }
To save some space, we will use the latter format. So, we need to delete all of those field definitions and add an automatic property for each field, with the first letter capitalized.
The data contract part of the finished service contract file, IProductService.cs,should now look like this:
[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, open the Service1.cs file and do the following:
    • Change its namespace from RealNorthwindService to MyWCFServices.RealNorthwindService.
    • Change the class name from Service1 to ProductService. Make it inherit from the IProductService interface, instead of IService1. The class definition line should be like this:
      public class ProductService : IProductService
    • Delete the GetData and GetDataUsingDataContract methods.
    • Add the following method, to get a product:
      public Product GetProduct(int id)
      {
      // TODO: call business logic layer to retrieve product
      Product product = new Product();
      product.ProductID = id;
      product.ProductName = "fake product name from service layer";
      product.UnitPrice = (decimal)10.0;
      return product;
      }
In this method, we created a fake product and returned it to the client.Later, we will remove the hard-coded product from this method and call the business logic to get the real product.
  • Add the following method to update a product:
    public bool UpdateProduct(Product product)
    {
    // TODO: call business logic layer to update product
    if (product.UnitPrice <= 0)
    return false;
    else
    return true;
    }
    Also, in this method, we don't update anything. Instead, we always return true if a valid price is passed in.
  • Change the filename from Service1.cs to ProductService.cs. The content of the ProductService.cs file should be like this:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    namespace MyWCFServices.RealNorthwindService
    {
    public class ProductService : IProductService
    {
    public Product GetProduct(int id)
    {
    // TODO: call business logic layer to retrieve product
    Product product = new Product();
    product.ProductID = id;
    product.ProductName = "fake product name
    from service layer";
    product.UnitPrice = (decimal)10;
    return product;
    }
    public bool UpdateProduct(Product product)
    {
    // TODO: call business logic layer to update product
    if (product.UnitPrice <= 0)
    return false;
    else
    return true;
    }
    }
    }

Modifying the app.config file

Because we have changed the service name, we have to make the appropriate changes to the configuration file. Note that when you rename the service, if you have used the refactor feature of Visual Studio, some of the following tasks may have been done by Visual Studio.
Follow these steps to change the configuration file:
  1. Open the app.config file from Solution Explorer.
  2. Change all instances of the RealNorthwindService string except the one in baseAddress to MyWCFServices.RealNorthwindService. This is for the namespace change.
  3. Change the RealNorthwindService string in baseAddress to MyWCFServices/RealNorthwindService.
  4. Change all instances of the Service1 string to ProductService. This is for the actual service name change.
  5. Change the service address port from 8731 to 8080. This is to prepare for the client application, which we will create soon.
  6. You can also change Design_Time_Addresses to whatever address you want, or delete the baseAddress part from the service. This can be used to test your service locally. We will leave it unchanged for our example.
The content of the app.config file should now look like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the
config file must be added to the host's app.config file.
System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service name="MyWCFServices.RealNorthwindService.
ProductService">
<endpoint address="" binding="wsHttpBinding"
contract="MyWCFServices.
RealNorthwindService.IProductService">\
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/Design_Time_
Addresses/MyWCFServices/
RealNorthwindService/ProductService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata
endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging
purposes, set the value below to true. Set to false before
deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

Testing the service using WCF Test Client

Because we are using the WCF Service Library template in this example, we are now ready to test this web service. As we pointed out when creating this project, this service will be hosted in the Visual Studio 2010 WCF Service Host environment.
To start the service, press F5 or Ctrl + F5. WcfSvcHost will be started and WCF Test Client is also started. This is a Visual Studio 2010 built-in test client for WCF Service Library projects.
In order to run the WCF Test Client you have to log into your machine as a local administrator. You also have to start Visual Studio as an administrator because we have changed the service port from 8732 to 8080 (port 8732 is pre-registered but 8080 is not).
Again, if you get an Access is denied error, make sure you run Visual Studio as an administrator (under Windows XP you need to log on as an administrator).
Now from this WCF Test Client we can double-click on an operation to test it.First, let us test the GetProduct operation.
Now the message Invoking Service… will be displayed in the status bar as the client is trying to connect to the server. It may 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 bottom panel.
If you started the test client in debugging mode (by pressing F5), you can set a breakpoint at a line inside the GetProduct method in the RealNorthwindService.cs file, and when the Invoke button is clicked, the breakpoint will be hit so that you can debug the service as we explained earlier. However, here you don't need to attach to the WCF Service Host.
Note that the response is always the same, no matter what product ID you use to retrieve the product. Specifically, the product name is hard-coded, as shown in the diagram. Moreover, from the client response panel, we can see that several properties of the Product object have been assigned default values.
Also, because the product ID is an integer value from the WCF Test Client, you can only enter an integer for it. If a non-integer value is entered, when you click on the Invoke button, you will get an error message box to warn you that you have entered a value with the wrong type.
Now let's test the operation, UpdateProduct.
The Request/Response packages are displayed in grids by default but you have the option of displaying them in XML format. Just select the XML tab at the bottom of the right-side panel, and you will see the XML-formatted Request/Response packages. From these XML strings, you can see that they are SOAP messages.
Besides testing operations, you can also look at the configuration settings of the web service. Just double-click on Config File from the left-side panel and the configuration file will be displayed in the right-side panel. This will show you the bindings for the service, the addresses of the service, and the contract for the service.
What you see here for the configuration file is not an exact image of the actual configuration file. It hides some information such as debugging mode and service behavior, and includes some additional information on reliable sessions and compression mode.
If you are satisfied with the test results, just close the WCF Test Client, and you will go back to 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 ASP.NET Development Server, where ASP.NET Development Server still stays active even after you close the client.
(Read more interesting articles on WCF 4.0  here.)

Testing the service using our 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 only simple WCF services. For complex WCF services, we have to create our own test client. For this purpose, we can use the methods we learned earlier to host the WCF service in IIS, ASP.NET Development Server, or a managed .NET application, and create a test client to test the service.
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 just need to create a client. In this article, we will use this hosting method to save us some time.
First, let us find a way to get the metadata for the service. From the Visual Studio 2010 built-in WCF Test Client,you can't examine the WSDL of the service, although the client itself must have used the WSDL to communicate with the service. To see the WSDL outside of the WCF Service Test Client, just copy the address of the service from the configuration file and paste it into a web browser. In our example, the address of the service is: http://localhost:8080/Design_Time_Addresses/MyWCFServices/RealNorthwindService/ProductService/. So copy and paste this address to a web browser, and we will see the WSDL languages of the service, just as we have seen many times before.
Implementing a WCF Service in the Real World
To get the metadata for the service, the service host application must run. The easiest way to start RealNorthwindService 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 using the same method that we used earlier. But this time we will let Visual Studio do it for us, So you can close the WCF Test Client for now.
Follow these steps to build your own client to test the WCF service:
  1. Add a new Console Application project to the RealNorthwind solution. Let's call it RealNorthwindClient.
  2. Add a reference to the WCF service. In Visual Studio Solution Explorer, right-click on the RealNorthwindClient project, select Add Service Reference… from the context menu, and you will see the Add Service Reference dialog box.
    Implementing a WCF Service in the Real World
  3. In the Add Service Reference dialog box, type the following address into the Address box, and then click on the Go button to connect to the service:
    http://localhost:8080/Design_Time_Addresses/MyWCFServices/
    RealNorthwindService/ProductService/
    You can also simply click on the Discover button (or click on the little arrow next to the Discover button, and select Services in Solution) to find this service.
    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 be automatically started for this purpose. However, if it is not started in advance, it may take a while for the Add Service Reference window to download the required metadata information for the service.
    ProductService 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.
  4. Next, let's change the namespace of this service from ServiceReference1 to ProductServiceRef. This will make the reference meaningful in the code.
  5. If you want to make this client run under .NET 2.0, click on the Advanced… button in the Add Service Reference window, and in the Service Reference Settings pop-up dialog box, click on the Add Web Reference… button. This will cause the proxy code to be generated based on .NET 2.0 web service standards.
    Implementing a WCF Service in the Real World
    In this example, we won't do this, So click on the Cancel button to discard these changes.
  6. 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 Solution Explorer for the RealNorthwindClient 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 Solution Explorer.
    Implementing a WCF Service in the Real World
A new file, App.config, is also added to the project as well as several WCF-related references such as System.ServiceModel and System.Runtime.Serialization.
At this point, the proxy code to connect to the WCF service and the required configuration file have both 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 to write just a few lines of code to call this service.
Just as we did earlier, we will modify Program.cs to call the WCF service.
  1. First open the Program.cs file, and add the following using line to the file:
    using RealNorthwindClient.ProductServiceRef;
  2. Then inside the Main method add the following line of code to create a client object:
    ProductServiceClient client = new ProductServiceClient();
  3. Finally add the following lines to the file to call the WCF service to get and update a product:
    Product product = client.GetProduct(23);
    product.UnitPrice = (decimal)20.0;
    bool result = client.UpdateProduct(product);
The content of the Program.cs file is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RealNorthwindClient.ProductServiceRef;
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());
}
}
}
Now you can run the client application to test the service. Remember that you need to run Visual Studio as an administrator.
  • If you want to start it in debugging mode (F5), you need to add a Console.ReadLine(); statement to the end of the program so that you can see the output of the program. Also remember to set the RealNorthwindClient 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 non-debugging mode (Ctrl + F5), you need to start the WCF Service Host application (and the WCF Test Client application) in advance. You can start the WCF Service Host application (and the WCF Test Client) from another Visual Studio IDE instance, or you can set RealNorthwindService as the startup project, start it in non-debugging mode (Ctrl + F5), leave it running, and then change RealNorthwindClient to be the startup project, and start it in non-debugging mode. Also, you can set the solution to start with multiple projects with RealNorthwindService as the first project to be run and RealNorthwindClient as the second project to be run. In my environment, I 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 debugging mode or not.
The output of this client program is as shown in the following figure:
Implementing a WCF Service in the Real World

Adding a business logic layer

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

Adding the product entity project

Before we add the business logic layer, we need to add a project for business entities. The business entities project will hold all business entity object definitions such as products, customers, and orders. These entities will be used across the business logic layer, the data access layer, and the service layer. The Product entity will have the same properties as the product contract data, plus some extra properties such as UnitsInStock and ReorderLevel. These properties will be used internally and shared by all 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 entities by themselves do not act as a layer. They are just pure C# classes representing internal data within the service implementations. There is no logic inside these entities. Also, in our example, these entities are very similar to the data contracts (with only two extra fields in the entity class). In reality, the entity classes could be very different from the data contracts, from property names and property types to data structures.
As with the data contracts, the business entities' classes should be in their own assembly. So we first need to create a project for them. Just add a new C# class library, RealNorthwindEntities, to the Solution. Then rename Class1.cs to ProductEntity.cs, and modify it as follows:
  1. Change its namespace from RealNorthwindEntities to MyWCFServices.RealNorthwindEntities.
  2. Change the class name from Class1 to ProductEntity.
  3. Add the following properties to this class:
    ProductID, ProductName, QuantityPerUnit, UnitPrice, Discontinued,
    UnitsInStock, UnitsOnOrder, and ReorderLevel.
The following is the code list of the ProductEntity class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyWCFServices.RealNorthwindEntities
{
public class ProductEntity
{
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 us create the business logic layer project. Again, we just need to add a new C# class library project, RealNorthwindLogic, to the solution. So rename Class1.cs to ProductLogic.cs, and modify it as follows:
    1. Change its namespace from RealNorthwindLogic to MyWCFServices.RealNorthwindLogic.

    1. Change the class name from Class1 to ProductLogic
    2. Add a reference to the project, RealNorthwindEntities, as shown in the following Add Reference image:

      Implementing a WCF Service in the Real World

Now we need to add some code to the ProductLogic class.
    1. Add the following using line:
      using MyWCFServices.RealNorthwindEntities;
    2. Add the method, GetProduct. It should look like this:
      public ProductEntity GetProduct(int id)
      {
      // TODO: call data access layer to retrieve product
      ProductEntity p = new ProductEntity();
      p.ProductID = id;
      p.ProductName = "fake product name from business logic layer";
      p.UnitPrice = (decimal)20.00;
      return p;
      }
      In this method we create a ProductEntity object, assign values to some of its properties, and return it to the caller. Everything is still hardcoded so far.

We hardcode the product name as 'fake product name from business logic layer' so that we know this is a different product from the one returned directly from the service layer.
  • Add the method, UpdateProduct, as follows:
    public bool UpdateProduct(ProductEntity product)
    {
    // TODO: call data access layer to update product
    // first check to see if it is a valid price
    if (product.UnitPrice <= 0)
    return false;
    // ProductName can't be empty
    else if (product.ProductName == null
    || product.ProductName. Length == 0)
    return false;
    // QuantityPerUnit can't be empty
    else if (product.QuantityPerUnit == null
    || product. QuantityPerUnit.Length == 0)
    return false;
    // then validate other properties
    else
    {
    ProductEntity productInDB = GetProduct(product.ProductID);
    // invalid product to update
    if (productInDB == null)
    return false;
    // a product can't be discontinued if there are
    // non-fulfilled orders
    if (product.Discontinued == true
    && productInDB. UnitsOnOrder > 0)
    return false;
    else
    return true;
    }
    }
  • Add test logic to the GetProduct method.
We still haven't updated anything in a database but this time we have added several pieces of logic to the UpdateProduct method. First, we checked the validity of the UnitPrice property and returned i if it was not valid. We then checked the product name and quantity per unit properties to make sure they were not empty. We then tried to retrieve the product to see if it was a valid product to update. We also added a check to make sure that a supplier can't discontinue a product if there are unfulfilled orders for this product. However, at this stage, we can't truly enforce this logic because when we check the UnitsOnOrder property of a product it is always 0 as we didn't assign 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 MyWCFServices.RealNorthwindEntities;
namespace MyWCFServices.RealNorthwindLogic
{
public class ProductLogic
{
public ProductEntity GetProduct(int id)
{
// TODO: call data access layer to retrieve product
ProductEntity p = new ProductEntity();
p.ProductID = id;
p.ProductName =
"fake product name from business logic layer";
p.UnitPrice = (decimal)20.0;
if(id > 50) p.UnitsOnOrder = 30;
return p;
}
public bool UpdateProduct(ProductEntity product)
{
// TODO: call data access layer to update product
// first check to see if it is a valid price
if (product.UnitPrice <= 0)
return false;
// ProductName can't be empty
else if (product.ProductName == null || product.
ProductName.Length == 0)
return false;
// QuantityPerUnit can't be empty
else if (product.QuantityPerUnit == null || product.
QuantityPerUnit.Length == 0)
return false;
// then validate other properties
else
{
ProductEntity productInDB =
GetProduct(product.ProductID);
// invalid product to update
if (productInDB == null)
return false;
// a product can't be discontinued if there are
// non-fulfilled orders
else if (product.Discontinued == true && productInDB.
UnitsOnOrder > 0)
return false;
else
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. So we will only change the ProductService.cs file.
We will not touch the file, IProductService.cs. All of 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:
  1. Add a reference to the business logic layer.
    In order to call a method inside the business logic layer we need to add a reference to the assembly that the business logic is included in. We will also use the ProductEntity class. So we need a reference to the RealNorthwindEntities as well.
    To add the references from Solution Explorer right-click on the project, RealNorthwindService, select Add Reference… from the context menu, and select RealNorthwindLogic from the Projects tab. Also, select RealNorthwindEntities, as we will need a reference to ProductEntity inside it. Just hold down the Ctrl key if you want to select multiple projects. Click on the OK button to add references to the selected projects.
  2. Now we have added two references. We can add the following two using statements to the ProductService.cs file so that we don't need to type the full names for their classes.
    using MyWCFServices.RealNorthwindEntities;
    using MyWCFServices.RealNorthwindLogic;
  3. Now, inside the GetProduct method, we can use the following statements to get the product from our business logic layer:
    ProductLogic productLogic = new ProductLogic();
    ProductEntity product = productLogic.GetProduct(id);
  4. However, we cannot return this product back to the caller because this product is of the type, ProductEntity, 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. We need to translate this ProductEntity object to a Product object. To do this, we add the following new method to the ProductService class:
    private void TranslateProductEntityToProductContractData(
    ProductEntity productEntity,
    Product product)
    {
    product.ProductID = productEntity.ProductID;
    product.ProductName = productEntity.ProductName;
    product.QuantityPerUnit = productEntity.QuantityPerUnit;
    product.UnitPrice = productEntity.UnitPrice;
    product.Discontinued = productEntity.Discontinued;
    }
    Inside this translation method we copy all of the properties from the ProductEntity 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. Outside callers cannot see them at all.
    The GetProduct method should now look like this:
    public Product GetProduct(int id)
    {
    ProductLogic productLogic = new ProductLogic();
    ProductEntity productEntity = productLogic.GetProduct(id);
    Product product = new Product();
    TranslateProductEntityToProductContractData
    (productEntity, product);
    return product;
    }
    We can modify the UpdateProduct method in the same way, making it like this:
    public bool UpdateProduct(Product product)
    {
    ProductLogic productLogic = new ProductLogic();
    ProductEntity productEntity = new ProductEntity();
    TranslateProductContractDataToProductEntity(
    product, productEntity);
    return productLogic.UpdateProduct(productEntity);
    }
  5. Note that we have to create a new method to translate a product contract data object to a ProductEntity object. In translation we leave the three extra properties unassigned in the ProductEntity object because we know a supplier won't update these properties. Also, we have to create a ProductLogic variable in both the methods so that we can 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 MyWCFServices.RealNorthwindEntities;
    using MyWCFServices.RealNorthwindLogic;
    namespace MyWCFServices.RealNorthwindService
    {
    // NOTE: If you change the class name "Service1" here, you must
    also update the reference to "Service1" in App.config.
    public class ProductService : IProductService
    {
    ProductLogic productLogic = new ProductLogic();
    public Product GetProduct(int id)
    {
    /*
    // TODO: call business logic layer to retrieve product
    Product product = new Product();
    product.ProductID = id;
    product.ProductName =
    "fake product name from service layer";
    product.UnitPrice = (decimal)10.0;
    */
    ProductEntity productEntity = productLogic.GetProduct(id);
    Product product = new Product();
    TranslateProductEntityToProductContractData(
    productEntity, product);
    return product;
    }
    public bool UpdateProduct(Product product)
    {
    /*
    // TODO: call business logic layer to update product
    if (product.UnitPrice <= 0)
    return false;
    else
    return true;
    */
    ProductEntity productEntity = new ProductEntity();
    TranslateProductContractDataToProductEntity(
    product, productEntity);
    return productLogic.UpdateProduct(productEntity);
    }
    private void TranslateProductEntityToProductContractData(
    ProductEntity productEntity,
    Product product)
    {
    product.ProductID = productEntity.ProductID;
    product.ProductName = productEntity.ProductName;
    product.QuantityPerUnit = productEntity.QuantityPerUnit;
    product.UnitPrice = productEntity.UnitPrice;
    product.Discontinued = productEntity.Discontinued;
    }
    private void TranslateProductContractDataToProductEntity(
    Product product,
    ProductEntity productEntity)
    {
    productEntity.ProductID = product.ProductID;
    productEntity.ProductName = product.ProductName;
    productEntity.QuantityPerUnit = product.QuantityPerUnit;
    productEntity.UnitPrice = product.UnitPrice;
    productEntity.Discontinued = product.Discontinued;
    }
    }
    }

Testing the WCF service with a business logic layer

We can now compile and test the new service with a business logic layer. We will use the WCF Test Client to simplify the process.
  1. Make the project, RealNorthwindService, 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.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 property, UnitsOnOrder, and we will use this for our next test.
    Implementing a WCF Service in the Real World
Now let us 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.60, a name, and a quantity per unit, and leave the Discontinued property set to False, and 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 defaulted to 0. This is because in our business logic layer GetProduct operation, for a product with id <= 50, we didn't set the property, UnitsOnOrder, thus it defaults to 0, and in our business logic UpdateProduct operation, it is okay to set the Discontinued property to be True, if UnitsOnOrder is < 0.
Change the product ID to 51, leave the Discontinued value as True and the product price as 25.60, 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 and Discontinued properties and didn't allow us to make the update.
Implementing a WCF Service in the Real World

Summary

In this article, we have created a real world WCF service that has a service contract layer and a business logic layer. The key points in this article include:
  • WCF services should have explicit boundaries
  • The WCF Service Application template can be used to create WCF services with a hosting website created within the project
  • The WCF Service Library template can be used to create WCF services that are hosted by the WCF Service Host, and these can be tested using the WCF service Test Client
  • The service interface layer should contain only the service contracts such as the operation contracts and data contracts
  • The business logic layer should contain the implementation of the service
  • The business entities represent the internal data of the service shared by all of the layers of the service and they should not be exposed to the clients

Comments