MULTIMEDIA

WCF Services : Data Contract - Hierarchy

9/20/2010 9:57:25 AM
Your data contract class may be a subclass of another data contract class. WCF requires that every level in the class hierarchy explicitly opt in for a given data contract, because the DataContract attribute is not inheritable:
[DataContract]
class Contact
{
[DataMember]
public string FirstName;

[DataMember]
public string LastName;
}
[DataContract]
class Customer : Contact
{
[DataMember]
public int CustomerNumber;
}

Failing to designate every level in the class hierarchy as serializable or as a data contract will result in an InvalidDataContractException at the service load time. WCF lets you mix the Serializable and DataContract attributes in the class hierarchy:

[Serializable]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}

However, the Serializable attribute will typically be at the root of the class hierarchy, if it appears at all, because new classes should use the DataContract attribute. When you export a data contract hierarchy, the metadata maintains the hierarchy, and all levels of the class hierarchy are exported when you make use of the subclass in a service contract:

[ServiceContract]
interface IContactManager
{
[OperationContract]
void AddCustomer(Customer customer); //Contact is exported as well
...
}

1. Known Types

In traditional object-oriented programming, a reference to a subclass is also a reference to its base class, so the subclass maintains an Is-A relationship with its base class. Any method that expects a reference to a base class can also accept a reference to its subclass. This is a direct result of the way the compiler spans the state of the subclass in memory, by appending it right after the base class section.

While languages such as C# let you substitute a subclass for a base class in this manner, this is not the case with WCF operations. By default, you cannot use a subclass of a data contract class instead of its base class. Consider this service contract:

[ServiceContract]
interface IContactManager
{
//Cannot accept Customer object here:
[OperationContract]
void AddContact(Contact contact);

//Cannot return Customer objects here:
[OperationContract]
Contact[] GetContacts();
}

Suppose the client defined the Customer class as well:

[DataContract]
class Customer : Contact
{
[DataMember]
public int CustomerNumber;
}

While the following code will compile successfully, it will fail at runtime:

Contact contact = new Customer();

ContactManagerClient proxy = new ContactManagerClient();

//Service call will fail:
proxy.AddContact(contact);

proxy.Close();

The reason is that you are not actually passing an object reference; you are instead passing the object’s state. When you pass in a Customer instead of a Contact, as in the previous example, the service does not know it should deserialize the Customer portion of the state.

Likewise, when a Customer is returned instead of a Contact, the client does not know how to deserialize it, because all it knows about are Contacts, not Customers:

/////////////////////////// Service Side //////////////////////////////
[DataContract]
class Customer : Contact
{
[DataMember]
public int CustomerNumber;
}
class CustomerManager : IContactManager
{
List<Customer> m_Customers = new List<Customer>();

public Contact[] GetContacts()
{
return m_Customers.ToArray();
}
//Rest of the implementation
}
/////////////////////////// Client Side //////////////////////////////
ContactManagerClient proxy = new ContactManagerClient();
//Call will fail if there are items in the list:
Contact[] contacts = proxy.GetContacts();
proxy.Close();

The solution is to explicitly tell WCF about the Customer class using the KnownTypeAttribute, defined as:

[AttributeUsage(AttributeTargets.Struct|AttributeTargets.Class,
AllowMultiple = true)]
public sealed class KnownTypeAttribute : Attribute
{
public KnownTypeAttribute(Type type);
//More members
}

The KnownType attribute allows you to designate acceptable subclasses for the data contract:

[DataContract]
[KnownType(typeof(Customer))]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}

On the host side, the KnownType attribute affects all contracts and operations using the base class, across all services and endpoints, allowing it to accept subclasses instead of base classes. In addition, it includes the subclass in the metadata so that the client will have its own definition of the subclass and will be able to pass the subclass instead of the base class. If the client also applies the KnownType attribute on its copy of the base class, it can in turn receive the known subclass back from the service.

2. Service Known Types

The downside of using the KnownType attribute is that it may be too broad in scope. WCF also provides the ServiceKnownTypeAttribute, defined as:

