Contract Inheritance



Contract Inheritance

Service contract interfaces can derive from each other, enabling you to define a hierarchy of contracts. However, the ServiceContract attribute is not inheritable:

[AttributeUsage(Inherited = false,...)]
public sealed class ServiceContractAttribute : Attribute
{...}

Consequently, every level in the interface hierarchy must explicitly have the ServiceContract attribute, as shown in Figure.

Service-side contract hierarchy

[ServiceContract]
interface ISimpleCalculator
{
   [OperationContract]
   int Add(int arg1,int arg2);
}
[ServiceContract]
interface IScientificCalculator : ISimpleCalculator
{
   [OperationContract]
   int Multiply(int arg1,int arg2);
}

When it comes to implementing a contract hierarchy, a single service class can implement the entire hierarchy, just as with classic C# programming:

class MyCalculator : IScientificCalculator
{
   public int Add(int arg1,int arg2)
   {
      return arg1 + arg2;
   }
   public int Multiply(int arg1,int arg2)
   {
      return arg1 * arg2;
   }
}

The host can expose a single endpoint for the bottom most interface in the hierarchy:

<service name = "MyCalculator">
   <endpoint
      address  = "http://localhost:8001/MyCalculator/"
      binding  = "basicHttpBinding"
      contract = "IScientificCalculator"
   />
</service>

Client-Side Contract Hierarchy

When the client imports the metadata of a service endpoint whose contract is part of an interface hierarchy, the resulting contract on the client side does not maintain the original hierarchy. Instead it will include a flattened hierarchy in the form of a single contract named after the endpoint's contract. The single contract will have a union of all the operations from all the interfaces leading down to it in the hierarchy, including itself. However, the imported interface definition will maintain, in the Action and ResponseAction properties of the OperationContract attribute, the name of the original contract that defined each operation:

[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
   public string Action
   {get;set;}
   public string ReplyAction
   {get;set;}
   //More members
}

Finally, a single proxy class will implement all methods in the imported contract. Given the definitions of Figure, Figure shows the imported contract and the generated proxy class.

Client-side flattened hierarchy

[ServiceContract]
public interface IScientificCalculator
{
   [OperationContract(Action = ".../ISimpleCalculator/Add",
                      ReplyAction = ".../ISimpleCalculator/AddResponse")]
   int Add(int arg1,int arg2);

   [OperationContract(Action = ".../IScientificCalculator/Multiply",
                      ReplyAction = ".../IScientificCalculator/MultiplyResponse")]
   int Multiply(int arg1,int arg2);
}

public partial class ScientificCalculatorClient :
             ClientBase<IScientificCalculator>, IScientificCalculator
{
   public int Add(int arg1,int arg2)
   {...}
   public int Multiply(int arg1,int arg2)
   {...}
   //Rest of the proxy
}

Restoring the hierarchy on the client

The client can manually rework the proxy and the imported contract definitions to restore the contract hierarchy as shown in Figure.

Client-side contract hierarchy

[ServiceContract]
public interface ISimpleCalculator
{
   [OperationContract]
   int Add(int arg1,int arg2);
}
public partial class SimpleCalculatorClient : ClientBase<ISimpleCalculator>,
                                              ISimpleCalculator
{
   public int Add(int arg1,int arg2)
   {
      return Channel.Add(arg1,arg2);
   }
   //Rest of the proxy
}

[ServiceContract]
public interface IScientificCalculator : ISimpleCalculator
{
   [OperationContract]
   int Multiply(int arg1,int arg2);
}
public partial class ScientificCalculatorClient :
                           ClientBase<IScientificCalculator>,IScientificCalculator
{
   public int Add(int arg1,int arg2)
   {
      return Channel.Add(arg1,arg2);
   }
   public int Multiply(int arg1,int arg2)
   {
      return Channel.Multiply(arg1,arg2);
   }
   //Rest of the proxy
}

