Click here to Skip to main content
15,878,959 members
Articles / Web Development / ASP.NET

Centralizing WCF Client Configuration in a Class Libary

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
3 Dec 2015CPOL10 min read 30.9K   425   19   6
This article describes how to put your WCF client configuration in a class library's config file instead of client application's config file

Introduction

Here is a common scenario: I have a client application, which consumes business logic written in a different class library project.  The class library has a service reference to a WCF service through either Visual-Studio-generated proxy classes or ChannelFactory.  When the service reference is added to the class library, Visual Studio generates a bunch of configuration under system.serviceModel section of the class library project's config file.  During runtime, unfortunately, the application will not pick up the configuration from this file.  Instead, it will try to pick the configuration from the config file of the client application such as the app.config or web.config.  Obviously, this will throw an error, since the configuration is missing in that file.  One workaround is to copy the system.serviceModel configuration from the class library's config file to the client application's config file.  While this might look to be a an immediate solution, it can be a maintenance headache if you have multiple client applications that use the same business logic library, where you would essentially end up duplicating the same configuration in each client application.

Background

In one of the projects that I had to recently work, I had 9 client applications, all of which consumed the same business logic layer.  This business logic layer was in turn calling a WCF service.  The first idea was to copy the WCF client configuration in every client application, but that obviously was identified to be a maintenance nightmare, so I had to find a way to centralize the configuration in a class library (the BLL) and consume that class library in the client application.  I googled a bit, found some resources, but most of them discussed the concept partially, provided code samples partially, which when I sat to implement kept throwing some kind of error.  So, I decided to write a simple-to-understand step-by-step guide that described everything in detail so that somebody else might find it helpful.

Solution

Fortunately, there is a way to solve this issue and let the WCF proxy pick the configuration from class library's config file.  This way we can centralize the service configuration and proxy generation at a single place.  I am going to demonstrate two methods here of how this can be achieved:

1.  Through auto-generated generated proxy class through Visual Studio Wizard.

2.  Implementing your own proxy class.

I will separate those sections at appropriate place down the page as Part I and Part II.

In either case, I will follow a step-by-step approach that guides you through the entire process.  So, let us start by creating a fresh solution, where we are creating a books service.  The service just returns a list of hard-coded book names.  In real practice, this might come from a database, etc., but to demonstrate the concept, a simple list would be enough.

First of all, create a new WCF Service Library project in Visual Studio and name the project as MyService and the solution as CentralizeWCFConfigDemo:

Image 1

Delete both IService1.cs and Service1.cs files from the Solution Explorer.  We will create new classes from scratch.  We will build a book service that returns a list of books with their names and IDs.

Add a new solution folder and name it "Server."  Add a new interface file to the project and name it IBookService.  Decorate the interface with [ServiceContract].  Add a method called GetAllBooks() and decorate the method with [OperationContract].  Additionally, add a new class and name it Book.  This will function as a data transfer object that holds the book information.  The interface definition looks like below:

C#
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace MyService
{
    [ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IList<Book> GetAllBooks();
    }

    /// <summary>
    /// Book DTO
    /// </summary>
    [DataContract]
    public class Book
    {
        [DataMember]
        public int BookId { get; set; } 
        [DataMember]
        public string BookName { get; set; }
    }
}

Add a new class file to the project and name it BookService.  This class will implement the IBookService we created above.  The implementation is given below:

C#
using System.Collections.Generic;
namespace MyService
{
    public class BookService : IBookService
    {
        public IList<Book> GetAllBooks()
        {
            return new List<Book>
            {
                new Book { BookId = 1, BookName = "Gold of Small Things"},
                new Book { BookId = 2, BookName = "The Guide"},
                new Book { BookId = 3, BookName = "Midnight's Children"},
                new Book { BookId = 4, BookName = "Wings of Fire"},
                new Book { BookId = 5, BookName = "My Experiements with Truth"},
            };
        }
    }
}

Delete everything inside the app.config of your MyService project and paste the content given below.  Here, we have changed the service name, baseAddress, and endpoint to make it point to the BookService that we created above instead of the default  Service1 that was auto-generated by Visual Studio.

XML
<?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="MyService.BookService">
        <host>
          <baseAddresses>
            <add baseAddress = "http://localhost:8733/Design_Time_Addresses/MyService/BookService/" />
          </baseAddresses>
        </host>
        <!-- Service Endpoints -->
        <!-- Unless fully qualified, address is relative to base address supplied above -->
        <endpoint address="" binding="basicHttpBinding" contract="MyService.IBookService">
          <!--
              Upon deployment, the following identity element should be removed or replaced to reflect the
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity
              automatically.
          -->
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <!-- Metadata Endpoints -->
        <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
        <!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information,
          set the value below to false 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>

What we have done so far is that we have created a WCF service, which is going to run on the server side.  We created a new small service named BookService in it.  Now we are going to call this service from a class library on the client side, which is described below:

Now add a new Solution folder and name it "Client." We will be putting our client-side code in this folder.  Right-click this folder and add a new class library project.  Name the project as BusinessLogicLayer.  This class will work as the business logic layer for our application.  This is where we are going to add a reference to the WCF service that we created in the previous section.