[AttributeUsage(AttributeTargets.Interface|
AttributeTargets.Method |
AttributeTargets.Class,
AllowMultiple = true)]
public sealed class ServiceKnownTypeAttribute : Attribute
{
public ServiceKnownTypeAttribute(Type type);
//More members
}

Instead of using the KnownType attribute on the base data contract, you can apply the ServiceKnownType attribute on a specific operation on the service side. Then, only that operation (across all supporting services) can accept the known subclass:

[DataContract]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}

[ServiceContract]
interface IContactManager
{
[OperationContract]
[ServiceKnownType(typeof(Customer))]
void AddContact(Contact contact);

[OperationContract]
Contact[] GetContacts();
}

Other operations cannot accept the subclass.

When the ServiceKnownType attribute is applied at the contract level, all the operations in that contract can accept the known subclass across all implementing services:

[ServiceContract]
[ServiceKnownType(typeof(Customer))]
interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);

[OperationContract]
Contact[] GetContacts();
}


Warning: Do not apply the ServiceKnownType attribute on the service class itself. Although the code will compile, this will have an effect only when you don’t define the service contract as an interface (something I strongly discourage in any case). If you apply the ServiceKnownType attribute on the service class while there is a separate contract definition, it will have no effect.

Whether you apply the ServiceKnownType attribute at the operation or the contract level, the exported metadata and the generated proxy will have no trace of it and will include the KnownType attribute on the base class only. For example, given this service-side definition:

[ServiceContract]
[ServiceKnownType(typeof(Customer))]
interface IContactManager
{...}

The imported definition will be:

[DataContract]
[KnownType(typeof(Customer))]
class Contact
{...}
[DataContract]
class Customer : Contact
{...}
[ServiceContract]
interface IContactManager
{...}

You can manually rework the client-side proxy class to correctly reflect the service-side semantic by removing the KnownType attribute from the base class and applying the ServiceKnownType attribute to the appropriate level in the contract.

3. Multiple Known Types

You can apply the KnownType and ServiceKnownType attributes multiple times to inform WCF about as many known types as required:

[DataContract]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}

[DataContract]
class Person : Contact
{...}

[ServiceContract]
[ServiceKnownType(typeof(Customer))]
[ServiceKnownType(typeof(Person))]
interface IContactManager
{...}

The WCF formatter uses reflection to collect all the known types of the data contracts, then examines the provided parameter to see if it is of any of the known types.

Note that you must explicitly add all levels in the data contract class hierarchy. Adding a subclass does not add its base class(es):

[DataContract]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}

[DataContract]
class Person : Customer
{...}

[ServiceContract]
[ServiceKnownType(typeof(Customer))]
[ServiceKnownType(typeof(Person))]
interface IContactManager
{...}

4. Configuring Known Types

The main downside of the known types attributes is that they require the service or the client to know in advance about all possible subclasses the other party may want to use. Adding a new subclass necessitates changing the code, recompiling, and redeploying. To alleviate this, WCF lets you configure the known types in the service’s or client’s config file, as shown in Example 1. You need to provide not just the type names, but also the names of their containing assemblies.

Example 1. Known types in config file
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type = "Contact,MyClassLibrary,Version = 1.0.0.0,Culture = neutral,
PublicKeyToken = null">
<knownType type = "Customer,MyOtherClassLibrary,Version = 1.0.0.0,
Culture = neutral,PublicKeyToken = null"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>


When not relying on string name or assembly version resolution, you can just use the assembly-friendly name:

<add type = "Contact,MyClassLibrary">
<knownType type = "Customer,MyOtherClassLibrary"/>
</add>

Including the known types in the config file has the same effect as applying the KnownType attribute on the data contract, and the published metadata will include the known types definition.


Note: Using a config file to declare a known type is the only way to add a known type that is internal to another assembly.

5. Data Contract Resolvers

