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

WCF Contract Inheritance problem Explained simply

4.43/5 (8 votes)
9 Jun 2014CPOL4 min read 33.5K   127  
How to properly replace base classes with sub classes in WCF contracts ?

Introduction

I have run into a problem lately where i couldn't pass a subclass instead of the base class in WCF. I run a search about a topic but i didn't find a proper solution nor a proper explanation. So, i said to myself, this is a good article subject to write about.

Background

Let's do a brief description of our current situation: We have a WCF Application where the service (a.k.a Factory) sends informations to the client (a.k.a Factory Client).

First, this is the service contract:

C#
[ServiceContract]
public interface IFactory
{
    [OperationContract]
    string TryService(string echo);

    [OperationContract]
    Result GetResult();
}

It contains two methods,

  1. TryService is a dummy method to test our WCF communication
  2. GetResult will create a dictionary<string, BaseElement> and send it back to the client

Second, the service implementation:

C#
public class Factory : IFactory
{
    public string TryService(string echo)
    {
        return echo;
    }

    public Result GetResult()
    {
        var baseElem = new BaseElement
        {
            BaseName = "BaseElement"
        };

        return new Result()
        {
            Elements = new Dictionary<string, BaseElement>
            {
                {"1", baseElem}
            }
        };
    }
}

Our BaseElement class implementation:

C#
[DataContract]
public class BaseElement
{
    [DataMember]
    public string BaseName { get; set; }
}

Our service configuration:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="ElementService.Factory">
        <endpoint address="/FactoryService" behaviorConfiguration=""
          binding="basicHttpBinding" bindingConfiguration="" contract="ElementService.IFactory" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost/services" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

The client implementation:

C#
class FactoryClient : IFactory
{
    public FactoryClient()
    {
        var myBinding = new BasicHttpBinding();
        var endpoint = new EndpointAddress("http://localhost/services/FactoryService");
        ChannelFactory = new ChannelFactory<IFactory>(myBinding, endpoint);
    }

    public string TryService(string echo)
    {
        var client = ChannelFactory.CreateChannel();
        var response = client.TryService(echo);
        ((ICommunicationObject)client).Close();
        return response;
    }

    public Result GetResult()
    {
        var client = ChannelFactory.CreateChannel();
        var response = client.GetResult();
        ((ICommunicationObject)client).Close();
        return response;
    }

    public ChannelFactory<IFactory> ChannelFactory { get; set; }
}

Our Program:

C#
class Program
{
    static void Main(string[] args)
    {
        FactoryClient factory = new FactoryClient();
        var tt = factory.TryService("Bill");
        Console.WriteLine("Service says: " + tt);

        var res = factory.GetResult();
        foreach (var item in res.Elements)
        {
            Console.WriteLine("Key :" + item.Key + " Value: " + item.Value);
        }
        Console.ReadLine();
    }
}

Til this far, everything should work fine and the expected result when you run the program is

Image 1

Problem

Now let's suppose we have a new class (a.k.a SuperElement) that inherits from BaseElement:

C#
[DataContract]
public class SuperElement : BaseElement
{
    [DataMember]
    public string SuperName { get; set; }
}

It's perfectly fine to edit my GetResult method on the service side to return a SuperElement as well:

C#
public Result GetResult()
{
    var baseElem = new BaseElement
    {
        BaseName = "BaseElement"
    };

    var superElem = new SuperElement
    {
        SuperName = "SuperElement"
    };

    return new Result()
    {
        Elements = new Dictionary<string, BaseElement>
        {
            {"1", baseElem},
            {"2", superElem},
        }
    };
}

Except that when i run my client invokes the GetResult method on the service, we get an exception like this

An error occurred while receiving the HTTP response to http://localhost/services/FactoryService. The reason may be that the connection endpoint service does not use the HTTP protocol. This may also be due to the fact that a context of HTTP request was ignored by the server (possibly due to the discontinuation of service). For more information, check the server logs.

So what just happened here?

Explanation

Let's remind ourselves about inheritance:

C#
public class A
{
}

public class B : A
{
}

For a base class A, any subclass B inheriting from A is considered an A class as well, but an A class is not necessarly a B class.

This is true because whenever you create a new instance of the sub class, compiler starts by assigning the base class to the object memory state then adds the subclass parts to finalize the instanciation just like this figure explains:

Image 2

When a method expecting a reference to the base class receives a refrence to a sub class instead, it accepts it because it's also a reference to the base class. However, this is not the case with WCF because objects are not passed by reference but by value !

Passing elements by value is better than by reference in situations of multi-level applications where every layer has the freedom to handle classes in its own way. In addition, this behaviour favours application interoperability, asynchronous calling and long workflows.

In our case, after creating the Dictionary<string, BaseElement>, the service serializes it to send it inside the WCF message. Upon receiving the WCF message, the client tries to deserialize it using the service contract as a guiding map. As mentionned in the service contract, the object should be Dictionary<string, BaseElement> but the client finds another object (which is SuperElement). Because he was not expecting such type, the client raises an exception !

Solution

With the introduction of .Net framework 3.0, WCF created a workaround for this kind of problems using an attribute called KnownType. Let's see how to use it before explaining what is it about:

C#
[DataContract]
[KnownType(typeof(SuperElement))]
public class BaseElement
{
    [DataMember]
    public string BaseName { get; set; }
}

Using KnowType attribute, we are telling the service/client that if you find a SuperElement instance instead of a BaseElement, then it's perfectly fine and you can serialize/deserialize it too. Magically, the serialization/deserialization happens according to the subclass type (SuperElement) instead of the base class (BaseElement).

The KnowType attribute affects all operations and contracts, thus autorising the acceptance of the sub class instead of the base class. Furthermore, it allows the client to pass in sub classes as well to the service by including the sub class in metadata.

Beware

You have to keep in mind three points:

  • You must always include the hole hierarchy to enable sub class substitution. Adding a sub class does not add its base class !
  • KnownType attribute reduces the performance of your application. This cost becomes significant when you start to make substitutions everywhere in you application. You might want to take a second look at your application architecture in that case.
  • If having many base class/sub classe is inevitable, microsoft provided other alternatives such as:
    • ServiceKnowType attribute
    • Write the list of awaited classes to be substituted inside the application configuration file section System.runtime.serialization
    • Best solution is available on .Net 4.0: DataContractResolver is powerful tool that intercepts serialization/derserialization process allowing you to alter the behaviour through your own code.

I hope this article will be a good use for you.

Credits

All credits go to an MSDN article written about this same subject. I strongly recommend reading the part about DataContractResolver.

License

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