domingo, 28 de septiembre de 2008

Sindicar contenido con WCF y sobrecarga de métodos

Syndication con WCF, sobrecarga de métodos de paso.

Otra de las funcionalidades que se agregaron con el assemblie System.ServiceModel.Web es la posibilidad de sindicar contenido al estilo .net o sea, fácil y tipificado de verdad, es tán fácil que vamos a ir diréctamente al ejemplo de código. Vamos a ver que en este caso definí una interface del servicio con los atributos, esto es lo más natural y nos da un extra de flexiblidad y un poco más de limpieza en el código que hacerlo como en los ejemplos anteriores:

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter))] //indica la forma de serializar los datos Atom
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface IServicioPrueba
{
    [OperationContract]
    [WebGet] //se usa webget porque tanto RSS con Atom corren sobre HTTP y se invocan con verbos HTTP
    SyndicationFeedFormatter Feed(string tipo);

    [OperationContract]
    [WebGet]
    SyndicationFeedFormatter Feed();
}
public class ServicioPrueba : IServicioPrueba
{
    /// <summary>
    /// Este método nos permite elegir si queremos Atom, sino RSS
    /// </summary>
    public SyndicationFeedFormatter Feed(string tipo)
    {
        SyndicationFeed feed = GenerarFeed();

        if (tipo.Equals("atom"))
        {
            return new Atom10FeedFormatter(feed);
        }
        else
        {
            return new Rss20FeedFormatter(feed);
        }
    }

    /// <summary>
    /// Genera un feed con dos entradas
    /// </summary>
    private static SyndicationFeed GenerarFeed()
    {
        IList<SyndicationItem> items = new List<SyndicationItem>();

        items.Add(new SyndicationItem("primer noticia", "extra extra cupido apesta!", null));
        items.Add(new SyndicationItem("segunda noticia", "Meteoro no seas tonto el enmascarado es tu hermano", null));

        SyndicationFeed feed = new SyndicationFeed(items);
        return feed;
    }

    /// <summary>
    /// En este método lo publicamos como Rss las clases Rss20FeedFormatter y Atom10FeedFormatter heredas
    /// de SyndicationFeedFormatter por lo tanto se pueden intercambiar casi indistintamente (tiene su especialización)
    /// </summary>
    /// <returns></returns>
    public SyndicationFeedFormatter Feed()
    {
        return new Rss20FeedFormatter(GenerarFeed());
    }
}

Por último el host

static void Main(string[] args)
{
    string address = "http://localhost:8080/servicio/";

    using(ServiceHost host = new ServiceHost(typeof(ServicioPrueba)))
    {                            
        //Creamos un endpoint igual al de REST
        ServiceEndpoint endPoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IServicioPrueba)), 
            new WebHttpBinding(), new EndpointAddress(address));

        endPoint.Behaviors.Add(new WebHttpBehavior());

        host.Description.Endpoints.Add(endPoint);

        Console.WriteLine("inicializando..");
        host.Open();
        Console.WriteLine("iniciado..");
        Console.ReadLine();
        Console.WriteLine("cerrando...");
        host.Close();
        Console.WriteLine("listo");
    }
}

Lo que hice fue crear dos métodos, uno (Feed) que devuelve siempre el canal en Rss y otro con un parámetro que nos permite elegir el formato. Si intentamos correr esto vamos a recibir un error muy feo del tipo InvalidOperationException y es porque estamos intentando sobrecargar métodos en WCF.

Sobrecarga de método en WCF

Sí, se pueden sobrecargar métodos en WCF, de echo lo hacemos y al compilar todo va bien, hasta que hacemos Open() del host y todo explota. ¿Qué ocurre? simple, al hacer Open() WCF comieza a generar la metadata para el servicio, como siempre utilizar WSDL y WSDL es SOAP (o naturalmente lo es), y a pesar que SOAP es "Simple Object Access Protocol" (ya sé que desde la versión 1.2 no lo es), no soporta conceptos de orientación a objetos como la sobrecarga por lo tanto BOOM, no funciona.

La metadata generada para el servicio a pesar que el WebHttpBinding es WSDL

<?xml version="1.0" encoding="utf8" ?>
 <wsdl:definitions name="ServicioPrueba" targetNamespace="http://tempuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasisopen.org/wss/2004/01/oasis200401wsswssecurityutility1.0.xsd" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:tns="http://tempuri.org/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex">
   <wsdl:types>
     <xsd:schema targetNamespace="http://tempuri.org/Imports">
      <xsd:import schemaLocation="http://localhost:8080/servicio/?xsd=xsd3" namespace="http://tempuri.org/" />
      <xsd:import schemaLocation="http://localhost:8080/servicio/?xsd=xsd0" namespace="http://schemas.microsoft.com/2003/10/Serialization/" />
      <xsd:import schemaLocation="http://localhost:8080/servicio/?xsd=xsd1" namespace="http://schemas.datacontract.org/2004/07/System.ServiceModel.Syndication" />
      <xsd:import schemaLocation="http://localhost:8080/servicio/?xsd=xsd2" namespace="http://www.w3.org/2005/Atom" />
    </xsd:schema>
  </wsdl:types>
   <wsdl:message name="IServicioPrueba_Feed_InputMessage">
    <wsdl:part name="parameters" element="tns:Feed" />
  </wsdl:message>
   <wsdl:message name="IServicioPrueba_Feed_OutputMessage">
    <wsdl:part name="parameters" element="tns:FeedResponse" />
  </wsdl:message>
   <wsdl:portType name="IServicioPrueba">
     <wsdl:operation name="Feed">
      <wsdl:input wsaw:Action="http://tempuri.org/IServicioPrueba/Feed" message="tns:IServicioPrueba_Feed_InputMessage" />
      <wsdl:output wsaw:Action="http://tempuri.org/IServicioPrueba/FeedResponse" message="tns:IServicioPrueba_Feed_OutputMessage" />
    </wsdl:operation>
  </wsdl:portType>
   <wsdl:binding name="WebHttpBinding_IServicioPrueba" type="tns:IServicioPrueba">
     <wsdl:operation name="Feed">
      <wsdl:input />
      <wsdl:output />
    </wsdl:operation>
  </wsdl:binding>
   <wsdl:service name="ServicioPrueba">
    <wsdl:port name="WebHttpBinding_IServicioPrueba" binding="tns:WebHttpBinding_IServicioPrueba" />
  </wsdl:service>
</wsdl:definitions>

¿Entonces cuál es la solución?

La solución es utilizar un alias para los métodos, ya sé que van a decir que entonces es como si tuvieran otro nombre, y tienen razón, pero parcialmente, si consumimos un servicio WCF desde WCF podemos llamar al mismo método en una u otra sobrecarga (utilizando la interface cliente no el proxy svcutility.exe en caso de usar el proxy se puede editar a mano)

Entonces el código de la interface queda así:

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter))] //indica la forma de serializar los datos Atom
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface IServicioPrueba
{
    [OperationContract(Name = "default")]//alias para esta operación
    [WebGet] //se usa webget porque tanto RSS con Atom corren sobre HTTP y se invocan con verbos HTTP
    SyndicationFeedFormatter Feed(string tipo);

    [OperationContract]
    [WebGet]
    SyndicationFeedFormatter Feed();
}

 

Y listo, ahora funciona

Mágico una vez más, hasta la próxima.

Referencias

SOAP W3C

WSDL W3C

WCF Syndication MDSN