The final technique for addressing known types would be to do so programmatically. This is the most powerful technique, since you can extend it to completely automate dealing with the known type issues. This is possible using a mechanism called data contract resolvers introduced by WCF in .NET 4.0. In essence, you are given a chance to intercept the operation’s attempt to serialize and deserialize parameters and resolve the known types at runtime both on the client and service sides.

The first step in implementing a programmatic resolution is to derive from the abstract class DataContractResolver defined as:

public abstract class DataContractResolver
{
protected DataContractResolver();

public abstract bool TryResolveType(Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace);

public abstract Type ResolveName(string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver);
}


Your implementation of the TryResolveType() is called when WCF tries to serialize a type into a message and the type provided (the type parameter) is different from the type declared in the operation contract (the declaredType parameter). If you want to serialize the type, you need to provide some unique identifiers to serve as keys into a dictionary that maps identifiers to types. WCF will provide you those keys during deserialization so that you can bind against that type. Note that the namespace key cannot be an empty string or a null. While virtually any unique string value will do for the identifiers, I recommend simply using the CLR type name and namespace. Set the type name and namespace into the typeName and typeNamespace out parameters.

If you return true from TryResolveType(), the type is considered resolved as if you had applied the KnownType attribute. If you return false, WCF fails the call. Note that TryResolveType() must resolve all known types, even those types that are decorated with the KnownType attribute or are listed in the config file. This presents a potential risk: it requires the resolver to be coupled to all known types in the application and will fail the operation call with other types that may come over time. It is therefore preferable as a fallback contingency to try to resolve the type using the default known types resolver that WCF would have used if your resolver was not in use. This is exactly what the knownTypeResolver parameter is for. If your implementation of TryResolveType() cannot resolve the type, it should delegate to knownTypeResolver.

The ResolveName() is called when WCF tries to deserialize a type out of a message, and the type provided (the type parameter) is different from the type declared in the operation contract (the declaredType parameter). In this case, WCF provides you with the type name and namespace identifiers so that you can map them back to a known type.

For example, consider again these two data contracts:

[DataContract]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}

Example 2 lists a simple resolver for the Customer type.

Example 2. The CustomerResolver
class CustomerResolver : DataContractResolver
{
string Namespace
{
get
{
return typeof(Customer).Namespace ?? "global";
}
}
string Name
{
get
{
return typeof(Customer).Name;
}
}

public override Type ResolveName(string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver)
{
if(typeName == Name && typeNamespace == Namespace)
{
return typeof(Customer);
}
else
{
return knownTypeResolver.
ResolveName(typeName,typeNamespace,declaredType,null);
}
}

public override bool TryResolveType(Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace)
{
if(type == typeof(Customer))
{
XmlDictionary dictionary = new XmlDictionary();
typeName = dictionary.Add(Name);
typeNamespace = dictionary.Add(Namespace);
return true;
}
else
{
return knownTypeResolver.
TryResolveType(type,declaredType,null,out typeName,out typeNamespace);
}
}
}


5.1. Installing the data contract resolver

The resolver must be attached as a behavior for each operation on the proxy or the service endpoint. For example, how you choose to resolve a known type (be it declaratively via the KnownType attribute or programmatically with a resolver) is a local implementation detail, on both the client and the service sides.

In WCF, every endpoint it represented by the type ServiceEndpoint. The ServiceEndpoint has a property called Contract of the type ContractDescription:

public class ServiceEndpoint
{
public ContractDescription Contract
{get;set;}

//More members
}

ContractDescription has a collection of operation descriptions, with an instance of OperationDescription for every operation on the contract:

public class ContractDescription
{
public OperationDescriptionCollection Operations
{get;}

//More members
}
public class OperationDescriptionCollection :
Collection<OperationDescription>
{...}

Each OperationDescription has a collection of operation behaviors of the type IOperationBehavior:

public class OperationDescription
{
public KeyedByTypeCollection<IOperationBehavior> Behaviors
{get;}
//More members
}

In its collection of behaviors, every operation always has a behavior called DataContractSerializerOperationBehavior with a DataContractResolver property:

