Servicios polimórficos en WCF

El polimorfismo es una característica de la programación orientada a objetos que nos hace la vida más fácil. Se basa en el concepto de herencia, el agrupamiento inteligente para reducir el código y el mantenimiento del mismo, además de las implicaciones semánticas que puede acarrear el uso de la misma.

En Windows Communication Foundation podemos crear servicios polimórficos, es decir, podemos aprovechar esta característica de la POO. Sin embargo, debemos informar al servicio de que queremos hacerlo, y darle pistas para poder serializar y deserializar los objetos correctamente. Para ello existe el concepto de KnownTypes. Mediante los KnownTypes le informamos al generador de WSDL de WCF de los tipos que están relacionados con una clase base.

Veamos cómo se comportaría el servicio sin los KnownTypes:

Creamos un servicio de WCF en el que queremos poder manipular personas y empleador. Para ello necesitamos una dirección (Address), un binding (¿cómo se traduce esto?) y un contrato (Contract) (ABC). La dirección a utilizar será http://localhost:8080/PolyService

El contrato lo podemos ver a continuación:

   1:  namespace Geeks.PolyService
   2:  {
   3:      [ServiceContract()]
   4:      interface IPolyService
   5:      {
   6:          [OperationContract]
   7:          bool IsBirthDay(Person person);
   8:          [OperationContract]
   9:          int GetAge(Person person);
  10:      }
  11:  }

Como podemos ver, en el contrato utilizamos un objeto llamado Person. Vamos a ver el contenido de esta clase, y vamos a definir una clase que herede de Person, llamada Employee.

A continuación podemos ver ambas clases:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using System.Runtime.Serialization;
   5:   
   6:  namespace Geeks.PolyService
   7:  {
   8:      [DataContract]
   9:      public class Person
  10:      {
  11:          private DateTime birthDate;
  12:   
  13:          [DataMember]
  14:          public DateTime BirthDate
  15:          {
  16:              get { return birthDate; }
  17:              set { birthDate = value; }
  18:          }
  19:      }
  20:   
  21:      [DataContract]
  22:      public class Employee : Person
  23:      {
  24:          private float income;
  25:   
  26:          [DataMember]
  27:          public float Income
  28:          {
  29:              get { return income; }
  30:              set { income = value; }
  31:          }
  32:      
  33:      }
  34:  }

Y por último la clase que implementa el servicio:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using System.ServiceModel;
   5:  using System.Runtime.Serialization;
   6:   
   7:  namespace Geeks.PolyService
   8:  {
   9:   
  10:      public class PolyService : IPolyService
  11:      {
  12:   
  13:          #region IPolyService Members
  14:   
  15:          public bool IsBirthDay(Person person)
  16:          {
  17:              DateTime currentDate = DateTime.Now;
  18:   
  19:              return ((person.BirthDate.Month == currentDate.Month) && (person.BirthDate.Day == currentDate.Day));
  20:          }
  21:   
  22:          public int GetAge(Person person)
  23:          {
  24:              DateTime currentDate = DateTime.Now;
  25:              return currentDate.Year - person.BirthDate.Year - 
  26:                     (person.BirthDate.Month > currentDate.Month ? 1
  27:                     : (person.BirthDate.Month != currentDate.Month) ? 0
  28:                     : (person.BirthDate.Day > currentDate.Day) ? 1 : 0);
  29:   
  30:          }
  31:   
  32:          #endregion
  33:      }
  34:   
  35:  }

Una vez tenemos la clase que implementa el servicio, el contrato, etc. debemos implementar un host que arranque el servicio, o bien usando IIS, generar un servicio IIS que haga referencia a nuestro componente. En el código de ejemplo está implementado un host. Tomemos la vía que tomemos para alojar nuestro servicio, hemos de configurarlo apropiadamente. Veamos qué pasaría si lo configuramos sin informarle de los KnownTypes:

   1:  <configuration>
   2:      <system.serviceModel>
   3:          <diagnostics>
   4:              <messageLogging logEntireMessage="true" logMalformedMessages="true"
   5:               logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="false" />
   6:          </diagnostics>
   7:          <services>
   8:              <service name="Geeks.PolyService.PolyService" behaviorConfiguration="metadataBehavior">
   9:                  <endpoint address="mex" binding="mexHttpBinding" name="metadataEndpoint"
  10:                       contract="Geeks.PolyService.IPolyService" />
  11:                  <endpoint contract="Geeks.PolyService.IPolyService" binding="wsHttpBinding"/>
  12:              </service>
  13:          </services>
  14:          <!-- Enable service metadata to be populated. -->
  15:          <behaviors>
  16:              <serviceBehaviors>
  17:                  <behavior name="metadataBehavior">
  18:                      <serviceMetadata httpGetEnabled="true" />
  19:                      <serviceDebug includeExceptionDetailInFaults="true"/>
  20:                  </behavior>
  21:              </serviceBehaviors>
  22:          </behaviors>
  23:      </system.serviceModel>
  24:  </configuration>

