Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Design a WCF Service

4.80/5 (9 votes)
31 Oct 2014CPOL9 min read 38.9K  
If you need to create a WCF service but aren't sure how to start or how to organise your Visual Studio solution, then hopefully this article will give you some ideas.

Introduction

If you need to create a WCF service but aren't sure how to start or how to organise your Visual Studio solution, then hopefully this article will give you some ideas. There are no right or wrong ways of designing a WCF service, but a few ideas is always welcome, especially if you have never created one before.

Background

This article is not an introduction to WCF. It is assumed that the reader is familiar with the basic concepts underpinning WCF and where and why you would use a WCF service. If you are unfamiliar with the WCF basics then there are plenty of articles on this forum that should get you up to speed.

I have uploaded a complete WCF solution onto Github that includes a WCF client, service and host for a fictitious Bailiff company (now referred to as Enforcement Agents). The client and host are both very simple console applications that are purely for demonstration purposes. The WCF service (called BailiffServices ) follows the same structure and design that I am about to describe in this article so feel free to have a look at the solution for further clarification on anything I describe here in the article.

Basic structure of a WCF service

I have followed a 3-tier architecture. The WCF service defines the business tier and the data tier. The WCF client (which is out of the scope of this article) defines the UI and consumes the services provided by the WCF service.

  • UI layer - defined and implemented by the WCF client application(s) (out of the scope of this article)
  • Business layer - defined and implemented by the WCF service (and will be described in this article)
  • Database layer - defined and implemented by the WCF service (and will be described in this article)

The following screen shot shows the overall structure of the WCF service. It primarily consists of three projects, each of which has an associated test harness project (that exercises the functionality contained within that project). The total number of projects comprising the solution are therefore six.

Image 1

  • BusinessTier - is the main project to the solution and implements the WCF interfaces and service contracts. All business logic is encapsulated within this project
  • DataTier - provides the data handling functionality and communicates with the backend database system. All database related functionality is encapsulated within this project.
  • Common - provides shared (or common) functionality that is used by both the BusinessTier and DataTier projects.

In addition to these projects the solution also contains test harness projects for exercising the functionality of the other projects. Any unit testing framework can be used, but in the code that I have uploaded to Github I have used NUnit.

  • BusinessTier.Tests - test harness project for exercising the functionality contained within the BusinessTier project
  • DataTier.Tests - test harness project for exercising the functionality contained within the DataTier project
  • Common.Tests - test harness project for exercising the functionality contained within the Common project

Before going into any further detail about the structure of each of the projects, it is worth pointing out that I use one file per class definition.

Each of the three main projects (BusinessTier, DataTier and Common) contains a file with a .snk extension. This is the file that is created when you sign the assembly. Only signed assemblies can be placed in the Global Assembly Cache (GAC). However it is not a requirement to sign your assemblies and add them to the GAC.

Building the WCF service

The solution also includes a build script as can be seen in the screen shot below.

Image 2

For these build scripts to run you will need to have Nant installed on your PC.

If running the build locally from your development PC then you run the batch file PrepareBuild.Build.bat first (which in turn executes the script PrepareBuild.Build.xml). This creates a folder with the same name as your solution folder but with a .Build extension e.g. BailiffServices.Build. You then run the batch file FullBuild.Build.bat from the BailiffServices.Build folder. If you are using a Continuous Integration server such as CruiseControl.NET then you can hook up your WCF service by attaching the file FullBuild.Build.xml (which is in fact an Nant build script).

The Business Tier

The BusinessTier project is the business tier layer of the 3-tier architecture. As such it defines the business rules for the application. Within the context of a WCF service it defines the interfaces and service contracts. The screen shot below shows the folder structure of the project.

Image 3

The project is organised by function. There are therefore folders relating to the interfaces, service contracts and helper functions.

  • Helpers - Contains the definitions of the helper functions. This is a design pattern used throughout the solution to allow for the definition of loosely coupled objects. The majority of them are defined within the Common project as they are shared by both the BusinessTier and DataTier projects.
  • Interfaces - Contains the definitions of the WCF service interfaces.
  • Services - Contains the definitions of the WCF service contracts including the base service contract class ServiceBase.
  • app.config - Defines the configuration required by the WCF service and defines such things as the bindings, services, behaviours and so on. When the WCF service is published this file forms the application web.config.

Helpers

This folder contains a single file called DatabaseManager.cs. This provides the single point of communication between the business tier and the data tier i.e. the BusinessTier project and the DataTier project. All database communication required by the business tier logic is routed through the DatabaseManager helper class.

The Data Tier

The DataTier project is the data tier layer of the 3-tier architecture. As such it defines and implements all database interactions for the application such as all database queries and communication with the backend database. The screen shot below shows the folder structure of the project.

It should be noted that all database functions invoke stored procedures. There are no SQL statements contained within the application. A change in a SQL query should not involve a recompilation and redeployment of your WCF service. All your SQL queries should be encapsulated within your database.

Image 4

All the database interactions for a particular entity (Client, Debtor) are defined within a single class and file.

The app.config contains only the connection string required by the WCF service. The base data tier class DataTierBase is also defined in this project.

C#
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="sqlserver1" connectionString="Data Source=server-sql;Initial Catalog=bailiff;User ID=bailiffservices;Password=********;" providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>

Common code

The Common project contains the common (or shared) code that is used by both the BusinessTier and DataTier projects.