public class DataContractSerializerOperationBehavior : IOperationBehavior,...
{
public DataContractResolver DataContractResolver
{get;set}
//More members
}


The DataContractResolver property defaults to null, but you can set it to your custom resolver.

To install a resolver on the host side, you must iterate over the collection of endpoints in the service description maintained by the host:

public class ServiceHost : ServiceHostBase
{...}

public abstract class ServiceHostBase : ...
{
public ServiceDescription Description
{get;}
//More members
}

public class ServiceDescription
{
public ServiceEndpointCollection Endpoints
{get;}

//More members
}

public class ServiceEndpointCollection : Collection<ServiceEndpoint>
{...}

Suppose you have the following service definition and are using the resolver in Example 3-7:

[ServiceContract]
interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);
...
}
class ContactManager : IContactManager
{...}

Example 3 shows how to install the resolver on the host for the ContactManager service.

Example 3. Installing a resolver on the host

foreach(ServiceEndpoint endpoint in host.Description.Endpoints)
{
foreach(OperationDescription operation in endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
behavior.DataContractResolver = new CustomerResolver();
}
}
host.Open();


On the client side, you follow similar steps, except you need to set the resolver on the single endpoint of the proxy or the channel factory. For example, given this proxy class definition:

class ContactManagerClient : ClientBase<IContactManager>,IContactManager
{...}

Example 4 shows how to install the resolver on the proxy in order to call the service of Example 3 with a known type.

Example 4. Installing a resolver on the proxy
ContactManagerClient proxy = new ContactManagerClient();

foreach(OperationDescription operation in proxy.Endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<DataContractSerializerOperationBehavior>();

behavior.DataContractResolver = new CustomerResolver();
}

Customer customer = new Customer();
...

proxy.AddContact(customer);


5.2. The generic resolver

Writing and installing a resolver for each type is obviously a lot of work, requiring you to meticulously track all known types, something that is error-prone and can quickly get out of hand in an evolving system. To automate implementing a resolver, I wrote the class GenericResolver defined as:

public class GenericResolver : DataContractResolver
{
public Type[] KnownTypes
{get;}

public GenericResolver();
public GenericResolver(Type[] typesToResolve);

public static GenericResolver Merge(GenericResolver resolver1,
GenericResolver resolver2);
}

GenericResolver offers two constructors. One constructor can accept an array of known types to resolve. The types in the array can include bounded generic types, that is, generic types for which you have already specified type parameters. The parameterless constructor will automatically add as known types all classes and structs in the calling assembly and all public classes and structs in assemblies referenced by the calling assemblies. The parameterless constructor will not add types originating in a .NET Framework–referenced assembly. Note that the parameterless constructor will also ignore generic types (since there is no way of inferring the type parameters used in code). In addition, GenericResolver offers the Merge() static method that you can use to merge the known types of two resolvers, returning a GenericResolver that resolves the union of the two resolvers provided. Example 5 shows the pertinent portion of GenericResolver without reflecting the types in the assemblies, which has nothing to do with WCF.

Example 5. Implementing GenericResolver (partial)
{
const string DefaultNamespace = "global";

readonly Dictionary<Type,Tuple<string,string>> m_TypeToNames;
readonly Dictionary<string,Dictionary<string,Type>> m_NamesToType;

public Type[] KnownTypes
{
get
{
return m_TypeToNames.Keys.ToArray();
}
}

//Get all types in calling assembly and referenced assemblies
static Type[] ReflectTypes()
{...}

public GenericResolver() : this(ReflectTypes())
{}
public GenericResolver(Type[] typesToResolve)
{
m_TypeToNames = new Dictionary<Type,Tuple<string,string>>();
m_NamesToType = new Dictionary<string,Dictionary<string,Type>>();

foreach(Type type in typesToResolve)
{
string typeNamespace = GetNamespace(type);
string typeName = GetName(type);

m_TypeToNames[type] = new Tuple<string,string>(typeNamespace,typeName);

if(m_NamesToType.ContainsKey(typeNamespace) == false)
{
m_NamesToType[typeNamespace] = new Dictionary<string,Type>();
}

m_NamesToType[typeNamespace][typeName] = type;
}
}
static string GetNamespace(Type type)
{
return type.Namespace ?? DefaultNamespace;
}
static string GetName(Type type)
{
return type.Name;
}

public static GenericResolver Merge(GenericResolver resolver1,
GenericResolver resolver2)
{
if(resolver1 == null)
{
return resolver2;
}
if(resolver2 == null)
{
return resolver1;
}
List<Type> types = new List<Type>();

types.AddRange(resolver1.KnownTypes);
types.AddRange(resolver2.KnownTypes);

return new GenericResolver(types.ToArray());
}
public override Type ResolveName(string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver)
{
if(m_NamesToType.ContainsKey(typeNamespace))
{
if(m_NamesToType[typeNamespace].ContainsKey(typeName))
{
return m_NamesToType[typeNamespace][typeName];
}
}
return knownTypeResolver.
ResolveName(typeName,typeNamespace,declaredType,null);
}
public override bool TryResolveType(Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace)
{
if(m_TypeToNames.ContainsKey(type))
{
XmlDictionary dictionary = new XmlDictionary();
typeNamespace = dictionary.Add(m_TypeToNames[type].Item1);
typeName = dictionary.Add(m_TypeToNames[type].Item2);
return true;
}
else
{
return knownTypeResolver.
TryResolveType(type,declaredType,null,out typeName,
out typeNamespace);
}
}
}


The most important members of GenericResolver are the m_TypeToNames and the m_NamesToType dictionaries. m_TypeToNames maps a type to a tuple of its name and namespace. m_NamesToType maps a type namespace and name to the actual type. The constructor that takes the array of types initializes those two dictionaries. The Merge() method uses the helper property KnownTypes[] to merge the two resolvers. The TryResolveType() method uses the provided type as a key into the m_TypeToNames dictionary to read the type’s name and namespace. The ResolveName() method uses the provided namespace and name as keys into the m_NamesToType dictionary to return the resolved type.

5.3. Installing the generic resolver

While you could use tedious code similar to Example 3-8 and Example 3-9 to install GenericResolver, it is best to streamline it with extension methods. To that end, use my AddGenericResolver() methods of GenericResolverInstaller defined as:

public static class GenericResolverInstaller
{
public static void AddGenericResolver(this ServiceHost host,
params Type[] typesToResolve);

public static void AddGenericResolver<T>(this ClientBase<T> proxy,
params Type[] typesToResolve) where T : class;

public static void AddGenericResolver<T>(this ChannelFactory<T> factory,
params Type[] typesToResolve) where T : class;
}


The AddGenericResolver() method accepts a params array of types, which means an open-ended, comma-separated list of types. If you do not specify types, that will make AddGenericResolver() add as known types all classes and structs in the calling assembly plus the public classes and structs in referenced assemblies. For example, given these known types:

[DataContract]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}

[DataContract]
class Employee : Contact
{...}

Example 6 shows several examples of using the AddGenericResolver() extension method.

Example 6. Installing GenericResolver
//Host side

ServiceHost host1 = new ServiceHost(typeof(ContactManager));
//Resolve all types in this and referenced assemblies
host1.AddGenericResolver();
host1.Open();

ServiceHost host2 = new ServiceHost(typeof(ContactManager));
//Resolve only Customer and Employee
host2.AddGenericResolver(typeof(Customer),typeof(Employee));
host2.Open();

ServiceHost host3 = new ServiceHost(typeof(ContactManager));
//Can call AddGenericResolver() multiple times
host3.AddGenericResolver(typeof(Customer));
host3.AddGenericResolver(typeof(Employee));
host3.Open();

//Client side

ContactManagerClient proxy = new ContactManagerClient();
//Resolve all types in this and referenced assemblies
proxy.AddGenericResolver();

