WCF : KnownType - Comment retourner des types enfants du type définit dans le ServiceContract
Lorsque vous créez un service WCF, vous allez créer un contrat. Dans la plupart des cas, ce contrat est une interface décorée de l'attribut ServiceContract contenant des méthodes décorées de l'attribut OperationContract.
Si vous voulez retourner un objet d'un type enfant du type déclaré vous obtiendrez une exception de type : "CommunicationException".
Prenons cet exemple :
[ServiceContract]
public interface IMyService
{
[OperationContract]
Animal GetAnimal(String name);
}
[DataContract]
public abstract class Animal
{ }
[DataContract]
public class Dog : Animal { }
[DataContract]
public class Cat : Animal { }
L'implémentation de ce contrat retournera soit un objet de type Dog ou un objet de type Cat suivant la valeur du paramètre name.
Lors de l'utilisation de ce service vous obtiendrez une CommunicationException :
There was an error while trying to serialize parameter http://tempuri.org/:query. The InnerException message was 'Type 'Test.Dog' with data contract name 'Dog:http://schemas.datacontract.org/2004/07/Test.Doc' 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.'. Please see InnerException for more details.
La raison est que vous retournez un objet qui n'est pas connu du service, il ne sait alors pas comment le sérialiser. Pour palier à ce problème, il faut faire connaitre ces types à notre service. Il existe plusieurs solutions :
- Utilisation de l'attribut ServiceKnownType : Cet attribut s'utilise au niveau du contrat soit sur la définition de l'interface, soit sur les différentes méthodes de l'interface :
[ServiceContract]
public interface IMyService
{
[OperationContract]
[ServiceKnownType(typeof(Dog))]
[ServiceKnownType(typeof(Cat))]
Animal GetAnimal(String name);
}
- Utilisation de l'attribut KnownType : Cet attribut s'utilise au niveau de la classe mère :
[DataContract]
[KnownType(typeof(Dog))]
[KnownType(typeof(Cat))]
public abstract class Animal
{ }
<configuration>
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="Test.Animal, MyAssembly">
<knownType type="Test.Dog, MyAssembly"/>
</add>
<add type="Test.Animal, MyAssembly">
<knownType type="Test.Dog, MyAssembly"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
</configuration>
Il existe d'autres solutions, mais toutes dérives de ces 3 solutions. J'aime particulièrement la solution qui consiste à appeler une méthode static d'un certain type, cette solution permet d'inclure des KnownType générique ou alors d'inclure tous les enfants d'un type via de la reflection.
Pour en savoir plus sur comment référencer vos types dans le contrat, je vous conseille cet article : All about knownTypes ou cet article MSDN : Data Contract Known Types