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

Creating a Generic (Static and Unchanging) WCF Interface

5.00/5 (5 votes)
11 Dec 2012CPOL10 min read 34.3K   430  
Learn how to create a static and unchanging WCF interface that still supports most WCF capabilities.

Introduction

This article describes a framework for a static-interface WCF service. 

At some point or another, everyone who has created a WCF service that is being consumed by a 3rd party is going to have updates to that service that break the compatibility between client and service.  At the very least they will have updates to the data that is flowing through that service, if not the contract itself.  When a WCF contract changes, or when the data flowing through that contract changes, you typically have to rebuild the client and service to include any of the changes.

What I wanted to do with this code sample is to come up with a way to define a WCF interface that would not require changes when the content flowing through it changed, nor when new methods are added.  What I came up with is a WCF contract that defines the bare minimum of data required to identify messages flowing through it- this means that the underlying data can change as often as required, without changes to the WCF interface itself.  Both the client and service can define the level of support that they want to maintain in regards to the overall data, independent of the WCF contract. 

Let’s begin with an example of a typical WCF contract where I am passing a Person class in, and getting an Address class back:

C#
[OperationContract]
Address GetAddress(Person person);

This is all well and good, and works fine, but what happens when I want to change my service, and get an Address back by passing a

Customer
in?  I now have to either change the interface so that it accepts a Customer, or add a whole new interface that supports the new call:

C#
[OperationContract]
Address GetAddress(Customer customer); 

Adding a new interface is the best option, as it doesn't break the compatibility of the WCF contract, however it does still require the client and service to recompile against this updated contract to support the new method.

Note: Changing the name or data type of a member, or removing data members is a breaking change.  Likewise changing the type, name, or parameters of a method is a breaking change.  Breaking changes will require an updated contract on the client and service.

So, you can see that I would have to add a new GetAddress method that takes a Customer object instead of just changing the existing one if I want to maintain compatibility with any of the clients consuming my service.  In the case of this example, this is not an optimal solution, as I don’t want the interface to change when I make ANY changes to my underlying service, including adding a new method. 

Solution  

The solution to this problem is to step away from the world of simple well-defined WCF calls, and into the shady world of custom serialization, message handlers, and other scary stuff.

Not really… actually, it’s quite simple if you use the framework that I’m laying out here. 

The solution that I put together for this example makes some fundamental changes to the WCF contract that we saw above, as well as requiring some changes outside of the WCF calls.

First off, the interface that we were using has to go.  In its place, we have two new methods, which will look like this:

C#
[OperationContract(IsOneWay = true)]
void SendMessageOneWay(int topicId, int messageId, byte[] message); 
C#
[OperationContract]
byte[] SendMessageRequestResponse(int topicId, int messageId, byte[] message); 

Here you can see that we’ve replaced all of the previous GetAddress methods with two generic looking methods.  Each method takes three parameters: a topicId, messageId, and a message.

Note: Both topicId and messageId are integers that map to an enumeration value internally.  They are created as integers to support the addition or removal of values within the internal enumeration without breaking compatibility. 

topicId:  The top-level category of the message being sent.  This serves as a means to organize messages into meaningful groups.  For the purpose of this example, let’s assume that we are using a topic of “Address”.

messageId: The specific message within the topic that was specified.  This identifies the message that will be sent through this method for the purpose of routing it to the correct handler (more on that later).  Message IDs can represent the different messages we want to send, so in this example let’s assume we have two message IDs: “GetAddressFromPerson” and “GetAddressFromCustomer”.

message: A serialized representation of whatever message is being sent over WCF.  (I will cover this later in the Protocol Buffers section)

return value (SendMessageRequestResponse only): A serialized representation of the response message.

For this example, the TopicId and MessageId enumerations contain the following:

C#
public enum TopicId : int
{ 
	Address = 1,
	Person = 2
}

public enum MessageId : int
{
	GetAddressFromPerson = 1,
	GetAddressFromCustomer = 2,
	GetAllAddresses = 3
} 

