none
Inheritance not supported on callback contracts?

    Question

  • Hello,

    When I try to invoke a callback method, I receive the following error:

    Callback method Renew is not supported, this can happen if the method is not marked with OperationContractAttribute or if its interface type is not the target of the ServiceContractAttribute's CallbackContract.

    I have a simple interface hierarchy as follows:

        [ServiceContract]

        public interface IRenewable

        {

            [OperationContract]

            void Renew();

        }

     

        [ServiceContract]

        public interface IMyServiceCallback : IRenewable

        {

            [OperationContract]

            void MyCallbackMethod();

        }

      

        [ServiceContract(CallbackContract = typeof(IMyServiceCallback)]

        public interface IMyService

        {

            [OperationContract]

            void MyServiceMethod();

        }

     

    This type of inheritance seems to work fine when dealing with regular service contracts, but not callbacks. Is this a supported scenario?

    Monday, October 02, 2006 5:32 PM

Answers

  • You can accomplish what you want using a structure like this:

    [ServiceContract(CallbackContract = typeof(IRenewable))]

    public interface IMyBaseContract

    {

        // No 'forward' operations

    }

    public interface IRenewable

    {

        [OperationContract]

        void Renew();

    }

    [ServiceContract(CallbackContract = typeof(IMyServiceCallback))]

    public interface IMyService : IMyBaseContract

    {

        [OperationContract]

        void MyServiceMethod();

    }

    public interface IMyServiceCallback : IRenewable

    {

        [OperationContract]

        void MyCallbackMethod();

    }

    Here's what's happening in general.  A ServiceContract plus its CallbackContract together define "the contract", a single entity.  For contract inheritance, they must both be refined together.  So in this case we have a base contract that has no 'forward' operations and the Renew() 'callback' operation, and then we inherit that in a derived contract which adds MyServiceMethod() as a 'forward' operation and MyCallbackMethod as a 'callback' operation.

    A couple things to note.  First, you don't need the [ServiceContract] attribute on the callback interface.  The callback interface is just "an extra bad of [OperationContract] methods", and not really a contract entity itself.  Think of it as merely an auxiliary part of the [ServiceContract] 'forward' interface.  Second, all of the inheritance is through the [ServiceContract] ('forward') interfaces.  When reflecting on the type, WCF will look through the contract's type hierarchy for parent interfaces that have [ServiceContract], and also pick up those parents' CallbackContracts.  But WCF does not walk up the CallbackContract interface hierarchy.  This explains how the code above "sees" the IRenewable interface - it sees it via the chain "IMyService has parent IMyBaseContract has callback IRenewable", and not via the chain "IMyService has callback IMyServiceCallback has parent IRenewable" because WCF doesn't look for parents of callbacks, it only looks for callbacks of parents.

     

    Wednesday, October 04, 2006 3:40 AM
  • Re:RC1 docs

    I believe the docs will cover this much better when we ship.

     

    Re: putting [ServiceContract] attribute on a callback contract

    Yes, [ServiceContract] will be ignored when being referenced from another [SC]'s CallbackContract.  But you can indeed still put it there if the CC is also an SC.  Thus is it possible to model two servers that talk to each other:

    [ServiceContract(CallbackContract=typeof(IPong)] interface IPing { ... }
    [ServiceContract(CallbackContract=typeof(IPing)] interface IPong { ... }

    or even fold these into a single contract (PeerChannel does this a bit):

    [ServiceContract(CallbackContract=typeof(ISelf)] interface ISelf { ... }

     

    Re: why is it this way?

    We struggled with the tension of finding a programming model that was easy to understand, and one that 'worked' without unexpected consequences.  There are lots of unusual edge cases that come about about as a result of multiple inheritance which cause problems with many of the 'simple' approaches.  Here's one example.  Suppose you have

    [ServiceContract(Namespace="foo", CallbackContract=typeof(IBaseCB))]
    interface IBaseC { ... }

    interface IBaseCB : IBaseBaseCB { ... }

    interface IBaseBaseCB { [OperationContract] void F(); }

    Now if I create an endpoint of type IBaseC and allow reflection to walk up both hierarchies, I'll end up with a callback operation F() in the contract whose namespace is "foo".

    Now suppose I write

    [ServiceContract(Namespace="bar", CallbackContract=typeof(IBaseBaseCB))]
    interface IOther { ... }

    An endpoint of IOther has callback operation F() in namespace "bar".  Now suppose I do

    interface IDerivedCB : IBaseCB { ...}

    [ServiceContract(CallbackContract=typeof(IDerivedCB))]
    interface IDerivedC : IBaseC, IOther { ... }

    What is the namespace of operation F() of an endpoint of type IDerivedC?  There's no good solution; with IBaseC and IOther one has created contracts which cannot reasonably be composed together.

    In order to try to avoid most of these weird edge cases, we tried to keep the model very constrained and simple.  In general, when working with duplex contracts, it is good to always define and refine the ServiceContract and CallbackContract interfaces together; think of them as a single entity that happens to be spread across two CLR interfaces.  The [SC] is the "authoritative" interface when it comes to getting info from attributes and walking the inheritance hierarchy, while the CC is just an auxiliary interface attached to a particular SC.

    Wednesday, October 04, 2006 4:34 PM

All replies

  •  

    Yes, i see the same problem and looks like a limitation to me.

    An exception of type 'System.NotSupportedException' occurred in mscorlib.dll but was not handled in user code

    Additional information: Callback method DemandStatusA is not supported, this can happen if the method is not marked with OperationContractAttribute or if its interface type is not the target of the ServiceContractAttribute's CallbackContract.

    Monday, October 02, 2006 6:53 PM
  • If this is a known limitation, I hope it's specific to the CTP. It's sure going to complicate my final implementation otherwise.
    Tuesday, October 03, 2006 9:07 PM
  • You can accomplish what you want using a structure like this:

    [ServiceContract(CallbackContract = typeof(IRenewable))]

    public interface IMyBaseContract

    {

        // No 'forward' operations

    }

    public interface IRenewable

    {

        [OperationContract]

        void Renew();

    }

    [ServiceContract(CallbackContract = typeof(IMyServiceCallback))]

    public interface IMyService : IMyBaseContract

    {

        [OperationContract]

        void MyServiceMethod();

    }

    public interface IMyServiceCallback : IRenewable

    {

        [OperationContract]

        void MyCallbackMethod();

    }

    Here's what's happening in general.  A ServiceContract plus its CallbackContract together define "the contract", a single entity.  For contract inheritance, they must both be refined together.  So in this case we have a base contract that has no 'forward' operations and the Renew() 'callback' operation, and then we inherit that in a derived contract which adds MyServiceMethod() as a 'forward' operation and MyCallbackMethod as a 'callback' operation.

    A couple things to note.  First, you don't need the [ServiceContract] attribute on the callback interface.  The callback interface is just "an extra bad of [OperationContract] methods", and not really a contract entity itself.  Think of it as merely an auxiliary part of the [ServiceContract] 'forward' interface.  Second, all of the inheritance is through the [ServiceContract] ('forward') interfaces.  When reflecting on the type, WCF will look through the contract's type hierarchy for parent interfaces that have [ServiceContract], and also pick up those parents' CallbackContracts.  But WCF does not walk up the CallbackContract interface hierarchy.  This explains how the code above "sees" the IRenewable interface - it sees it via the chain "IMyService has parent IMyBaseContract has callback IRenewable", and not via the chain "IMyService has callback IMyServiceCallback has parent IRenewable" because WCF doesn't look for parents of callbacks, it only looks for callbacks of parents.

     

    Wednesday, October 04, 2006 3:40 AM
  •  

    I tried it and it seem to work. However, iam still not  convinced why  WCF cannot walk up the  interface hierarchy for CallbackContract? Why can't it be the same way like ServiceContract inheritance?

    Thanks,

    T.Ramesh.

    Wednesday, October 04, 2006 6:12 AM
  • Brian, thank you for the detailed response. That explains the problem well and allows me to proceed with the solution you detailed. I see how walking the one side only allows you to tightly couple a set of forward operations to the particular callback operations relevant to that set.

    I looked for this kind of information in the SDK documentation for RC1 but wasn't able to locate it. I did find one section on contract inheritance, but it was empty. This would be very good information to include if it's not already since I expect this is going to be a common scenario.

    Regarding the [ServiceContract] attribute, I'll avoid putting it on interfaces like IMyServiceCallback, so thanks for bringing that to my attention. However, is it OK to include it on a callback interface even though it isn't necessary? Will it just be ignored? The reason I ask is, I need to use IRenewable on both callbacks and on service contracts, and I presume for WCF to find it when walking the service side I need to have the attribute.

    Thanks!

    Wednesday, October 04, 2006 12:16 PM
  • Re:RC1 docs

    I believe the docs will cover this much better when we ship.

     

    Re: putting [ServiceContract] attribute on a callback contract

    Yes, [ServiceContract] will be ignored when being referenced from another [SC]'s CallbackContract.  But you can indeed still put it there if the CC is also an SC.  Thus is it possible to model two servers that talk to each other:

    [ServiceContract(CallbackContract=typeof(IPong)] interface IPing { ... }
    [ServiceContract(CallbackContract=typeof(IPing)] interface IPong { ... }

    or even fold these into a single contract (PeerChannel does this a bit):

    [ServiceContract(CallbackContract=typeof(ISelf)] interface ISelf { ... }

     

    Re: why is it this way?

    We struggled with the tension of finding a programming model that was easy to understand, and one that 'worked' without unexpected consequences.  There are lots of unusual edge cases that come about about as a result of multiple inheritance which cause problems with many of the 'simple' approaches.  Here's one example.  Suppose you have

    [ServiceContract(Namespace="foo", CallbackContract=typeof(IBaseCB))]
    interface IBaseC { ... }

    interface IBaseCB : IBaseBaseCB { ... }

    interface IBaseBaseCB { [OperationContract] void F(); }

    Now if I create an endpoint of type IBaseC and allow reflection to walk up both hierarchies, I'll end up with a callback operation F() in the contract whose namespace is "foo".

    Now suppose I write

    [ServiceContract(Namespace="bar", CallbackContract=typeof(IBaseBaseCB))]
    interface IOther { ... }

    An endpoint of IOther has callback operation F() in namespace "bar".  Now suppose I do

    interface IDerivedCB : IBaseCB { ...}

    [ServiceContract(CallbackContract=typeof(IDerivedCB))]
    interface IDerivedC : IBaseC, IOther { ... }

    What is the namespace of operation F() of an endpoint of type IDerivedC?  There's no good solution; with IBaseC and IOther one has created contracts which cannot reasonably be composed together.

    In order to try to avoid most of these weird edge cases, we tried to keep the model very constrained and simple.  In general, when working with duplex contracts, it is good to always define and refine the ServiceContract and CallbackContract interfaces together; think of them as a single entity that happens to be spread across two CLR interfaces.  The [SC] is the "authoritative" interface when it comes to getting info from attributes and walking the inheritance hierarchy, while the CC is just an auxiliary interface attached to a particular SC.

    Wednesday, October 04, 2006 4:34 PM