Image 2

Delete the auto-created Class1.cs.  Add a new class file and name it BookManager.cs.

Note:  As mentioned above, I am demonstrating here two ways through which you can consume the WCF service in the class library.  The first is by adding a service reference using the Visual Studio wizard, which will auto-generate a proxy class.  The second is to manually create a proxy class.  Here I am splitting the remaining portion of this article into two as I originally mentioned above in this article.

Part I - Using Visual Studio Generated Proxy

Add a service reference to the service we just created above.

Image 3

In the Add Reference Wizard, click Discover, which will list all the services available in the Solution, give the name as "MyService" and click OK.

Image 4

This will add a new service reference to the BusinessLogicLayer project.  It will also add a new app.config file to the project, which will hold essential WCF client-side configuration.

Now we are going to add a new method inside the BookManager class we earlier wrote.  Add a new method called GetMyBooks.  Also add a class called Book.  The definition looks like the following.  Please note that we will later change the definition of the GetMyBooks method.

C#
using System.Collections.Generic;
using System.Linq;
namespace BusinessLogicLayer
{
    public class BookManager
    {
        public List<Book> GetMyBooks()
        {
            var client = new MyService.BookServiceClient();
            var apiResult = client.GetAllBooks();
            return apiResult.Select(x => new Book { BookId = x.BookId, BookName = x.BookName }).ToList();
        }
    }
}


//Book class
public class Book
{
        public int BookId { get; set; }
        public string BookName { get; set; }
}

Now we have to create a client application that consumes our business logic layer.  For this, right-click the Client solution folder and add a new console application.  We will call this TestApp.  Add a reference to the BusinessLogicLayer project.  Inside the Main method, write code to invoke the GetMyBooks method in the business logic layer.  The code looks like the following:

C#
using System.Collections.Generic;
using System.Linq;

namespace BusinessLogicLayer
{
    public class BookManager
    {
        public List<Book> GetMyBooks()
        {
            var client = new MyService.BookServiceClient();
            var apiResult = client.GetAllBooks();
            return apiResult.Select(x => new Book { BookId = x.BookId, BookName = x.BookName }).ToList();
        }
    }
}

Now, set the TestApp as startup project and try to run the solution.  Oops, it throws the following error:

Image 5

The error says the endpoint configuration was not found.  This occurs because the TestApp console application tries to locate the WCF configuration inside the TestApp's app.config file.   In fact, currently, the configuration lies in the class library's configuration file.  Now, we are going to make the application read the configuration from the class library's configuration file so that multiple client applications can centralize the business logic and service configuration in a single place - the class library.

To achieve this, we are going to make some additions and modifications to the class library.  First of all, let us rename the app.config file to BusinessLogicLayer.dll.config.  Right-click the configuration file in the BusinessLogicLayer class library in solution explorer and rename it to BusinessLogicLayer.dll.config.  On the properties window, change the Build Action setting to "Content" and Copy to Output Directory setting to "Copy Always".  This will make sure that the config file is copied to the ouput directory (usually the bin folder) of the client application and make the config file available at runtime.

Add a reference to the System.Configuration assembly so that we will be able to read the configuration file from within the class library.  Then, add a new class file to the same project and name it ServiceManger.  Add the following code to it:

C#
using System.Configuration;
using System.Reflection;
using System.ServiceModel.Configuration;

namespace BusinessLogicLayer
{
    public class ServiceManager
    {
        public static T CreateServiceClient<T>(string configName)
        {
            string _assemblyLocation = Assembly.GetExecutingAssembly().Location;
            var PluginConfig = ConfigurationManager.OpenExeConfiguration(_assemblyLocation);
            ConfigurationChannelFactory<T> channelFactory = new ConfigurationChannelFactory<T>(configName, PluginConfig, null);
            var client = channelFactory.CreateChannel();
            return client;
        }
    }
}

The above code is used to create a dynamic WCF service proxy, which will read the configuration from the class library's config file instead of the client application's config file.  The CreateServiceClient method then will return the newly created client object.  Note that we are not going to directly use the MyService proxy object we earlier created via Visual Studio service reference wizard any more.  The service reference was added just to make all the supported WCF service operations and the IBooServiceChannel interface available to the client application and additionally make use of the intellisense at coding time.

Now let us rewrite the GetMyBooks method to make it use the new dynamic proxy.  Go to the BookManager class and change the following line from:

C#
var client = new MyService.BookServiceClient();

To:

C#
var client = ServiceManager.CreateServiceClient<MyService.IBookServiceChannel>("BasicHttpBinding_IBookService");

Also note that the string "BasicHttpBinding_IBooService" is the configuration name as specified in the BusinessLogicLayer.dll.config file.  The full code looks like the following:

C#
using System.Collections.Generic;
using System.Linq;

namespace BusinessLogicLayer
{
    public class BookManager
    {
        public List<Book> GetMyBooks()
        {
            var client = ServiceManager.CreateServiceClient<MyService.IBookServiceChannel>("BasicHttpBinding_IBookService");
            var apiResult = client.GetAllBooks();
            return apiResult.Select(x => new Book { BookId = x.BookId, BookName = x.BookName }).ToList();
        }
    }
}