Note on enumeration types in this context: You might be asking why enumerations are used on either end of the service, but the interface specifies an int for the topicId, and messageId parameters.  The answer is again related to compatibility… if we decouple the method’s interface from a specific type (such as TopicId), we can support backward and forward compatibility on the interface when that type changes.  So, if the service receives a message that doesn’t have a topic or message ID that it knows about, it can simply discard it, or throw an error.  

Protocol Buffers 

 As you’ve no doubt noticed, the “message” parameter in the operation contracts (and the return value on the “SendMessageRequestResponse” method) is an array of bytes.  This array of bytes represents a serialized .NET object, which is being sent or received through WCF. 

In order to make the WCF interface generic and extensible, the objects that we pass back and forth need to be made into some generic format that does not impact the WCF contract when something is added, removed, or changed.  For this example, I am using a protocol buffer that has been serialized to a byte array, but you could use pretty much anything that can be sent over WCF.

What does this mean?  First of all, it means that in order to use this project, you’ll need to add a reference to protobuf-net (I used NuGet to add the reference- since it’s now integrated into VS2012!).  You’ll also need to create protobuf objects to send back and forth.  For example, my Person class looks like this:

C#
[ProtoContract]
public class Address
{
	[ProtoMember(1)]
	public string Address1 { get; set; }

	[ProtoMember(2)]
	public string Address2 { get; set; }

	[ProtoMember(3)]
	public string City { get; set; }

	[ProtoMember(4)]
	public string State { get; set; }
} 

Protocol buffers are very efficient, in both CPU and memory usage, which is why I chose to use it as the serialization engine for this sample.  You can read all about protobuf-net here: http://code.google.com/p/protobuf-net/ 

Using the code 

In this sample, I have included a couple of helper classes designed to ease the pain of moving to a solution such as this. Note that these are only a few of the classes contained within the contracts project. 

  1. IWcfContract.cs
  2. This is the interface that contains the “SendMessageOneWay” and “SendMessageRequestResponse” methods for the WCF service and client.  This file also contains the callback version of the contract.  Note that the names and namespaces have been explicitly set on the contracts contained within so that they are seen as interchangeable by WCF (IWcfContract and IWcfContractWithCallback).   

  3. SerializationHelper.cs
  4. This is a static class that has two methods that allow the serialization or deserialization of an object.

  5. ServiceWrapperBase.cs
  6. This abstract class is meant to be inherited by a class that acts as the service proxy for the WCF service.  It implements the WCF methods “SendMessageOneWay” and “SendMessageRequestResponse”, and provides support for registering handlers for topic and message IDs. 

  7. ClientWrapper.cs
  8. This class takes a reference to the WCF service, and wraps the “SendMessageOneWay” and “SendMessageRequestResponse” methods, and exposes them in a friendlier manner as one “Send” method. 

  9. ClientWrapperBuilder.cs 
  10. This class is a static factory class used to create the above mentioned ClientWrapper class. 

Setting up the service 

To set up the service, you’ll create a class like you would for any WCF service, by decorating it with the ServiceBehavior attribute, like so:

C#
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class MyService
{…} 

Next, make your class implement the ServiceWrapperBase, as follows:

C++
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] 
public class MyService : ServiceWrapperBase<TopicId, MessageId> 
{
     public MyService(bool isDuplex)
         : base(isDuplex)
     { ... } 
}          

Note: The TopicId and MessageId types should be enumeration types that have an underlying type of “int” (see my comments above related to this). 

Now that you have a class set up that implements the ServiceWrapperBase, it’s time to get that class hosted as a WCF service using the following code:

C#
MyService service = new MyService();						
ServiceHost serviceHost = new ServiceHost(service);
serviceHost.Open(); 

Remember: You need to add a reference to System.ServiceModel in order to have the ServiceHost class available. 

Once you’ve done this, and say put the code into a console application, you will have a self-hosted WCF service. 

Registering message handlers 

After you have the code in place to host your service, let’s register some message handlers!  Registering message handlers is how the ServiceWrapperBase class routes incoming messages to the appropriate methods.  For example, if I wanted to receive a message with a TopicId of Address, a MessageId of GetAddressFromCustomer, and a return value of Person, I would register a handler as follows:

First I would create a method to handle the message:

C#
private Address HandleGetAddressFromCustomer(Customer requestMessage)
{…} 

Then I would register the handler method with the ServiceWrapperBase:

C#
RegisterHandler<Customer, Address>(TopicId.Address, MessageId.GetAddressFromCustomer, HandleGetAddressFromCustomer); 

At this point, anything that comes into the WCF service with the above TopicId and MessageId will be routed to the HandleGetAddressFromCustomer method, in which I would do what I like with the incoming Customer data, returning an

Address
object. 

Calling the service 

Calling the service is about as simple as it gets… you’ll need a ChannelFactory to create an instance of IWcfContract, which I will leave as an exercise for the reader. you'll just need to call the appropriate GetClientWrapper factory method on the ClientWrapperBuilder class. 

You can create a new instance of the ClientWrapper class by doing the following: 

C#
clientWrapper = ClientWrapperBuilder.GetDuplexClientWrapper<TopicId, MessageId, IWcfContractWithCallback>("ProtobufSpikeCb");
// or  
clientWrapper = ClientWrapperBuilder.GetSimplexClientWrapper<TopicId, MessageId, IWcfContract>("ProtobufSpike"); 

Note that I am instantiating the ClientWrapper class with three types: TopicId, MessageId, and IWcfContract (or alternatively the IWcfContractWithCallback for duplex)  The TopicId and MessageId serve the same purpose as they do on the service side, and the IWcfContract is specifying the type of the service that you are passing in. 

Note: the

IWcfContract
type that is passed into the ClientWrapperBuilder is constrained within the ClientWrapperBuilder class to only accept types implementing IWcfContract or IWcfContractWithCallback.IWcfContractWithCallback.
 

Once you have an instance of the ClientWrapper, you can call it like this: 

C#
Address response = clientWrapper.Send<Address, Customer>(TopicId.Address, MessageId.GetAddressFromCustomer, new Customer() { Id = "123" });

Note: The above example is for calling the ClientWrapper to send a request/response type of message.  If you want to send a one-way message, simply call the ClientWrapper like so:

C#
clientWrapper.Send<OneWayMessage>(TopicId.Other, MessageId.OneWay, message);\

Duplex - Using the Callback 

The following is an example of how to register for a callback (on the client), and how to call a callback (on the service). 

<p />
C#
if (clientWrapper.IsDuplex)
	clientWrapper.RegisterCallbackHandler<string>(TopicId.Other, MessageId.PrintData, PrintData);   

As you can see above, the ClientWrapper is called to register a callback handler (in this case the callback handler takes one parameter, a string).  The registration method for callbacks is the same as the registration method for the service handlers. 

Calling the callback from the service side is as simple as doing the following inside of your service, which should implement the ServiceWrapperBase class (giving you access to the isDuplex and Callback members). 

C#
if (isDuplex)
      Callback<string>(TopicId.Other, MessageId.PrintData, "Hello there!  This is a callback!", OperationContext.Current); 


Other Comments  

While the generic interface described here defeats the purpose of WCF’s service versioning and compatibility scheme, it has a place in the software that I am working on, and is better than some of the alternatives I’ve looked into.  The real benefit here is that I get a stable and unchanging interface, along with the perks of using WCF.  Perks such as security, reliable transport, and whatever else I’d like to utilize from the WCF stack outside of contracts. 

Points of Interest  

Bonus!  You can use this code to create a WCF service that has a set of features that change dynamically at runtime.  You can register handlers for methods when making them available, and then unregister them when you don’t want them to be available- all without stopping your application, and certainly without recompiling anything.

History 

14NOV2012 - First revision.

11DEC2012 -  Second revision: Added functionality to support duplex WCF channels, as well as certificate-based security for the client.  Refactored the client wrapper with better creation methods,  simplified code all-around. 

License

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