Customer customer = new Customer();
...
proxy.AddContact(customer);

GenericResolverInstaller not only installs the GenericResolver, it also tries to merge it with the old generic resolver (if present). This means you can call the AddGenericResolver() method multiple times. This is handy when adding bounded generic types:

[DataContract]
class Customer<T> : Contact
{...}

ServiceHost host = new ServiceHost(typeof(ContactManager));

//Add all non-generic known types
host.AddGenericResolver();

//Add the generic types
host.AddGenericResolver(typeof(Customer<int>,Customer<string>));

host.Open();

Example 7 shows the partial implementation of GenericResolverInstaller.

Example 7. Implementing GenericResolverInstaller
public static class GenericResolverInstaller
{
public static void AddGenericResolver(this ServiceHost host,
params Type[] typesToResolve)
{
foreach(ServiceEndpoint endpoint in host.Description.Endpoints)
{
AddGenericResolver(endpoint,typesToResolve);
}
}

static void AddGenericResolver(ServiceEndpoint endpoint,Type[] typesToResolve)
{
foreach(OperationDescription operation in endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior behavior = operation.
Behaviors.Find<DataContractSerializerOperationBehavior>();

GenericResolver newResolver;

if(typesToResolve == null || typesToResolve.Any() == false)
{
newResolver = new GenericResolver();
}
else
{
newResolver = new GenericResolver(typesToResolve);
}

GenericResolver oldResolver = behavior.DataContractResolver
as GenericResolver;
behavior.DataContractResolver =
GenericResolver.Merge(oldResolver,newResolver);
}
}
}


If no types are provided, AddGenericResolver() will use the parameterless constructor of GenericResolver. Otherwise, it will use only the specified types by calling the other constructor. Note the merging with the old resolver if present.

5.4. GenericResolver and ServiceHost<T>

Using a generic resolver is great, and arguably should always be associated with any host since you have no way of knowing in advance all the known types your system will encounter. In that case, instead of explicitly adding GenericResolver to the host (as in Example 3-11), you can also have a custom host type that adds the generic resolver implicitly. For example, My ServiceHost<T> does just that:

public class ServiceHost<T> : ServiceHost
{
protected override void OnOpening()
{
this.AddGenericResolver();
...
}
}

This means that the following code is all you will need when it comes to known types:

ServiceHost host = new ServiceHost<ContactManager>();
host.Open();


5.5. Generic resolver attribute

If your service relies on the generic resolver by design, it is better not to be at the mercy of the host and to declare your need for the generic resolver at design time. To that end, I wrote the GenericResolverBehaviorAttribute:

[AttributeUsage(AttributeTargets.Class)]
public class GenericResolverBehaviorAttribute : Attribute,IServiceBehavior
{
void IServiceBehavior.Validate(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
ServiceHost host = serviceHostBase as ServiceHost;
host.AddGenericResolver();
}
//More members
}

This concise attribute makes the service independent of the host and the config file:

[GenericResolverBehavior]
class ContactManager : IContactManager
{...}

GenericResolverBehaviorAttribute derives from IServiceBehavior. IServiceBehavior is a special WCF interface and it is the most commonly used extension in WCF. Subsequent chapters will make extensive use of it and will discuss its various methods. Briefly, when the host loads the service, it uses reflection to determine if the service class has an attribute that supports IServiceBehavior and, if so, the host calls the IServiceBehavior methods, specifically the Validate() method, which lets the attribute interact with the host. In the case of GenericResolverBehaviorAttribute, it adds the generic resolver to the host.

6. Objects and Interfaces

The base type of a data contract class or a struct can be an interface:

interface IContact
{
string FirstName
{get;set;}
string LastName
{get;set;}
}
[DataContract]
class Contact : IContact
{...}

You can use such a base interface in your service contract or as a data member in a data contract if you use the ServiceKnownType attribute to designate the actual data type:

[ServiceContract]
[ServiceKnownType(typeof(Contact))]
interface IContactManager
{
[OperationContract]
void AddContact(IContact contact);

[OperationContract]
IContact[] GetContacts();
}

You cannot apply the KnownType attribute on the base interface, because the interface itself will not be included in the exported metadata. Instead, the imported service contract will be object-based and it will not include the data contract interface:

//Imported definitions:
[DataContract]
class Contact
{...}

[ServiceContract]
interface IContactManager
{
[OperationContract]
[ServiceKnownType(typeof(Contact))]
[ServiceKnownType(typeof(object[]))]
void AddContact(object contact);

[OperationContract]
[ServiceKnownType(typeof(Contact))]
[ServiceKnownType(typeof(object[]))]
object[] GetContacts();
}

The imported definition will always have the ServiceKnownType attribute applied at the operation level, even if it was originally defined at the scope of the contract. In addition, every operation will include a union of all the ServiceKnownType attributes required by all the operations, including a redundant service known type attribute for the array. These are relics from a time when these definitions were required in a beta version of WCF.

You can manually rework the imported definition to have only the required ServiceKnownType attributes:

[DataContract]
class Contact
{...}

[ServiceContract]
interface IContactManager
{
[OperationContract]
[ServiceKnownType(typeof(Contact))]
void AddContact(object contact);

[OperationContract]
[ServiceKnownType(typeof(Contact))]
object[] GetContacts();
}

Or better yet, if you have the definition of the base interface on the client side or if you refactor that definition, you can use that instead of object. This gives you an added degree of type safety as long as you add a derivation from the interface to the data contract:

[DataContract]
class Contact : IContact
{...}

[ServiceContract]
interface IContactManager
{
[OperationContract]
[ServiceKnownType(typeof(Contact))]
void AddContact(IContact contact);

[OperationContract]
[ServiceKnownType(typeof(Contact))]
IContact[] GetContacts();
}

However, you cannot replace the object in the imported contract with the concrete data contract type, because it is no longer compatible:

//Invalid client-side contract
[ServiceContract]
interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);

[OperationContract]
Contact[] GetContacts();
}

Other  
 
Most View
Lenovo IdeaPad Z480 – The Stylish Idealist
Enhance OS X's Features
Windows Small Business Server 2011 : Working with Disks During Installation
Wonderful Accessories For Your Life
SEH Computerteknik myUTN-150 - USB Deviceserver
Group Test: Android Tablets – November 2012 (Part 2) - DisGo 9104, Samsung Galaxy Tab 27.0 Wi-Fi
Olympus 15mm F8.0 Body Cap Lens Review
Ultimate Guide To Nexus (Part 3)
Corsair CX430M Power Supply - One Of Corsair's "Builder Series"
Apple iPad Mini - Falling Behind
Top 10
Publishing and Web Content Management : New in SharePoint 2013 (part 8) - SEO Page Optimization - Edit Display Templates
Publishing and Web Content Management : New in SharePoint 2013 (part 7) - SEO Page Optimization - Edit Master Pages
Publishing and Web Content Management : New in SharePoint 2013 (part 6) - SEO Page Optimization - Upload Design Files
Publishing and Web Content Management : New in SharePoint 2013 (part 5) - SEO Page Optimization - XML Site Map, Site Design Manager
Publishing and Web Content Management : New in SharePoint 2013 (part 4) - SEO Page Optimization - Page-Level SEO, Site Collection SEO
Publishing and Web Content Management : New in SharePoint 2013 (part 3) - Using the Content Search Web Part, Managed Navigation
Publishing and Web Content Management : New in SharePoint 2013 (part 2) - Catalog Enable List/Library
Publishing and Web Content Management : New in SharePoint 2013 (part 1) - Content Authoring Improvements, Image Rendition
SQL Server 2012 : Using SQLdiag Configuration Manager (part 4) - Adding Your Own Custom Collectors,Saving and Using a SQLdiag Configuration
SQL Server 2012 : Using SQLdiag Configuration Manager (part 3) - Feature-Specific Custom Diagnostics, Capturing Extended Events