Image 5

The project is organised by function. There are therefore folders relating to attributes, entities, enums, extensions and helper classes.

  • Attributes - Contains the definitions of the Attribute classes used by the application.
  • Entities - Contains the definitions of the entity classes used by the application. These are the classes that relate to the business domain of the application and include such classes as ClientEntity, DebtorEntity etc. It is instances of these classes that are returned to the WCF client application.
  • In an accounting system you would expect to find entity classes that related to PurchaseOrderEntity, JournalEntity etc.
  • Enums - Contains the definitions of the Enum classes used by the application.
  • Extensions - Contains the definitions of the Extension classes used by the application.
  • Helpers - Contains the definitions of the Helper classes used by the application.
  • app.config - Defines the configuration required by the project. In this case it is the names of the application's error log and information log.
  • ReflectPropertyInfo.cs - Contains the definition of the class ReflectPropertyInfo which maps an instance of an entity class to its SqlDataReader representation.
  • UnexpectedServiceFault.cs - Contains the definition of the class UnexpectedServiceFault which is used as part of the exception handling.
C#
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ErrorLog" value="bailiffservices_Error.log"/>
    <add key="InfoLog" value="bailiffservices_Info.log"/>
  </appSettings>
</configuration>

A detailed walkthrough of a service

Now that we have seen the overall solution structure and how the various folders and files are organised, it will prove useful to see how it all works. We will do this by taking one of the WCF services, and following it all the way through to see how / when the various parts get invoked and how they inter-relate together. For the purposes of clarity I have removed some sections of code to make the explanation as simple to understand as possible. The full source code can be found on Github.

I will use the WCF service GetClientInfo() for these purposes.

C#
[ServiceContract]
public interface IClient
{
    [OperationContract, FaultContract(typeof(UnexpectedServiceFault))]
    ClientEntity GetClientInfo(string clientid);
}

The function GetClientInfo() returns an instance of the type ClientEntity. For any service contracts that need to return an entity (which are in fact all POCO type classes), then they are all defined in the Entities folder of the Common project. The ClientEntity class is defined in the file ClientEntity.cs.

C#
public ClientEntity GetClientInfo(string clientid)
{
    try
    {
        DatabaseManager dm = new DatabaseManager();
        return dm.GetClientInfo(clientid);
    }
    catch (Exception ex)
    {
        throw new FaultException<UnexpectedServiceFault>(
            new UnexpectedServiceFault { ErrorMessage = ex.Message, Source = ex.Source, StackTrace = ex.StackTrace, Target = ex.TargetSite.ToString() }, 
            new FaultReason(string.Format(CultureInfo.InvariantCulture, "{0}", FaultReasons.GetClientInfo)));
    }
}

The service contract implementations are the only places where an exception is thrown. Everywhere else the exception is simply thrown higher up the call stack using the throw keyword. When the exception eventually bubbles up to the service contract then it will be passed to the client for handling. At all stages throughput the exception handling process the exception is logged.

C#
public ClientEntity GetClientInfo(string clientid)
{
    try
    {
        ClientData data = new ClientData();
        ClientEntity result = data.GetClientInfo(clientid);
        return result;
    }
    catch (Exception ex)
    {
        throw;
    }
}
C#
public ClientEntity GetClientInfo(string clientid)
{
    try
    {
        ClientEntity result = null;
        SqlConnection connection;
        using (connection = new SqlConnection(ConnectionString))
        {
            connection.Open();
            SqlCommand cmd;
            using (cmd = new SqlCommand("sp_get_clientinfo", connection))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@ClientId", clientid);
                SqlDataReader reader = cmd.ExecuteReader();
                if (reader.HasRows)
                {
                    reader.Read();
                    result = ReflectPropertyInfo.ReflectType<ClientEntity>(reader);
                }
            }
            connection.Close();
        }
        return result;
    }
    catch (Exception ex)
    {
        throw;
    }
}

This function executes a stored procedure to retrieve the data that relates to the specified ClientEntity. The SqlDataReader is firstly mapped to an instance of a ClientEntity which is then returned from the function.

  1. The interface definition for GetClientInfo() is defined in Interfaces/IClient.cs in the project BusinessTier
  2. The implementation of the service contract for GetClientInfo() is in the file Services/ClientService.cs in the project BusinessTier
  3. The service contract GetDebtorInfo() invokes the database helper which is a wrapper function to the database code. This database wrapper is in fact the DatabaseManager helper and wraps all database related calls from the BusinessTier to the DataTier. The implementation for DatabaseManager can be found within Helpers/DatabaseManager.cs in the BusinessTier project.
  4. The database helper function GetClientInfo() then calls the database function GetClientInfo() defined within the class ClientData in the file ClientData.cs in the DataTier project.

Once we have queried the database for the appropriate data and returned this back to the client, then the WCF service has completed its task and there is nothing more to do. By looking at just one of the WCF services we have managed to cover the entire WCF solution and see how the various parts work together and how they relate together.

The WCF service also provides the following functionality, which for the sake of clarity have been omitted for this article. However, you can see how they have been implemented by visiting the full source code over on Github.

  • Error logging
  • Information logging

Summary

Hopefully this article has given you a starting point in how you can design your own WCF service. You don't have to copy exactly what I have described here, but feel free to use whichever parts are appropriate to your own WCF service application(s). Feel free to leave a comment if you would like me to further elaborate on anything within this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)