Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF trouble with Types?

When I call a method of my WCF Soap service, an error is thrown and a error appears in the svlog file:

Type 'xxx.ActiveDirectoryService.classes.WCF.Message' with data contract name 'Message:http://schemas.datacontract.org/2004/07/xxx.ActiveDirectoryService.classes.WCF' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

I tried to use KnownType here and there but with no success (I must admit I'm not too sure I got its usage 100% right).

Here are my Interface/classes:

[ServiceContract]
public interface IActiveDirectory
{
    [OperationContract]
    [WebGet]
    void Dummy();

    [OperationContract]
    [WebGet]
    AbstractMessage Dummy2();

    [OperationContract]
    [WebGet]
    AbstractMessage Dummy3();

    [OperationContract]
    [WebGet]
    AbstractMessage SetPassWord(string customer, string customerPassword, string userLogin, string userPassword);
}

[DataContract]
public abstract class AbstractMessage
{
    [DataMember]
    public virtual bool IsError { get; set; }

    [DataMember]
    public virtual string ErrorMessage { get; set; }

    [DataMember]
    public virtual string ReturnValue { get; set; }
}

public class Message : AbstractMessage
{
<...>
}


[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
[KnownType(typeof(AbstractMessage))]
public class ActiveDirectory : IActiveDirectory
{
    public void Dummy()
    {
    }

    public AbstractMessage Dummy2()
    {
        return new AbstractMessage();
    }

    public AbstractMessage Dummy3()
    {
        return new Message();
    }

    public AbstractMessage SetPassWord(string customer, string customerPassword, string userLogin, string userPassword)
    {
    <...>

        return message; // message is of type Message
    }
}

Edit: 12AM35 GMT+1

I added Dummy() method.

  • Calling Dummy from client works fine.
  • Calling Dummy2 from client works fine.
  • Calling Dummy3 from client causes the same error.

Edit 12AM39 GMT+1

Making the following changes didn't help.

[DataContract]
[KnownType(typeof(AbstractMessage))]
public class Message : AbstractMessage

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
[KnownType(typeof(AbstractMessage))]
[KnownType(typeof(Message))]
public class ActiveDirectory : IActiveDirectory

Edit: 13AM31 GMT+1

If I set Dummy3 return type to Message, calls to Dummy3 in client code work.
There 's something odd with WCF + Polymorphism...

like image 462
Serge Avatar asked Jun 17 '13 10:06

Serge


2 Answers

You WCF services need to return DTOs. These can't be interfaces and if you want to return abstract base classes then you will need to tell the data contract serialiser which implementations of those types you actually intend to return.

It already knows about the AbstractMessage type (as its part of the operation contract), but all implementations of that type which are not explicitly declared in the operation contract should be declared in the known types.

try adding this:

[ServiceContract]
[ServiceKnownType(typeof(Message))]
public interface IActiveDirectory
{
    ...
}

Here you are telling the data contract serialiser that this service may return (or expect) objects of type Message as arguments to its methods.

and this should work as well:

[DataContract]
[KnownType(typeof(Message))]
public abstract class AbstractMessage
{
    ...
}

as you want to tell the data contract serialiser that Message is a known type of AbstractMessage

I believe your changes didn't work as you used KnownTypes on the service instead of ServiceKnownTypes and you tried to apply the known type on the derived class rather than the parent class, which is what you are used to doing in the language (Message is an AbstractMessage) but in WCF you have to flip it over and put the derived implementations on the parent class (AbstractMessage has implementation Message) which can be restrictive.

you said: There 's something odd with WCF + Polymorphism...

trust me, its better to not think of WCF as supporting polymophism, as it doesn't. You simply return DTOs and if you want to try and make these polymorphic you'll come up against a lot of problems. Polymorphism is implemented using interfaces and you can't use interfaces in WCF. If you use abstract classes then due to the lack of multiple inheritence in c# you'll soon realise that a DTO can only represent a single view of your object and you can't create a DTO which represents multiple interfaces of a domain model class. (see this question on the subject)

See my answer here for details of how you can pass on the knowledge of known types, either through KnownTypes, ServiceKnownTypes or configuration.

like image 153
Sam Holder Avatar answered Oct 05 '22 11:10

Sam Holder


[KnownType(typeof(AbstractMessage))]

The compiler already figured out that you return AbstractMessage. What it cannot figure out from the description are the instances that are derived from AbstractMessage. Those are the types that you need to make known. Any type you derived from AbstractMessage and you return there needs to be a known type, and this needs to be declared on the AbstractMessage itself.

[KnownType(typeof(DerivedMessage1))]
[KnownType(typeof(DerivedMessage2))]
[KnownType(typeof(DerivedMessage3))]

In your case, you need the attribute to tell the data contract serializer: "hey, as you can see I will return an AbstractMessage, but what you cannot possibly know: in reality, it's a Message":

[ServiceBehavior(...)]
[KnownType(typeof(Message))]
public class ActiveDirectory : IActiveDirectory
{
    public AbstractMessage Dummy3()
    {
        return new Message();
    }
}
like image 25
nvoigt Avatar answered Oct 05 '22 12:10

nvoigt