Now, run the application.  You can see that the application picks up the configuration from the class library's config file and executes correctly.

Part II - Implementing Your Own Proxy Class

Now let us look into the second method of consuming WCF services - implenting your own proxy class, but still using the centralized configuration.  In this method, we do not add a service reference to the project via Visual Studio wizard in Solution Explorer at all.  So we are going to delete the MyService reference in the BusinessLogicLayer class library project under Service References folder, but before doing that, copy everything in the BusinessLogicLayer.dll.config file to clipboard because deleting the service reference might also clear the serviceModel section from this file.  Once the service reference is deleted, check if the settings got cleared from BusinessLogicLayer.dll.config as part of the removal of the service reference, and if it did, paste the already copied content to the BusinessLogicLayer.dll.config.

At this point, since we have removed the reference of service from the class library, we are no longer provided with the luxury of intellisense that the WCF service supports.  If you look at the GetMyBooks method in the BookManager class in your client application, you can see that VS complains that it cannot find MyService namespace.  So we need to create a class that acts as a proxy to call the WCF method.  This class should refer to the service library inside the WCF project.  To make it happen, right click the BusinessLogicLayer project and "Add Reference…" to the MyService WCF project (note that this time this is not a service reference, but a class library reference).  Once you have done this, add a new class file to the project and name it "MyServiceProxy."  Make this class inherit from ClientBase<MyService.IBookService>, MyService.IBookService.  Also, right-click the MyService.IBookService and choose Implement Interface command from the menu.  Modify the generated code so that it looks like the following:

C#
using System.Collections.Generic;
using System.ServiceModel;

namespace BusinessLogicLayer
{
    public class MyServiceProxy : ClientBase<MyService.IBookService>, MyService.IBookService
    {
        public IList<MyService.Book> GetAllBooks()
        {
            return this.Channel.GetAllBooks();
        }
    }
}

What this this code does is that it creates a proxy class, which inherits from the ClientBase of type IBookService.  We also have implemented the interface so that it internally calls the exposed service methods that the service channel offers.

As the next step add one interface to the project, whose definition looks like the following:

C#
namespace BusinessLogicLayer
{
    public interface IBookServiceChannel : MyService.IBookService, System.ServiceModel.IClientChannel
    {
 
    }
}

This is the interface that we are going to pass into the CreateServiceClient method.  To do that, come back to the BookManager class and modify the GetMyBooks method from:

C#
var client = ServiceManager.CreateServiceClient<MyService.IBookServiceChannel>("BasicHttpBinding_IBookService");

To:

C#
var client = ServiceManager.CreateServiceClient<IBookServiceChannel>("BasicHttpBinding_IBookService");

Now, try running the code, and if you have done everything as described, you should be able to call the GetAllBooks service method successfully.

Using the code

This article is accompanied by working examples of two methods of consuming WCF service (of course there may be more methods, but I cocentrate on two), which can be downloaded as attachments.

History

Article created on Dec 01, 2015.

License

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


Written By
Architect
India India
Nejimon CR started coding in early 2000s with classic Visual Basic 6 and later moved to .NET platform. His primary technology stack expertise is around Microsoft technologies, but with the previous and latest areas of functioning to include a wide variety of technologies such as Win32 APIs, AutoIt scripting, UI Automation, ASP.NET MVC and Web API, Node.js, NoSQL, Linq, Entity Framework, AngularJS, etc.

His Articles on CodeProject:
http://www.codeproject.com/Articles/1060520/Centralizing-WCF-Client-Configuration-in-a-Class-L
http://www.codeproject.com/Articles/567356/Asynchronous-Access-of-Web-Service-from-WPF-with-B
http://www.codeproject.com/Articles/63849/Serial-Foot-Pedal-Device-Server
http://www.codeproject.com/Tips/149249/Simplest-way-to-implement-irregular-forms-in-NET
http://www.codeproject.com/Tips/564388/Entity-Framework-Code-First-Navigation-Property-is

Comments and Discussions

 
QuestionHow do I handle duplex communication ? Pin
Member 134862607-Feb-18 2:55
Member 134862607-Feb-18 2:55 
One thing to note here is "ConfigurationChannelFactory" class constructor doesnot not accept callback instance. So how do we implement callback while not using the the .config file explicitly ?
QuestionNo attachements Pin
saturn_13-Dec-15 15:43
saturn_13-Dec-15 15:43 
AnswerRe: No attachements Pin
Nejimon CR3-Dec-15 23:12
Nejimon CR3-Dec-15 23:12 
QuestionI would like to try this out but..... Pin
TomLeonard1-Dec-15 13:11
professionalTomLeonard1-Dec-15 13:11 
AnswerRe: I would like to try this out but..... Pin
Nejimon CR3-Dec-15 23:13
Nejimon CR3-Dec-15 23:13 
GeneralMy vote of 5 Pin
Mevin Mathew30-Nov-15 22:03
Mevin Mathew30-Nov-15 22:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.