WCF - serializing inherited types
Asked Answered
H

3

15

I have these classes:

[DataContract]
public class ErrorBase {}

[DataContract]
public class FileMissingError: ErrorBase {}

[DataContract]
public class ResponseFileInquiry
{
  [DataMember]
  public List<ErrorBase> errors {get;set;};
}

An instance of the class ResponseFileInquiry is what my service method returns to the client. Now, if I fill ResponseFileInquiry.errors with instances of ErrorBase, everything works fine, but if I add an instance of inherited type FileMissingError, I get a service side exception during serialization:

Type 'MyNamespace.FileMissingError' with data contract name 'FileMissingError' 
is not expected. 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.'

So serializer is getting confused because it's expecting the List to contain the declared type objects (ErrorBase) but it's getting inherited type (FileMissingError) objects.

I have the whole bunch of error types and the List will contain combinations of them, so what can I do to make it work?

Horseradish answered 24/2, 2010 at 20:58 Comment(0)
R
17

You should add KnownType attribute to your base class

[DataContract]
[KnownType(typeof(FileMissingError))]
public class ErrorBase {}

Read more about KnownType attribute in this blog

Replenish answered 24/2, 2010 at 21:1 Comment(3)
Thanks, the blog entry has all the possible ways of declaring known types.Horseradish
In my case, using KnownType doesn't help much since the type is coming from a separate assembly that I don't reference. Other devs are extending the class that I have for my DataContract to add some properties. What if I just wanted to throw away whatever derived class and use the base class?Herta
This is the solution that I had to use: https://mcmap.net/q/823499/-wcf-returning-a-derived-object-for-a-contract-with-base-object-datacontractresolver Note: I had to apply the customer DataContractSerializer to the ClientBaseHerta
C
7

Try this:

[DataContract]
[KnownType(typeof(FileMissingError))]
public class ErrorBase {}

As the error message states, any information that cannot be know statically (like the polymorphic relationship you have expressed here) must be supplied via attributes. In this case you need to specify that your FileMissingError data contract is a known type of its base class, ErrorBase.

Clothier answered 24/2, 2010 at 21:0 Comment(2)
So that means that I need to specify all child Error classes here? Is there any other way of doing that? I don't like the fact that the parent class would be aware of child classes in a way of attached attriibutes and "using" clauses in the class file. The exception said "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." So is there any way to just add them to a list of known types? How do I do that?Horseradish
You can pass the list of known types to the serialized only if you are manually serializing the contracts yourself. Since it appears as though you are allowing WCF to handle the serialization for you, the only thing you can do is add a KnownTypeAttribute to the base class, one for each child class that it needs to know about.Clothier
B
2

A tad bit late, but maybe for future generations. =)

If you don't want to add an attribute for every child class to your parent class, you could construct a list of known types in the parent classes static constructor using

 IEnumerable<Assembly> assemblies = AppDomain.CurrentDomain
                                             .GetAssemblies()
                                             .Where(a => !a.GlobalAssemblyCache);

 IEnumerable<Type> serializableTypes = assemblies.SelectMany(a => a.GetTypes())
                                                 .Where(t => IsSerializable(t));

// ...

private static bool IsSerializable(Type type)
{
    return type.GetCustomAttributes(true).Any(a => a is DataContractAttribute);
}

and pass this list to the de/serializers constructor. I don't know how robust this solution is, but that's what I am doing and so far it works. It is a little slow, so make sure to cache the result.

Baumgartner answered 23/10, 2012 at 14:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.