Understanding WCF Versioning ("My Random Notes" Edition)

I never got around to putting the following into something pretty and eloquent... and might not blog any more, but here are my raw notes I would have used to write something more full.


WCF Service Contract Versioning

Clients calls a service interface, not an implemenation. It identifies a particular interface via the WCF namespace set on the service contract on the service. As long as the client calls an interface with a particular namespace on the service side, the call will be allowed.

Strong Versioning

For example, if a client is using the following service contract, it can only call this contract using its full XML namespace. The namespace is specified with the Namespace property of the ServiceContract attribute. However, the interface that is called is the specified namespace suffixed with the interface name. This gives a feel very similar to that of .NET's namespace system which allows for namespaces for organization and full types as the types namespace suffixed with the type name.

As an example, see the following interface is accessed via the "http://www.abccorp.com/services/sample01/2007/02/IServiceV1" identifier.

[ServiceContract(Namespace="http://www.abccorp.com/services/sample01/2007/01")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(Int32 value);
}

If, however, the month portion of a namespace is changed from version 01 to 02, as the following example demonstrate, the client would immediately break as it would still be calling via the old identifier.

[ServiceContract(Namespace="http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(Int32 value);
}

On the service-side there is no "magic" that allows one service to be access over another. The client accesses a specific interface and that specific iterface is of course assigned as a WCF endpoint. Therefore, each version requires it's own endpoint. However, an endpoint doesn't mean another entire host, power, or base URI. In an HTTP environment, this simply means that the endpoint identifier is suffixed to the actual service URI.

For example, instead an address of "" (blank), the address is an arbitrary endpoint identifier as seen below:

Before

<endpoint address="" binding="basicHttpBinding" contract="IService" />

After

<endpoint address="v1" binding="basicHttpBinding" contract="IServiceV1" />
<endpoint address="v2" binding="basicHttpBinding" contract="IServiceV2" />

This will be mentioned in a bit more detail in a later section.

Lax Versioning

Assume the following service service contract, implementation, and endpoint:

[ServiceContract(Namespace = "http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(Int32 value);
}

public class Service : IServiceV1
{
    public String GetData(Int32 value) {
        return String.Format("You send {0}.", value);
    }
}

<endpoint address="v1" binding="wsHttpBinding" contract="IServiceV1" />

At this point, if the client gets the service metadata and creates a proxy, the client may send the following:

using (ServiceV1Client client = new ServiceV1Client("WSHttpBinding_IServiceV1")) {
    Console.WriteLine(client.GetData(3));
}

Now, assume the service changes from Int32 to String with no version change.

[ServiceContract(Namespace = "http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(String value);
}

public class Service : IServiceV1
{
    public String GetData(String value) {
        return String.Format("You send {0}.", value);
    }
}

With updating the service proxy on the client, the client is strongly typed to an Int32. Therefore, the client will not be able to send a String to the server. However, the client will still function as the service implicitly converts the value to a String.

The reverse of this situation can also be true. If the service started out using a String and was changed to use an Int32, the client, even though it is bound to a String type, can send data to the server as long as it is castable to an Int32. See the following for an example:

// This will work
using (ServiceV1Client client = new ServiceV1Client("WSHttpBinding_IServiceV1")) {
    Console.WriteLine(client.GetData("4"));
}

// This will NOT work
using (ServiceV1Client client = new ServiceV1Client("WSHttpBinding_IServiceV1")) {
    Console.WriteLine(client.GetData("four"));
}

Strong and Lax Together

A service may serve service contracts as the following demonstrates

public class Service : IServiceV1, IServiceV2
{
    // ...
}

The service contracts have the same essence, but their operation signatures will differ.

[ServiceContract(Namespace = "http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(Int32 value);
}

[ServiceContract(Namespace = "http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV2
{
    [OperationContract]
    String GetData(Int32 value, String code);
}

In this case, the namespace is the same, but this is in no way a requirement. Any portion of the namespace can change as they are two completely different contracts.

These two contracts also share a method with the same name. This is legal in .NET, but there is no such thing as "service overloading". That is, the overloading is only in .NET and does not transfer to the service. Therefore, as has been previously mentioned, each contract must have its own endpoint. Given that each endpoint is for the same service, only the following is requires:


When a client will specify which endpoint to call when it creates its service proxy. For example,

using (ServiceV1Client client = new ServiceV1Client("WSHttpBinding_IServiceV1")) {
    Console.WriteLine(client.GetData(4));
}

using (ServiceV2Client client = new ServiceV2Client("WSHttpBinding_IServiceV2")) {
    Console.WriteLine(client.GetData(3, "hello"));
}

The client's application configuration will contain references to each endpoint. For example, this client may have the following:

<endpoint
  address="http://localhost:2352/Website/Service.svc/v1"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV1"
  name="WSHttpBinding_IServiceV1" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v2"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV2"
  name="WSHttpBinding_IServiceV2" />

The client also needs a specific strongly typed proxy for each endpoint, but it is also trivial. Below is an example of a complete proxy; other proxies are created following the same pattern. The properties set in the attributes should be easy to comprehend. However, it should be explicitly stated that the ConfigurationName property of the ServiceContract attribute matches the contract stated in the client application configuration.

[ServiceContract(Namespace="http://www.abccorp.com/services/sample01/2007/02", ConfigurationName="ServiceReference1.IServiceV1")]
public interface IServiceV1 {
    
    [OperationContract(Action="http://www.abccorp.com/services/sample01/2007/02/IServiceV1/GetData", ReplyAction="http://www.abccorp.com/services/sample01/2007/02/IServiceV1/GetDataResponse")]
    String GetData(Int32 value);
}

public interface IServiceV1Channel : IServiceV1, IClientChannel {
}

public partial class ServiceV1Client : ClientBase, IServiceV1 {
    public ServiceV1Client() {
    }
    
    public ServiceV1Client(String endpointConfigurationName) : 
            base(endpointConfigurationName) {
    }

    public ServiceV1Client(String endpointConfigurationName, String remoteAddress) : 
            base(endpointConfigurationName, remoteAddress) {
    }

    public ServiceV1Client(String endpointConfigurationName, EndpointAddress remoteAddress) : 
            base(endpointConfigurationName, remoteAddress) {
    }
    
    public ServiceV1Client(Binding binding, EndpointAddress remoteAddress) : 
            base(binding, remoteAddress) {
    }

    public String GetData(Int32 value) {
        return base.Channel.GetData(value);
    }
}

Given the simplicity of WCF, there is no reason to ever use tools like Visual Studio or svcutil.exe for proxy or configuration creation. Also, be aware that not all of the constructors for the proxy ("ServiceV1Client") are required.

It should also be explicitly stated that nothing that nothing here forbids the use of different bindings on each of the endpoints. For example, the following are legal endpoints on the client and Server:

Server

>endpoint address="v1" binding="basicHttpBinding" contract="IServiceV1" /<
>endpoint address="v2" binding="wsHttpBinding" contract="IServiceV2" //<

Client

>endpoint address="http://localhost:2352/Website/Service.svc/v1" binding="basicHttpBinding" contract="ServiceReference1.IServiceV1" name="WSHttpBinding_IServiceV1" /<
>endpoint address="http://localhost:2352/Website/Service.svc/v2" binding="wsHttpBinding" contract="ServiceReference1.IServiceV2" name="WSHttpBinding_IServiceV2" /<
If there are multiple bindings (B) and multiple contracts (C), then there needs to be B*C endpoints. For example, you may have the following: Server:
<endpoint address="v1/basic" binding="basicHttpBinding" contract="IServiceV1" />
<endpoint address="v2/basic" binding="basicHttpBinding" contract="IServiceV2" />
<endpoint address="v3/basic" binding="basicHttpBinding" contract="IServiceV3" />
<endpoint address="v4/basic" binding="basicHttpBinding" contract="IServiceV4" />

<endpoint address="v1/wshttp" binding="wsHttpBinding" contract="IServiceV1" />
<endpoint address="v2/wshttp" binding="wsHttpBinding" contract="IServiceV2" />
<endpoint address="v3/wshttp" binding="wsHttpBinding" contract="IServiceV3" />
<endpoint address="v4/wshttp" binding="wsHttpBinding" contract="IServiceV4" />

Client

<endpoint
  address="http://localhost:2352/Website/Service.svc/v1/basic"
  binding="basicHttpBinding"
  contract="ServiceReference1.IServiceV1"
  name="WSHttpBinding_IServiceV1_Basic" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v2/basic"
  binding="basicHttpBinding"
  contract="ServiceReference1.IServiceV2"
  name="WSHttpBinding_IServiceV2_Basic" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v3/basic"
  binding="basicHttpBinding"
  contract="ServiceReference1.IServiceV3"
  name="WSHttpBinding_IServiceV3_Basic" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v4/basic"
  binding="basicHttpBinding"
  contract="ServiceReference1.IServiceV4"
  name="WSHttpBinding_IServiceV4_Basic" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v1/wshttp"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV1"
  name="WSHttpBinding_IServiceV1_WSHttp" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v2/wshttp"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV2"
  name="WSHttpBinding_IServiceV2_WSHttp" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v3/wshttp"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV3"
  name="WSHttpBinding_IServiceV3_WSHttp" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v4/wshttp"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV4"
  name="WSHttpBinding_IServiceV4_WSHttp" />

Notice that neither the service contract, the service, nor the client proxy know anything about the bindings or the endpoints.

WCF Data Contract Versioning

If a v2 service sends a populated Person object to a v1 client, then the client will see a series of default values with the actual values being in the ExtensionData object. If the client sets its own data and sends that to the server, these values are effectively ignored.

For example, if the v2 service sends the following v2 data to the v1 client, the v1 client will see null.

return new Person()
{
    FirstName = "John",
    LastName = "Doe",
    Address1 = "1827 North South Street",
    City = "Winchestertonfieldville",
    State = "IA",
    Zip = "50182",
    Id = id
};

If the v1 client then proceeds to populate the v1 object and send it to the v2 service, the v2 service will completely ignore everything the v1 client has set because it is setting v1 data that the v2 service knows nothing about. The following client data, for example, is meaningless:

Person p = client.GetPerson(3);
p.Address = "Blank";
p.FirstName = "Jane";
client.SavePerson(p);

The v2 service will simply receive the same data it had previously send. That is, FirstName will still say "John".

The v2 service will accept the data from the v1 client if the v1 client had previously obtained that data from a v2 service. This is true even if the v2 data contract has a newly added data member which has IsRequired set to true.

However, if the client attempts to send a new v1 object to the v2 service, which has a v2 contract with a newly added data member with IsRequired set to true, a service fault will be thrown.

Client Data Contract

The data contract on the service is the same as the data contract on the client. Therefore the following is usable on both ends:

[DataContract(Namespace = "http://www.abccorp.com/services/sample01/datacontracts/2007/02")]
public class Person
{
    [DataMember]
    public Int32 Id { get; set; }

    [DataMember]
    public String FirstName { get; set; }

    [DataMember]
    public String LastName { get; set; }

    [DataMember(IsRequired=true)]
    public String Address1 { get; set; }

    [DataMember]
    public String Address2 { get; set; }

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

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

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

This means that if the service sends an instance of this data contract to the client and does not have the Address1 property, a fault will occur. Given that IsRequired is checked at the destination, the fault will occur immediately when the client requests the data from the service.

In order to obtain data that is not strongly typed in the data member, you must implement the IExtensibleDataObject interface. This allows data that is unknown at the destination to be encapulated in an object. This is usable on both the client and service end. The data will be placed into the ExtensionData object which is of type ExtensionDataObject.

For example, if the server adds more data members to its data contract than what the client expects, the client will obviously not be able to populate this data into any local object. However, if the client data contract implements the IExtensibleDataObject interface, then any excess data members which the server sends to the client are placed into the ExtensionData property of the data contract instance. The functionality can be provided in a reverse manner as well. That is, if the client sends more information to ther service, if the service data contract implements the IExtensibleDataObject interface, then it will have the data encapulsated in a ExtensionData property. The IDesign standard, written by the designers of WCF, states that you should always implement this interface.

INotifyPropertyChanged

When using the Visual Studio generation tool, the data contracts that are created implement the INotifyPropertyChanged interface and have special logic in their set accessors to allow the client to monitor changes to properties. This in no way has any affect on the operations of WCF and are not required. Furthermore, given that the client service contract creation is trivial and that the data contract on the client is the same as the data contract on the server, it's recommended that all proxies be created by hand without use of Visual Studio or svcutil.exe.