BT

Sharing Code in WCF without Code Generation

Posted by Jonathan Allen on Jan 23, 2013 |

One of the principal problems with normal WCF development is code reuse. No matter how well you design your classes on the server, once the proxy generator has touched them you get nothing but simple DTOs. This article shows how to bypass the proxy generator so your client and server can share code.

For the sake of argument we will be using this service interface in the examples that follow.

[ServiceContract(Namespace = "https://zsr.codeplex.com/services/")]
public interface IInformationService
{

      [OperationContract]
     Task<ZombieTypeSummaryCollection> ListZombieTypes();

     [OperationContract]
     Task<ZombieTypeDetails> GetZombieTypeDetails(int zombieTypeKey);

     [OperationContract]
     Task<int> LogIncident(SessionToken session, ZombieSighting sighting);
}

Every method returns a Task or Task<T> in order to support the async/await keywords in .NET 4.5.

Reasons to Not Use the Proxy Generator

Immutables and Data Contracts

At this point it is pretty well established that immutable objects are less error prone. Unless the code that consumes the class actually needs to directly edit a property, it should be marked as read-only so mistakes don’t occur.

Here is an example class that would be bound to a read-only display:

using System;
using System.Runtime.Serialization;

namespace Zombie.Services.Definitions
{
   [DataContract(Namespace = "https://zsr.codeplex.com/services/")]
   public class ZombieTypeSummary
   {
   public ZombieTypeSummary(string zombieTypeName, int zombieTypeKey, string briefDescription = null, Uri thumbnailImage = null)
      {
         ZombieTypeName = zombieTypeName;
         ZombieTypeKey = zombieTypeKey;
         BriefDescription = null;
         ThumbnailImage = thumbnailImage;
      }

      [Obsolete("This is only used by the DataContractSerializer", true)]
      public ZombieTypeSummary() { }

      [DataMember]
      public string ZombieTypeName { get; private set; }

      [DataMember]
      public int ZombieTypeKey { get; private set; }

      [DataMember]
      public string BriefDescription { get; private set; }

      [DataMember]
      public Uri ThumbnailImage { get; private set; }

   }
}

You will notice one strange thing in the code above. It has a public constructor that has been marked obsolete. Even though WCF doesn’t actually invoke this constructor when deserializing object, it still requires it to exist. Add in a few attributes so WCF knows which fields should go over the wire and we’re done.

If we look at the proxy service, we see something only vaguely resembling what we started with:

[DebuggerStepThroughAttribute()]
[GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[DataContractAttribute(Name = "ZombieTypeSummary", Namespace = "https://zsr.codeplex.com/services/")]
[SerializableAttribute()]
[KnownTypeAttribute(typeof(ZombieTypeDetails))]
public partial class ZombieTypeSummary : object, IExtensibleDataObject, INotifyPropertyChanged
{
   [NonSerializedAttribute()]
   private ExtensionDataObject extensionDataField;

   [OptionalFieldAttribute()]
   private string BriefDescriptionField;

   [OptionalFieldAttribute()]
   private Uri ThumbnailImageField;

   [OptionalFieldAttribute()]
   private int ZombieTypeKeyField;

   [OptionalFieldAttribute()]
   private string ZombieTypeNameField;

   [BrowsableAttribute(false)]
   public ExtensionDataObject ExtensionData
   {
      get { return this.extensionDataField; }
      set { this.extensionDataField = value; }
   }

   [DataMemberAttribute()]
   public string BriefDescription
   {
      get { return this.BriefDescriptionField; }
      set
      {

   if ((object.ReferenceEquals(this.BriefDescriptionField, value) != true))
         {
            this.BriefDescriptionField = value;
            this.RaisePropertyChanged("BriefDescription");
         }
      }
   }

   [DataMemberAttribute()]
   public Uri ThumbnailImage
   {
      get { return this.ThumbnailImageField; }
      set
      {

   if ((object.ReferenceEquals(this.ThumbnailImageField, value) != true))
         {
            this.ThumbnailImageField = value;
            this.RaisePropertyChanged("ThumbnailImage");
         }
      }
   }

   [DataMemberAttribute()]
   public int ZombieTypeKey
   
   {
      get { return this.ZombieTypeKeyField; }
      set
      {
         if ((this.ZombieTypeKeyField.Equals(value) != true))
         {
            this.ZombieTypeKeyField = value;
            this.RaisePropertyChanged("ZombieTypeKey");
         }
      }
   }

   [DataMemberAttribute()]
   public string ZombieTypeName
   {
      get { return this.ZombieTypeNameField; }
      set
      {

   if ((object.ReferenceEquals(this.ZombieTypeNameField, value) != true))
         {
            this.ZombieTypeNameField = value;
            this.RaisePropertyChanged("ZombieTypeName");
         }
      }
   }

   public event PropertyChangedEventHandler PropertyChanged;

   protected void RaisePropertyChanged(string propertyName)
   
{
      PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
      if ((propertyChanged != null))
      {
         propertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}

Sidebar: Performance and PropertyChangedEventArgs

Let’s say we are actually using a mutable property. Another performance problem can come from creating instances of PropertyChangedEventArgs. Individually, each one is incredibly cheap. The strings used to populate them are interned so all you are paying for is a single allocation per event.

The sticking point is of course “per event”. If you have lots of events being fired, you are going to be creating unnecessary memory pressure and more frequent garbage collection cycles. And if the events cause other objects to be allocated, you are interleaving short and long-lived objects. Usually this isn’t a problem, but in performance sensitive applications it can be. Instead you’ll want to cache the event args as shown below:

static readonly IReadOnlyDictionary<string, PropertyChangedEventArgs> s_EventArgs =
   Helpers.BuildEventArgsDictionary(typeof(ZombieSighting));

void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
   OnPropertyChanged(s_EventArgs[propertyName]);
}

public DateTimeOffset SightingDateTime
{
   get { return m_SightingDateTime; }
   set
   {
      if (m_SightingDateTime == value)
         return;
      m_SightingDateTime = value;
      OnPropertyChanged();
   }
}

It is rather surprising the proxy generator didn’t create its own event args cache. It wouldn’t even need a dictionary lookup, it could just emit static fields such as this:

static readonly PropertyChangedEventArgs s_SightingDateTime = new PropertyChangedEventArgs("SightingDateTime");

Validation, Calculated Properties, and the Like

Using traditional proxy services, validation methods, calculated properties, and the like tend to be shared via copy-and-paste. This can be error prone, especially if the code base is undergoing a lot of churn. They can be shared using partial classes in separate files, with some of the files shared. This makes it less error prone, but there are still severe limitations to the technique.

A well designed code generator (e.g. ADO.NET Entity Framework) will create “XxxChanging” and “XxxChanged” partial methods. This allows developers to inject additional logic in the property setter. Unfortunately the proxy generator doesn’t do this, forcing the developer to attach property changed listeners in the constructor and the OnDeserialized method.

Another problem is declarative validation cannot be shared between the client and server. Since the proxy generates the properties, there is no place to add the appropriate attributes.

Collections

As any WCF developer will tell you, the proxy generator completely disregards collection types. The client can choose between arrays, lists, and observable collections but any type specific collection will be lost. In fact, as far as the WCF proxy generator is concerned, all collections might as well be exposed as IList<T>.

Bypassing the proxy generator fixes this problem, but introduces some new problems. Specifically, you cannot use the DataContract attribute on collections. This means collections cannot have any serialized properties, a rather unfortunate design decision considering SOAP is XML based and XML is more than capable of expressing the concept of a collection with attributes/properties.

If you can recalculate all of the collection’s properties from its child items, you can regenerate them on the fly. Otherwise, you’ll have to separate the class into separate normal and collection classes.

Code Generation

A major source of preventable development bugs is the proxy code generator itself. Since it requires the server to be running when the proxy is updated, it is difficult to integrate into the normal build process. Instead the developer has to manually invoke the update, a task easily overlooked. While this is unlikely to create production problems, it can waste developer time trying to figure out why service calls suddenly stopped working.

Implementing Proxyless WCF

The basic design pattern is so simple it is a wonder why the proxy generator even exists. (Well not entirely; proxy generation is still needed when consuming a non-WCF service.) As you can see, all you need to do is subclass ClientBase with the service interface you wish to implement and expose the Channel property. The constructors are recommended, but optional.

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace Zombie.Services.Definitions
{
   public class InformationClient : ClientBase<IInformationService>
   {
      public new IInformationService Channel
      {
         get { return base.Channel; }
      }

      public InformationClient()
      {
      }

      public InformationClient(string endpointConfigurationName) :
         base(endpointConfigurationName)
      {
      }

   public InformationClient(string endpointConfigurationName, string remoteAddress) :
         base(endpointConfigurationName, remoteAddress)
      {
      }

   public InformationClient(string endpointConfigurationName, EndpointAddress remoteAddress) :
         base(endpointConfigurationName, remoteAddress)
      {
      }

   public InformationClient(Binding binding, EndpointAddress remoteAddress) :
         base(binding, remoteAddress)

      {
      }
   }
}

Adding Support for Dependency Injection

A nice side effect of this pattern is it easily supports dependency injection for the purpose of unit testing. To do this, we first need a constructor that accepts the service interface. Then we override or shadow some of the methods exposed by ClientBase.

private IInformationService m_MockSerivce;
public InformationClient(IInformationService mockService)
   : base(new BasicHttpBinding(), new EndpointAddress("http://fakeAddress.com"))
{
   m_MockSerivce = mockService;
}

public new IInformationService Channel
{
   get { return m_MockSerivce ?? base.Channel; }
}

protected override IInformationService CreateChannel()
{
   return m_MockSerivce ?? base.CreateChannel();
}

public new void Open()
{
   if (m_MockSerivce == null)
      base.Open();
}

The astute reader will notice this isn’t the cleanest API and leaves some vulnerabilities. For example, a QA developer could cast down to the base class and call the real Open method directly. As long as this is a known limitation, mistakes should be rare. And with the fake address, there is no chance of it actually connecting to a real server.

Partial Code Sharing Options

The default option for code sharing between a .NET server and .NET or WinRT client is to simply share an assembly reference. But there will be times when you want to only share part of a class between server and client. There are two ways to do it:

Option 1 is to use linked files with conditional compilation directives. This has the advantage of putting everything in one place, but can be quite cluttered.

Option 2 also uses linked files; but instead of conditional compilation you use a partial class across multiple files. One file is shared, while another has client-only or server-only code.

Silverlight Considerations

This pattern can be used with Silverlight, but there are additional considerations. First of all, Silverlight’s version of WCF requires all service methods to be written in the old IAsyncResult style.

[ServiceContract(Namespace = "https://zsr.codeplex.com/services/")]
public interface IInformationService
{
   [OperationContractAttribute(AsyncPattern = true)]
   IAsyncResult BeginListZombieTypes(AsyncCallback callback, object asyncState);

   ZombieTypeSummaryCollection EndListZombieTypes(IAsyncResult result);

   [OperationContractAttribute(AsyncPattern = true)]
   IAsyncResult BeginGetZombieTypeDetails(int zombieTypeKey, AsyncCallback callback, object asyncState);

   ZombieTypeDetails EndGetZombieTypeDetails(IAsyncResult result);

   [OperationContractAttribute(AsyncPattern = true)]
   IAsyncResult BeginLogIncident(SessionToken session, ZombieSighting sighting, AsyncCallback callback, object asyncState);

   int EndLogIncident(IAsyncResult result);
}

In order to use the new async/await style, you’ll have to rewrap the interface in tasks using the FromAsync function.

public static class InformationService
{
   public static Task<ZombieTypeSummaryCollection> ListZombieTypes(this IInformationService client)
   {

   return Task.Factory.FromAsync<ZombieTypeSummaryCollection>(client.BeginListZombieTypes(null, null), client.EndListZombieTypes);
   }

   public static Task<ZombieTypeDetails> GetZombieTypeDetails(this IInformationService client, int zombieTypeKey)
   {

   return Task.Factory.FromAsync<ZombieTypeDetails>(client.BeginGetZombieTypeDetails(zombieTypeKey, null, null), client.EndGetZombieTypeDetails);
   }

   public static Task<int> LogIncident(this IInformationService client, SessionToken session, ZombieSighting sighting)
   {

   return Task.Factory.FromAsync<int>(client.BeginLogIncident(session, sighting, null, null), client.EndLogIncident);
   }
}

About Zombie Standard Reference

We are in the process of a creating a reference application for demonstrating the differences between various techniques and technologies on the .NET platform. Rather than a typical hello world application, we decided to create the Zombie Standard Reference. This represents a set of applications for reporting zombie sightings, managing inventory (e.g. anti-zombie vaccines), and dispatching investigators. This will allow us to look at databases, mobile applications, geo-location/correction, and a host of other common features useful in real world applications.

As each article is published we will be updating the source code on CodePlex.

About the Author

Jonathan Allen has been writing news report for InfoQ since 2006 and is currently the lead editor for the .NET queue. If you are interested in writing news or educational articles for InfoQ please contact him at jonathan@infoq.com.

 

 

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

F# WSDL TypeProvider is a alternative, in some cases by Faisal Waris

I have used some variant of what you are describing (i.e. share WCF declarations across client and server); this pattern is quite effective and reduces code bloat.

The obvious limitation (as you have mentioned) is that it only works with WCF clients and services.

The F# WSDL TypeProvider uses code generation under the covers (the results can be cached) but all of that is hidden from the developer and it works with non-WCF services, too.

See docs here: msdn.microsoft.com/en-us/library/hh362328.aspx

The F# Type Provider is not meant for implementing service interfaces and so is not suitable for all scenarios (such as mock services).

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

1 Discuss

Educational Content

General Feedback
Bugs
Advertising
Editorial
InfoQ.com and all content copyright © 2006-2014 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT