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:
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:
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace MyService
{
[ServiceContract]
public interface IBookService
{
[OperationContract]
IList<Book> GetAllBooks();
}
[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:
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.
="1.0"="utf-8"
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<services>
<service name="MyService.BookService">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8733/Design_Time_Addresses/MyService/BookService/" />
</baseAddresses>
</host>
<endpoint address="" binding="basicHttpBinding" contract="MyService.IBookService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<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.
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.
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.
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.
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();
}
}
}
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:
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:
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:
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:
var client = new MyService.BookServiceClient();
To:
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:
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:
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:
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:
var client = ServiceManager.CreateServiceClient<MyService.IBookServiceChannel>("BasicHttpBinding_IBookService");
To:
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.