Arrancamos nuestro servicio, y desde Visual Studio 2005 añadimos una referencia a servicio…

Y ahora supongamos que queremos usar nuestra clase Employee. Como la clase Employee hereda de Person, en principio deberíamos poder utilizarla. Sin embargo, utilizando el intellisense, vemos que el proxy que nos ha generado es el siguiente:

Hmmm… La clase Employee no aparece por ninguna parte…

Si lo pensamos con más detenimiento, podemos intentar averiguar cómo se genera el WSDL. Sería lógico que WCF se recorriese los contratos que se han definido, y que WCF examine los parámetros de las operaciones que contiene dicho contrato. Como todas las operaciones utilizan la clase Person, genera el WSDL necesario para dicha clase. Tiene sentido, verdad? Sin embargo, Employee hereda de Person, y no genera el proxy para esa clase. Hemos de indicarle a WCF que en cliente también vamos a utilizar dicha clase, y para ello se utilizan los KnownTypes. Tenemos dos opciones para ello,

  • Etiquetar la clase base con un atributo KnownType, indicándole qué clases heredan de ella. Esta solución, a parte de no ser muy flexible, para mí no tiene demasiado sentido, ya que la relación entre las clases ya la estoy indicando mediante la herencia…
  • Informar de los tipos mediante ficheros de configuración. Esta opción es más flexible, ya que simplemente cambiando el fichero de configuración podemos controlar qué es visible y qué no desde el cliente.

El fichero de configuración resultante sería el siguiente:

   1:  <configuration>
   2:      <system.serviceModel>
   3:          <diagnostics>
   4:              <messageLogging logEntireMessage="true" logMalformedMessages="true"
   5:               logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="false" />
   6:          </diagnostics>
   7:          <services>
   8:              <service name="Geeks.PolyService.PolyService" behaviorConfiguration="metadataBehavior">
   9:                  <endpoint address="mex" binding="mexHttpBinding" name="metadataEndpoint"
  10:                       contract="Geeks.PolyService.IPolyService" />
  11:                  <endpoint contract="Geeks.PolyService.IPolyService" binding="wsHttpBinding"/>
  12:              </service>
  13:          </services>
  14:          <!-- Enable service metadata to be populated. -->
  15:          <behaviors>
  16:              <serviceBehaviors>
  17:                  <behavior name="metadataBehavior">
  18:                      <serviceMetadata httpGetEnabled="true" />
  19:                      <serviceDebug includeExceptionDetailInFaults="true"/>
  20:                  </behavior>
  21:              </serviceBehaviors>
  22:          </behaviors>
  23:      </system.serviceModel>
  24:   
  25:      <!-- Add KnownTypes here -->
  26:      <system.runtime.serialization>
  27:          <dataContractSerializer>
  28:              <declaredTypes>
  29:                  <add type="Geeks.PolyService.Person, Geeks.PolyService">
  30:                      <knownType type="Geeks.PolyService.Employee,Geeks.PolyService" />
  31:                  </add>
  32:              </declaredTypes>
  33:          </dataContractSerializer>
  34:      </system.runtime.serialization>
  35:      
  36:  </configuration>

Si ahora eliminamos la referencia al servicio, y la volvemos a añadir (no sé exactamente por qué, pero refrescar en este caso no es suficiente…), veremos que ya podemos utilizar la clase Employee desde cliente, y además funciona correctamente.

Bueno, pues nada más por hoy… Un saludo!

El código de este artículo ha sido formateado con c# code format, y está disponible como adjunto.

Un comentario en “Servicios polimórficos en WCF”

  1. Excelente post…

    Parece que WCF sigue con el mismo problema relacionado al polimorfismo que tenia con WS en VS.NET2005 …

    tampoco han corregido el problema en el IDE vs.net 2008 beta 2…

    saludos

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *