Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#

Generic WCF Service Host and Client

Rate me:
Please Sign up or sign in to vote.
4.96/5 (21 votes)
13 May 2010CPOL5 min read 259.3K   4.7K   118   50
A generic WCF Windows Service host and client with minimal configuration, with asynchronous support.

Introduction

Tired of writing host and client (proxy) code for your common Windows Communication Foundation (WCF) services hosted by a Windows Service using standard binding and transport in code rather than error prone configuration files? I was. Not now. This article presents a solution to the problem: a generic WCF Windows Service host and client proxy class library that will host and allow you to use any WCF service contract you write. You write the service contract and data contract in one class library and implement the operations contract in another. Then modify a simplified configuration section on the generic service and your client, and restart the service. No fuss, no muss.

GenericWcfServiceHostAndClient.png

Background

The Visual Studio project templates and config tool can make writing WCF services easier, but more and more I found I was avoiding those tools and writing it all by hand in order to get exactly what I wanted without all the heavy, sometimes problematic, configuration files. And every time I did that, I realized the steps were identical and tedious. And isn't this why we write code, to avoid repetitive tedium?

So I decided to create a reusable framework for hosting my services. Here's what I wanted:

  • Fast net.tcp binding
  • Windows credentials and authentication
  • Encrypted and signed transport (no packet sniffing allowed)
  • Simplified configuration (hide everything I don't want to see)
  • Windows Service host that behaves like a console app when I'm debugging
  • Dynamic loading of the service (no changes to the host code to add a new service)
  • Generic client so I don't have to write or generate proxy code
  • Client that is truly IDisposable (hide Abort vs. Close for me)
  • Long timeout in DEBUG mode so I can really take my time while debugging
  • Inclusion of exception details in DEBUG mode only
  • Base service class with a simple Authorize method to support multiple Windows groups
  • Support for multiple Windows group authorization
  • Identical configuration for server and client
  • Cached resolution of service plug-in service and contract types
  • Minimal number of assemblies (projects) in the solution
  • Keep the implementation of the service hidden from the client
  • (Probably some I've forgotten to mention)

Using the Code

Once you have defined your contracts, implemented your service operations, and written a tiny bit of configuration, the real fun begins: using the generic client. You use the WcfServiceClient<T>.Create method, passing in the string key for the service you want to use. Then you just use the Instance property of the returned object to use the interface of the service.

C#
namespace WcfSvcTest
{
  class Program
  {
    static void Main(string[] args)
    {
      using (var client1 = WcfServiceClient<IThatOneService>.Create("test1"))
      {
        var name = client1.Instance.GetName("seed");
        Console.WriteLine(name);

        int addressId = 8;
        var address = client1.Instance.GetAddress(addressId);
        Console.WriteLine("{0}", address.City);
      }
 
      using (var client2 = WcfServiceClient<IMyOtherService>.Create("test2"))
      {
        try
        {
          Console.WriteLine(client2.Instance.GetName("newseed"));
          var per = client2.Instance.GetPerson(7);
          Console.WriteLine("{0}, {1}", per.Name, per.Title);
        }
        catch (FaultException<WcfServiceFault> fault)
        {
          Console.WriteLine(fault.ToString());
        }
        catch (Exception e) //handles exceptions not in wcf communication
        {
          Console.WriteLine(e.ToString());
        }
      }
 
      Console.ReadLine();
    }
  }
}

To save space, I wrapped the use of the client in a try catch on the second test service.

The Configuration

One of the critical parts of this generic WCF service host and client is the custom configuration section class WcfServiceConfigurationSection. You can download the code to review it, but this class allows us to define a single, identical config section for both the server and the client. Here's the simplified config with two services configured. The configuration is identical for the service host and the client, greatly simplifying deployment and reducing configuration errors.

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="wcfServices" 
          type="WcfServiceCommon.WcfServiceConfigurationSection, WcfServiceCommon" />
  </configSections>
  <appSettings/>
  <connectionStrings/>
  <wcfServices consoleMode="On">
    <services>
      <add key="test1"
          serviceAddressPort="localhost:2981"
          endpointName="Test1EndPoint"
          authorizedGroups="WcfServiceClients,someOtherGoup"
          hostType="Test1Service.ThatOneService, Test1Service"
          contractType="Test1Common.IThatOneService, Test1Common" />
      <add key="test2"
          serviceAddressPort="localhost:2981"
          endpointName="Test2EndPoint"
          authorizedGroups="WcfServiceClients,someOtherGoup"
          hostType="Test2Service.MyOtherService, Test2Service"
          contractType="Test2Common.IMyOtherService, Test2Common" />
    </services>
  </wcfServices>
</configuration>

Installing the Windows Service

You will note that the <wcfServices> node in the configuration has an attribute called "consoleMode" which tells the generic host to run in console mode when the value is "On" and to otherwise run as a Windows Service. This makes for easy debug sessions. Just set your Visual Studio solution to start the host service before your client starts, and you can debug straight through to your implementation.

To install the service as a Windows Server, you will need to use the InstallUtil.exe in your .NET Framework directory (e.g., C:\Windows\Microsoft.NET\Framework64\v4.0.30319). For help with this utility, see the MSDN article on installutil.

The Service Contract and Implementation

Here's the simple service and data contract:

C#
namespace Test1Common
{
  [ServiceContract]
  public interface IThatOneService
  {
    [OperationContract, FaultContract(typeof(WcfServiceFault))]
    string GetName(string seed);

    [OperationContract, FaultContract(typeof(WcfServiceFault))]
    Address GetAddress(int id);
  }

  [DataContract]
  public class Address
  {
    [DataMember]
    public string Line1 { get; set; }

    [DataMember]
    public string Line2 { get; set; }

    [DataMember]
    public string City { get; set; }

    [DataMember]
    public string State { get; set; }

    [DataMember]
    public string Zip { get; set; }
  }
}

And here's the implementation of the service. It really is simple, but note that it inherits from WcfServiceBase (see below).

C#
namespace Test1Service
{
  public class ThatOneService : WcfServiceBase, IThatOneService
  {
    public string GetName(string seed)
    {
      return "Mr. " + seed.ToUpper();
    }

    public Address GetAddress(int id)
    {
      return new Address 
      { 
        Line1 = "100 Main Street", 
        Line2 = "P.O. Box 100", 
        City = "MallTown", 
        State = "TX", 
        Zip = "12345" 
      };
    }
  }
}

The WcfServiceBase Class

The base class WcfServiceBase provides two very important common requirements for the service implementation. First, you get a ServiceBehavior attribute tacked on with IncludeExceptionDetailInFaults set to true when in DEBUG build. The second is the Authorize method to make authorizing the calling client against the list of authorized groups in the configuration very easy. If the calling client is found in one of those groups, the Authorize method does nothing. Otherwise, an exception is thrown.

C#
namespace WcfServiceCommon
{
#if(DEBUG)
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, 
   MaxItemsInObjectGraph = 131072, IncludeExceptionDetailInFaults = true)]
#else
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, 
   MaxItemsInObjectGraph = 131072, IncludeExceptionDetailInFaults = false)]