Using the value of the Action property in the various operations, the client can factor out the definitions of the comprising contracts in the service contract hierarchy and provide interface and proxy definitions, for example. ISimpleCalculator and SimpleCalculatorClient in Figure. There is no need to set the Action and ResponseAction properties, and you can safely remove them all. Next, manually add the interface to the inheritance chain as required:

[ServiceContract]
public interface IScientificCalculator : ISimpleCalculator
{...}

Even though the service may have exposed just a single endpoint for the bottom-most interface in the hierarchy, the client can view it as different endpoints with the same address, where each endpoint corresponds to a different level in the contract hierarchy:

<client>
   <endpoint name = "SimpleEndpoint"
      address  = "http://localhost:8001/MyCalculator/"
      binding  = "basicHttpBinding"
      contract = "ISimpleCalculator"
   />
   <endpoint name = "ScientificEndpoint"
      address  = "http://localhost:8001/MyCalculator/"
      binding  = "basicHttpBinding"
      contract = "IScientificCalculator"
   />
</client>

The client can now write the following code, taking full advantage of the contract hierarchy:

SimpleCalculatorClient proxy1 = new SimpleCalculatorClient( );
proxy1.Add(1,2);
proxy1.Close( );

ScientificCalculatorClient proxy2 = new ScientificCalculatorClient( );
proxy2.Add(3,4);
proxy2.Multiply(5,6);
proxy2.Close( );

The advantage of the proxy refactoring in Figure is that each level in the contract is kept separately and decoupled from the levels underneath it. Anyone on the client side that expects a reference to ISimpleCalculator can now be given a reference to IScientificCalculator:

void UseCalculator(ISimpleCalculator calculator)
{...}

ISimpleCalculator proxy1 = new SimpleCalculatorClient( );
ISimpleCalculator proxy2 = new ScientificCalculatorClient( );
IScientificCalculator  proxy3 = new ScientificCalculatorClient( );
SimpleCalculatorClient  proxy4 = new SimpleCalculatorClient( );
ScientificCalculatorClient proxy5 = new ScientificCalculatorClient( );

UseCalculator(proxy1);
UseCalculator(proxy2);
UseCalculator(proxy3);
UseCalculator(proxy4);
UseCalculator(proxy5);

However, there is no Is-A relationship between the proxies. Even though the IScientificCalculator interface derives from ISimpleCalculator, a ScientificCalculatorClient is not a SimpleCalculatorClient. In addition, you have to repeat the implementation of the base contract in the proxy for the subcontract. You can rectify that by using a technique I call proxy chaining, shown in Figure.

Proxy chaining

public partial class SimpleCalculatorClient : ClientBase<IScientificCalculator>,
                                              ISimpleCalculator
{
   public int Add(int arg1,int arg2)
   {
      return Channel.Add(arg1,arg2);
   }
   //Rest of the proxy
}

public partial class ScientificCalculatorClient : SimpleCalculatorClient,
                                                  IScientificCalculator
{
   public int Multiply(int arg1,int arg2)
   {
      return Channel.Multiply(arg1,arg2);
   }
   //Rest of the proxy
}

Only the proxy that implements the top most base contract derives directly from ClientBase<T>, providing it as a type parameter with the bottom most subinterface. All the other proxies derive from the proxy immediately above them and the respective contract.

Proxy chaining gives you an Is-A relationship between the proxies as well as code reuse. Anyone on the client side that expects a reference to SimpleCalculatorClient can be given now a reference to ScientificCalculatorClient:

void UseCalculator(SimpleCalculatorClient calculator)
{...}

SimpleCalculatorClient proxy1 = new SimpleCalculatorClient( );
SimpleCalculatorClient proxy2 = new ScientificCalculatorClient( );
ScientificCalculatorClient proxy3 = new ScientificCalculatorClient( );

UseCalculator(proxy1);
UseCalculator(proxy2);
UseCalculator(proxy3);