Click here to Skip to main content
15,881,898 members
Articles / Programming Languages / C#

WCF ServiceSide Ad Hoc Polymorphism

Rate me:
Please Sign up or sign in to vote.
4.80/5 (2 votes)
10 Oct 2013CPOL8 min read 21.4K   138   7   3
A method of emulating ad hoc polymorphism (operator overloading) in WCF service development

Introduction

Design of WCF ServiceContracts must conform to the constraints of WSDL which precludes the concepts of ad hoc polymorphism, i.e. operator overloading. With the use of OperationContractAttribute one can disguise this problem on the service side by mapping the overloaded OperationContracts implementations (implicitly implemented) to a unique name that then appears in the <operation/> element of the WSDL. While this gives a (perhaps) acceptable story for the service side overloading implementation, additional work is required to bring the overloading to the client side. A specialized proxy can be developed for each service contract that again leverages OperationContractAttribute to handle the name mapping.1 However, this technique increases the development complexity and increases the deployment footprint.

If one has the luxury of implementing WCF services for an environment in which the DataContractSerializer can be utilized, another alternative arises by leveraging KnownTypes. One can design DataContract hierarchies with abstract base classes which are used to parameterize the OperationContracts. The serializer can then move subclasses of the DataContract parameter classes over the wire transparently, and WCF will materialize the specialization when the operation is dispatched. This effectively provides a form of overloading by allowing single OperationContract to have a wide variety of actual parameterizations.

However useful this is in ServiceContract management, the OperationContract implementation becomes rapidly unmanageable without further infrastructure support. Each OperationContract individually must handle every possible parameter specialization in some way. This article describes an implementation of the Bridge Pattern that solves this problem cleanly and leads to a complete separation of the implementation of the OperationContract from the code that produces its results. The side effect is to provide a very clean service implementation pattern subject to enhancement by normal subclassing techniques. ServiceContract implementation classes become largely boilerplate, and service implementations can potentially be injected by an IOC at runtime.

Background

The Bridge Pattern can be viewed as a form of multiple dispatch. The four principal entities in the pattern are

  • The Abstraction which defines the abstract, externally exposed interface and also maintains a reference to the actual implementation, typically in a member variable of type Implementor
  • The RefinedAbstraction which realizes the Abstraction
  • The Implementor which defines the interface for the implementation
  • The ConcreteImplementor which realizes the Implementor

Operationally, when the RefinedAbstraction is constructed it must initialize the Abstraction’s implementor variable with a specific instance of the ConcreteImplementor. Subsequent calls on the RefinedAbstraction interface implementation are delegated by the Abstraction into an appropriate implementation in the ConcreteImplementor.

By using reflection in .NET the nature of the Abstraction can be greatly generalized within the context of the WCF service implementation. The need for an explicit definition of Implementor interface can be eliminated by adopting naming and parameter signature conventions for the ConcreteImplementor. The convention used herein is that a valid delegation method in a ConcreteImplementor must match the name and signature of an OperationContract implemented by the service. The Abstraction delegation process then needs minimal understanding of the WCF interface.

When implementing WCF ServiceContracts, one may use either implicit or explicit implementation of the interface. Implicit implementations take the form of public methods of the correct signature in the service class. Explicit implementations take the form of private methods of the correct signature in the service class with operation names qualified with the ServiceContract name. While the delegator described below handles either, the full realization of Separation of Concerns between the RefinedAbstraction and the ConcreteImplementor can only be achieved with explicit implementations. Also explicit implementations allow OperationContracts with the same name and parameter structure to have separate implementations if necessary.

The Contracts

The WCF services implemented in this article take the form of a stateful add/subtract calculator whose parameters can be either integers or string representations of integers. These parameters types are described by the MathArgument DataContract hierarchy.

C#
namespace Samples.CalculatorService.Contract
{
    [DataContract(IsReference = true)]
    [KnownType(typeof(IntArgument))]
    [KnownType(typeof(StringArgument))]
    public abstract class MathArgument
    {
 
    }
 
    [DataContract(IsReference = true)]
    public class IntArgument : MathArgument
    {
        [DataMember]
        public int Value;
    }
 
    [DataContract(IsReference = true)]
    public class StringArgument : MathArgument
    {
        [DataMember]
        public string Value;
    }
}
The service supports two contracts with different interface names but identical OperationContracts.

C#
namespace Samples.CalculatorService.Contract
{
    [ServiceContract(Namespace = "http://Samples/CalculatorService/",
        SessionMode = SessionMode.Required)]
    public interface ICalculatorServiceB
    {
        [OperationContract(IsOneWay = false)]
        int Add(MathArgument number);
 
        [OperationContract(IsOneWay = false)]
        int Subtract(MathArgument number);
    }
    :
 
    [ServiceContract(Namespace = "http://Samples/CalculatorService/",
       SessionMode = SessionMode.Required)]
    public interface ICalculatorServiceA
    {
        [OperationContract(IsOneWay = false)]
        int Add(MathArgument number);
 
        [OperationContract(IsOneWay = false)]
        int Subtract(MathArgument number);
    }
}

The Abstraction

The Abstraction is realized as the ServiceBase class. ServiceBase's constructor argument initializes the ConcreteImplementor instance _implementor. The generic Type parameter of the ServiceBase is a subclass of ServiceBase. If the subclass does not have a ServiceBehaviorAttribute, all subsequent dispatches attempts fault because the caller cannot be determined to be a valid WCF operation dispatch point. If the subclass has the ServiceBehaviorAttribute, then constructor fills _callSites with a list of all valid dispatch point names by analyzing each ServiceContract on the service class.

 ServiceBase's single protected method MethodDispatcher performs the delegation between the RefinedAbstraction and the ConcreteImplementor. Because of the overhead of validating the delegated calls reflectively, the MethodDispatcher caches each successfully resolved delegation for future use.

C#
namespace Samples.CalculatorService.Infrastructure.Service
{
    public class ServiceBase<t> where T : class
    {
        private readonly HashSet<string> _callSites = new HashSet<string>();
                                // Contains names of all viable callSites (dispatch points from WCF) 
                                // in the service class 

        private readonly object _implementor;
                                // the class to which the calls will be dispatched, i.e. the ConcreteImplementor

        private readonly Dictionary<dispatchkey,> _methodCache =
            new Dictionary<dispatchkey,>(new DispatchKeyComparer());
                               // dynamically built cache of dispatchable call information
  

        public ServiceBase(object implementor)
        {
            _implementor = implementor;
            Array.ForEach(FindCallSites(), s => _callSites.Add(s)); // load all possible dispatchable methods names
        }

                 :
       protected virtual object MethodDispatcher(MethodBase methodBase, params object[] parameters)
        {
            // Note by the time we get here, WCF will have deserialized abstract DataContract parameters
            // into concrete specializations.
            DispatchEntry delegatedCall;
            var key = new DispatchKey(methodBase.Name, parameters);
            // if entry has not been cached before, do so now.
            if (!_methodCache.TryGetValue(key, out delegatedCall))
            {
                if (!_callSites.Contains(methodBase.Name)) // is call from a legal point of origin?
                {
                    throw new ServiceMethodDispatchException(
                        ServiceMethodDispatchException.IllegalCallerMessageFormatter(methodBase));
                }
                // Get specifications of delegation
                delegatedCall = LoadDelegationDetails(methodBase, key);

                // Save for next call at same target
                // The cache can ultimately contain many DispatchKeys with the same callSite (MethodName) name and different ParameterTypes
                _methodCache.Add(key, delegatedCall);
            }
            // delegate call
            return delegatedCall.DelegationTarget.Invoke(delegatedCall.DelegationInstance, parameters);
        }
                :
    }
}
</dispatchkey,></dispatchkey,></string></string>
The key of the cache is composite, formed of the OperationContract name and the ordered array of associated parameter types. LoadDelgationDetails performs reflective analysis of the caller (RefinedAbstraction) and the ConcreteImplementor to determine the correct delegation.

The RefinedAbstractions

The RefinedAbstractions are the two WCF service classes, CalculatorService and CalculatorExtender. CalculatorService fully implements the service contracts (one method implicitly), delegates the implementation to the MethodDispatcher, and casts the return value appropriately.
C#
namespace Samples.CalculatorService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public partial class CalculatorService : ServiceBase, ICalculatorService, ICalculatorServiceA
    {
        public CalculatorService(object implementor) : base(implementor)
        {
        }
 
        public CalculatorService() : base(new CalculatorImplementation())
        {
        }
        
 
        // Implicit implementation of ICalculatorService.Subtract
        public int Subtract(MathArgument number)
        {
            return (int)MethodDispatcher(MethodBase.GetCurrentMethod(), 
                new object[] {number});
        }
        
        int ICalculatorService.Add(MathArgument number)
        {
            return (int)MethodDispatcher(MethodBase.GetCurrentMethod(), 
                new object[] {number});
        }
 
        int ICalculatorServiceA.Add(MathArgument number)
        {
            return (int)MethodDispatcher(MethodBase.GetCurrentMethod(), 
                new object[] { number });
        }
 
        int ICalculatorServiceA.Subtract(MathArgument number)
        {
            return (int)MethodDispatcher(MethodBase.GetCurrentMethod(), 
                new object[] { number });
        }
    }
}
CalculatorExtender however is much simpler in that it simply inherits from CalculatorService and provides a different implementation of the ConcreteImplementor. The WCF interface implementation does not have to be re-implemented.
C#
namespace Samples.CalculatorService
{
    /// <summary>
    /// Customization sample
    /// Overrides implementations in CalculatorService
    /// </summary>
    public class CalculatorExtender : CalculatorService
    {
        public CalculatorExtender() : base( new ExtenderImplementation())
        {
        }
    }
 
}

The ConcreteImplementors

The ConcreteImplementors, CalculatorImplementation and ExtenderImplementation, provide implementations that calculate the values for the services. CalculatorImplementation has one function for every possible parameterization of an OperationContract manifested by the WCF interfaces in concrete types. In this case there is one function for each concrete MathArgument that can be deserialized by WCF.

Two of the functions are marked by the ExplicitMethodDispatchAttribute. The occurrence of this attribute signals the dispatcher to delegate to the decorated method when processing calls for the specified OperationContracts (in ICalculatorServiceA in this case). While this couples the implementor to the ServiceContract (not the service class), it provides a means for different implementations for the two interfaces to be provided. If the ExplicitMethodDispatchAttributes are removed, the corresponding OperationContracts are delegated to the same implementation function (the one whose name is not suffixed with "A").
C#
namespace Samples.CalculatorService
{
    public partial class CalculatorImplementation
    {
        protected int total { get; set; }
 
 
        protected virtual int Add(IntArgument number)
        {
            this.total += number.Value;
            return this.total;
        }
 
        protected virtual int Add(StringArgument number)
        {
            this.total += Convert.ToInt32(number.Value);
            return this.total;
        }
 
        protected virtual int Subtract(IntArgument number)
        {
            this.total -= number.Value;
            return this.total;
        }
 
        [ExplicitMethodDispatch(typeof(ICalculatorServiceA), "Add")]
        protected virtual int AddA(StringArgument number)
        {
            this.total += 3 * Convert.ToInt32(number.Value);
            return total;
        }
 
        [ExplicitMethodDispatch(typeof(ICalculatorServiceA), "Subtract")]
        protected virtual int SubtractA(StringArgument number)
        {
            this.total -= 3 * Convert.ToInt32(number.Value);
            return this.total;
        }
 
        protected virtual int Subtract(StringArgument number)
        {
            return total -= Convert.ToInt32(number.Value);
        }
    }
}
Similarly to the service class, the ExtenderImplementation simply inherits from the CalculatorImplementation and overrides some of its functions. Note that one of the overrides is to a method decorated with the ExplicitMethodDispatchAttribute; the attribute does not have to be repeated for correct delegation to occur.
C#
namespace Samples.CalculatorService
{
    public class ExtenderImplementation : CalculatorImplementation
    {
        protected override int Add(IntArgument number)
        {
            total += number.Value * 2;
            return total;
        }
 
        protected override int SubtractA(StringArgument number)
        {
            this.total -= Convert.ToInt32(number.Value);
            return this.total;
        }
    }
}
One should also note that the ConcreteImplementors do 'funny math'. Input values in some cases are scaled to provide easily recognizable results in the unit tests that verify the dispatching path.

Exceptions

If dynamic resolution of the delegated call fails, the MethodDispatcher throws a ServiceMethodDispatchException at runtime containing one of two messages. Both indicate a problem in the service development, not execution.
  1. The UndispatchableCall message indicates that the dispatcher could not find a ConcreteImplementor method conforming to the parameterization of the incoming WCF call. This occurs, for example, if a parameter's DataContract hierarchy gains a new subclass but no corresponding implementation support is provided.
  2. The IllegalCall message indicates that a call was attempted from a location other than that of a defined OperationContract implementation. WCF interfaces using the MethodDispatcher must be marked with the ServiceContractAttribute for validation to pass.

Using the code

The provided sample solution contains three projects (VS2012). The CalculatorService contains all of the classes described above, plus provides a WCF console host for the CalulatorExtender service over nettcp. The CalculatorClient provides a nettcp client for that service. To run as WCF services, start the CalulatorService (e.g. Debug/Start New Instance). After the host is console window opens, the Calculator Client may be similarly started.

ServiceImplTests contains xUnit tests against the service class infrastructure. This is all POCO and does not leverage WCF transport.

Points of Interest

  • When using explicit interface implementations all WCF service classes are essentially boiler plate. This means creation of the code artifact could be easily delegated to a code generation process, reducing development effort. These could even be packaged in an independent assembly.
  • Customization by subclassing in both the RefinedAbstraction and the ConcreteImplementor makes it easier to evolve the code with less repetitive code in the customizations.
  • The binding of the ConcreteImplementor to the Abstraction can be performed by Dependency Injection making the delegation runtime swappable without re-implementing the RefinedAbstractions.

Alternatives

IDispatchOperationSelector

WCF's operation call dispatching pipeline offers an optional interception point at which the call can be redirected to an alternative implementation, very similarly to the process included herein. The functionality required must be encapsulated in an EndPointBehavior and attached to the EndpointDispatcher for each service EndPoint. Lowy shows an example of this in his book.2 Encapsulating the dispatching in an EndPointBehavior would completely eliminate the ServiceBase class as shown here. The Abstraction implementation would then reside in the EndPointBehavior, and one call dispatch would be eliminated from the pipeline. The process of injecting the ConcreteImplementor would then be significantly different as well.

Assignable Parameters

The Bridge Pattern itself allows for the possibility of parameter coercion prior to the delegated call. While this implementation is based on exact parameter Type matching, this constraint could be relaxed to allow match to occur if the incoming parameter can be assigned to a ConcreteImplementor's parameter, i.e. the OperationContract could have a parameter of Type of IEnumerable<T> and match a ConcreteImplementor parameter of List<T>.

To enable a similar loosening in an IDispatchOperationSelector implementation, yet another EndpointBehavior implementing IParameterInspector would be likely be required.

References

1Lowy, Juval, Programming WCF Services, Third Edition: Mastering WCF and the Azure
AppFabric Service Bus.
Sabastopol, CA, USA: O'Reilley Media, Inc., August, 2010, pp. 83-85.

2Ibid., pp. 798-800.

History

License

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


Written By
Architect SoftwareDo, Inc.
United States United States
I'm the President and Chief Architect of SoftwareDo, Inc. I've spent most of my career architecting, designing, and implementing large distributed compute systems for the retail industry, mostly focused on the Point-Of-Service problems. Most of this has work has been done on the Microsoft platforms.

I have extensive experience in COM, C++, Sql Server, and C#. I have a longterm interest in domain specific modeling leading to metadata driven code generation. I've been involved with WCF from the earliest TAP programs at Microsoft onward. My current focus is on distributed, enterprise level Service Oriented Applications using WCF and the Azure service bus.

I hold a BS in Physics from Northwest Missouri State University and a PhD in Astronomy from the University of Florida.

My major avocation for the last 40 years has been as a martial arts instructor. I hold a 4th degree blackbelt in karate and a 5th degree blackbelt in aikido.

Comments and Discussions

 
QuestionGenerated proxy classes are not marked abstract Pin
romanmar124-Feb-14 10:04
romanmar124-Feb-14 10:04 
AnswerRe: Generated proxy classes are not marked abstract Pin
David Killian6-Aug-14 22:40
professionalDavid Killian6-Aug-14 22:40 
GeneralRe: Generated proxy classes are not marked abstract Pin
romanmar119-Sep-14 6:55
romanmar119-Sep-14 6:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.