[WCF] DataContract et héritage

Publié le 28 août 2013 par Jeremy.jeanson

Encore une fois, j'exhume un petit peu de code pour répondre à un problème de POO qui semble simple, mais qui peut devenir cauchemardesque dès que l'on y associe WCF et les DataContract.

Le scénario est le suivant :

  • On a prévu d'utiliser une classe (Item) comme argument d'une méthode de service (Service1).
  • Côté client, l'objet réellement manipulé est une instance d'une classe qui hérite d'Item (ItemDerived).
  • Côté client, la classe de base n'est jamais manipulée directement.
  • Chaque classe ou interface est dans un namespace ou un assembly différent

Le diagramme de classe donne ceci

Pour appeler le service on utilise un code tel que celui-ci :

var result = proxy.Process(new ItemDerived());

Ou celui-là :

var result = proxy.Process((Item)new ItemDerived());

En l'état, les appels au service provoquent systématiquement une exception du style : le type utilisé lors de la sérialisation n'est pas du type attendu

Note : les messages d'exception semblent varier d'une version du Framework à l'autre.

Dans les faits, lors de la sérialisation, le DataContractSerializer est perdu :

  • Il attend une classe Item dans un namespace X avec un nom Y avec un DataContract (namespace X, name Y)
  • Il reçoit une classe ItemDerived dans un namespace U avec un nom V sans DataContract (on utilise juste l'héritage).

Il faut donc aider notre DataContractSerializer à s'en sortir. La solution est toute simple : partager une information claire et commune, le contrat.

La classe item est donc modifiée pour exposer deux constantes : le DataContractNamespace et le DataContractName.

Si on ne conserve que les déclarations de classes et des constantes, on obtient ceci :

    
      namespace Demos.Wcf.Base

    
  
    
      {

    
  
    
        // Use a data contract as illustrated in the sample below to add composite types to service operations.
    
  
    
        [DataContract(Name = DataContractName, Namespace = DataContractNamespace)]

    
  
    
        public class Item
    
  
    
        {

    
  
    
        protected const String DataContractName = "Item";

    
  
    
        protected const String DataContractNamespace = "Datas";

    
  
    
        }

    
  
    
      }

    
  
    
      namespace Demos.Wcf.Inherit

    
  
    
      {

    
  
    
        [DataContract(Name = DataContractName, Namespace = DataContractNamespace)]

    
  
    
        public class ItemDerived : Item
    
  
    
        {

    
  
    
        }

    
  
    
      }

    
  

Note : J'ai découvert par hasard que mes constantes protected pouvaient être utilisées via les attribues de la classe dérivée.

En partageant ces informations, on peut maintenant utiliser des classes dérivées avec notre proxy. Il n'y a plus d'exceptions.

Simple comme WCF ;)