#endif
  public abstract class WcfServiceBase
  {
    public void Authorize()
    {
      string[] groups = null;
      Type serviceType = this.GetType();
      var configItem = Config.GetServiceConfig(serviceType);

      if (null != configItem)
      {
        groups = configItem.Item.AuthorizedGroups.Split(',');
      }

      if (null != groups)
      {
        PrincipalPermission[] pps = new PrincipalPermission[groups.Length];
        for (int i = 0; i < groups.Length; i++)
        {
          pps[i] = new PrincipalPermission(null, groups[i]);
        }

        PrincipalPermission pp = pps[0];
        if (groups.Length > 0)
        {
          for (int i = 1; i < groups.Length; i++)
          {
            pp = (PrincipalPermission)pp.Union(pps[i]);
          }
        }
        pp.Demand();
      }
      else
        throw new SecurityException("Group is null");
    }
  }
}

Conclusion

I hope you find this WCF plug-in architecture and the accompanying code useful. I will certainly be using it. If you do use it, please let me know how it goes.

Points of Interest

Note: You have to remember that the WcfServiceHost requires the "common" and the "service" assemblies of your dynamically loaded services in its bin folder. The client (see the WcfSvcTest project in the solution) will also need a copy of the "common" assemblies in its bin folder. You'll find I'm doing that for the test using post-build commands (copy $(TargetPath) $(SolutionDir)WcfServiceHost\bin\debug\). And of course, both need to have identical config sections as shown in the code.

More Important Note: The service host project does not reference your "common" or "service" implementations. They are dynamically loaded using the information in the config file. Your client project will need a reference to the WcfServiceCommon assembly as well as your "common" contracts assembly, of course, but the WcfServiceCommon's generic WcfServiceClient will dynamically load your "common" contracts assembly using the config file information, so the WcfServiceCommon assembly does not require a reference to your "common" contract assembly.

Update: On Making Asynchronous Calls

A user asked about asynchronous usage. Download v2 of the source. Take a look at the updated WcfServiceClient<T> class with an eye to the AsyncBegin method and the AsyncCompleted event. For a more generalized version of this, check out my new blog post: generic asynchronous wrapper.

History

  • 5/3/2010 - Some spelling corrections and one or two grammar and heading fixes in the text. Thanks to my reviewer and editor Charles W.
  • 5/13/2010 - Updates with v2 of source which includes the generic client's asynchronous support.

License

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


Written By
Web Developer
United States United States
Since 2001 I've been writing .NET applications in C# and architecting n-tier applications in the enterprise. Before that I worked as a tech writer for nine years. Don't bother doing the math. I'm old. Ever since I laid eyes on my first Commodore PET, I've been a technologist. I've worked in the software world for fifteen years. I started as a technical writer and learned to code from the best engineers as I worked with them in creating technical documentation. It was then that I learned that writing code was more fun and frankly easier than writing about code. I've been doing both ever since. You can visit my blog at http://www.tsjensen.com/blog.

Comments and Discussions

 
AnswerRe: I send you a screenshot Pin
Tyler Jensen21-May-12 5:27
Tyler Jensen21-May-12 5:27 
QuestionI login just as administrator Pin
liuping102521-May-12 3:52
liuping102521-May-12 3:52 
I did just that.
QuestionThe another case Pin
liuping102521-May-12 3:49
liuping102521-May-12 3:49 
AnswerRe: The another case Pin
Tyler Jensen21-May-12 4:28
Tyler Jensen21-May-12 4:28 
QuestionHow to install the WcfServiceHost Pin
liuping102521-May-12 2:30
liuping102521-May-12 2:30 
AnswerRe: How to install the WcfServiceHost Pin
Tyler Jensen21-May-12 3:21
Tyler Jensen21-May-12 3:21 
Generalglad to meet you Pin
liuping102517-May-12 18:07
liuping102517-May-12 18:07 
QuestionMany thanks Pin
liuping102517-May-12 16:56
liuping102517-May-12 16:56 
AnswerRe: Many thanks Pin
Tyler Jensen17-May-12 17:02
Tyler Jensen17-May-12 17:02 
QuestionHow to pass type parameter to service Pin
liuping102516-May-12 22:17
liuping102516-May-12 22:17 
AnswerRe: How to pass type parameter to service Pin
Tyler Jensen17-May-12 4:18
Tyler Jensen17-May-12 4:18 
AnswerRe: How to pass type parameter to service Pin
Tyler Jensen17-May-12 4:22
Tyler Jensen17-May-12 4:22 
QuestionHow to pass type parameter to service Pin
liuping102516-May-12 22:12
liuping102516-May-12 22:12 
QuestionHow do I use a generic service class Pin
liuping102516-May-12 6:11
liuping102516-May-12 6:11 
AnswerRe: How do I use a generic service class Pin
Tyler Jensen16-May-12 6:33
Tyler Jensen16-May-12 6:33 
QuestionAbout the code zip file Pin
liuping102515-May-12 7:25
liuping102515-May-12 7:25 
AnswerRe: About the code zip file Pin
Tyler Jensen15-May-12 9:37
Tyler Jensen15-May-12 9:37 
AnswerRe: About the code zip file Pin
Tyler Jensen17-May-12 4:15
Tyler Jensen17-May-12 4:15 
QuestionWhy does the wcfServices is null Pin
liuping102515-May-12 5:42
liuping102515-May-12 5:42 
AnswerRe: Why does the wcfServices is null Pin
Tyler Jensen15-May-12 6:50
Tyler Jensen15-May-12 6:50 
QuestionWhy does the wcfServices is null Pin
liuping102515-May-12 5:11
liuping102515-May-12 5:11 
QuestionAbout custom app.config elements Pin
liuping102514-May-12 6:55
liuping102514-May-12 6:55 
QuestionObjectDisposedException in ExecuteAsyncMethod() Pin
G. Filkov21-Dec-11 14:20
G. Filkov21-Dec-11 14:20 
AnswerRe: ObjectDisposedException in ExecuteAsyncMethod() Pin
Tyler Jensen17-May-12 17:07
Tyler Jensen17-May-12 17:07 
SuggestionEasier solution Pin
PKochar11-Oct-11 12:05
PKochar11-Oct-11 12:05 

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.