Mostrando entradas con la etiqueta WCF. Mostrar todas las entradas
Mostrando entradas con la etiqueta WCF. Mostrar todas las entradas

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

 

domingo, 21 de septiembre de 2008

REST en WCF

REST con WCF

Siguiendo con el tema de WCF vamos a ver que fácil que es exponer un nuevo endPoint para un servicio existente para publicarlo de otra manera, en el post anterior vimos cómo crear un servicio y exponerlo "a la antigua" para que sea compatible con ASMX, ahora vamos a ver que simple que es tomar el mismo código y exponer el servicio como una api REST ya que está de moda.

REST una arquitectura

La idea de rest es aprovechar las capacidades del protocolo HTTP para utilizarlo como arquitectura de servicios, es decir: ¿si ya tenemos un protocolo que todos comprenden por qué no aprovecharlo para exponer servicios en lugar de ponerle otros protocolos arribla, como SOAP? gran idea, además ahorramos overhead. Tenemos los verbos necesarios para hacer las operaciones (GET, POST, DELETE y PUT), las direcciones, el formato, todo universal, por lo tanto podemos aprovecharlo.

Framework 3.5

Para poder utilizar REST sobre WCF es necesario instalar el Framework 3.5, el assemblie que contiene la funcionalidad es System.ServiceModel.Web, así que hay que referenciarlo, una vez que hacemos esto el código es éste:

namespace PruebaWCF
{
    class Program
    {
        static void Main(string[] args)
        {
            string uriRest = "http://localhost:8080/rest/";

            using (ServiceHost host = new ServiceHost(typeof(MiServicio)))
            {
                ContractDescription description = ContractDescription.GetContract(typeof(MiServicio));

                //cambiamos el binding por WebHttpBinding
                ServiceEndpoint endPointREST = new ServiceEndpoint(description,
                    new WebHttpBinding(), new EndpointAddress(uriRest));

                //agregamos un comportamiento al endPoint del tipo WebHttpBehavior
                endPointREST.Behaviors.Add(new WebHttpBehavior());

                //agregamos el endPoint al servicio
                host.Description.Endpoints.Add(endPointREST);

                //por último abrimos el host
                host.Open();

                Console.WriteLine("iniciado..");
                Console.ReadLine();
                //cuando se presiona enter ser cierra el servicio
                host.Close();
                Console.WriteLine("finalizado..");
            }
        }
    }

    /// <summary>
    /// Con ServiceContract indicamos que éste es un contrato de servicio
    /// </summary>
    [ServiceContract]
    public class MiServicio
    {
        /// <summary>
        /// Con OperationContract indicamos que esta operación es un operación del servicio
        /// </summary>
        [OperationContract]
        //sólo es necesario este atributo para permitir la invocación del tipo Http Get
        [WebGet]
        public string Saludo(string nombre)
        {
            return string.Concat("Hola ", nombre);
        }
    }

}

Super mágico, hasta la próxima.

Referencias

REST

HTTP

Framework 3.5

miércoles, 17 de septiembre de 2008

Introducción a WCF ABC, con cliente ASMX

WCF

Windows Comunication Foundation es un SDK para desarrollar servicio de Microsoft, es parte del framework 3, necesita framework 2 y el framework 3.5 agrega algunas extensiones como el soporte para servicios REST.

La idea principal detrás de WCF es un modelo unificado de desarrollo de servicios independiente de detalles de implementación como el protocolo de transporte, la seguridad, etc

Entonces podemos tener un único servicio programado de una manera particular y exponerlo de diferentes formas, cada punto de contacto con el exterior de un servicio en WCF se conoce como Endpoint. Por lo tanto decimos que un servicio WCF tiene un numero de Endpoints, cada cual diferenciado por dirección, puerto, protocolo de transporte, etc.

El ABC

Cada vez que veamos una introducción a WCF vamos a ver que se habla del ABC, esto es,

A: Address, la dirección del servicio. Está relacionado obviamente con el protocolo, ya que si es http seguramente el address comenzará con http.

B: Binding, el protocolo de transporte o mejor dicho el esquema de transporte porque es algo más que el protoloco, entre ellos HTTP, TCP, Named pipes, MSMQ, etc.

C: Contract, el contrato, es decir las acciones y tipos que expone el servicio.

Hosting

Seguramente se están preguntando cómo "hostear" los servicio, bien, hay un par de alternativas, IIS, en una aplicación .net cualquiera (un servicio de windows, una aplicación de consola) o con WAS (Windows activation service), en estos ejemplos vamos a utilizar una aplicación de consola .net.

Vamos a los bifes.

Como no me gusta dar vueltas, me parece que lo mejor es ver cómo hacemos para crear un servicio y tener varios Endpoint (por ahora uno y lo vamos a ir modificando en otros posts). Antes que nada necesitamos Visual Studio 2005 + la extensiones para framework 3 o directamente cualquier versión de Visual Studio 2008.

Agregamos el assemblie System.ServiceModel.dll y listo, veámos el código auto-descriptivo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace PruebaWCF
{
    /// <summary>
    /// Con ServiceContract indicamos que éste es un contrato de servicio
    /// </summary>
    [ServiceContract]
    public class MiServicio
    {
        /// <summary>
        /// Con OperationContract indicamos que esta operación es un operación del servicio
        /// </summary>
        [OperationContract]
        public string Saludo(string nombre)
        {
            return string.Concat("Hola ", nombre);
        }
    }
}}

Como vemos es una simple clase con un par de atributos para indicar algunas cosas al WCF ( más adelante vamos a ver que es más interesante extraer la interface con los atributos pero por ahora lo dejamos así para que se más fácil).

Con esto tenemos el servicio terminado, sólo falta "hostearlo", vamos a ver cómo se hace y cómo configuramos los bindings, behaviors, endpoints y etc./p>

Hosteando nuestro primer servicio WCF

Para el primer ejemplo vamos a utilizar un Binding que nos permite utlizar este servicio con un cliente .net 2.0 clásico, el binding que nos permite esto es el BasicHttpBinding.

La configuración

La configuración es un tema medio pesado, ya que hay muchas cosas que configurar (cuanto más flexible mas complejo), si bien se han agregado algunos "factories" para facilitar la creación de servicios cuando se "hostean" en IIS, nosotros por el momento vamos a ver alguinos ejemplos sencillos. La configuración puede hacerce por código o por archivo de configuración, la segunda opción permite más flexibilidad ya que podemos cambiar muchas cosas sin recompilar como agregar un registro de sucesos, autenticación, endonints, etc. si bien no todo se puede hacer por configuración es un poco pesado el tema, por lo tanto vamos a ver los ejemplos haciéndolo por código que de paso quedan más claros los conceptos.

Behaviors

Lo último que voy a contar antes del código es sobre los behaviors o comportamientos, hay de dos niveles, de nivel servicio (afectan a todos los endpoints) y a nivel endpoint, qué permiten hacer? por ejemplo registrar los mensajes, publicar la metadata, algo personalizado, etc.

Ahora sí, el código de nuestro host

namespace PruebaWCF
{
    class Program
    {
        static void Main(string[] args)
        {
            string uri = "http://localhost:8080/servicio/";

            using(ServiceHost host = new ServiceHost(typeof(MiServicio)))
            {
                //definimos un endpoint para el servicio del tipo
                //basicHttpBinding que es compatible con servicios asmx
                //como vemos en la forma más sencilla sólo necesitamos 2 líneas
                //de código para configurar un servicio WCF
                ServiceEndpoint endPoint = new ServiceEndpoint(
                        ContractDescription.GetContract(typeof(MiServicio)),
                        new BasicHttpBinding(), new EndpointAddress(uri));

                host.Description.Endpoints.Add(endPoint);

                //este comportamiento indica que se va a publicar la metadata del servicio
                //para poder generar el proxy desde el lado del cliente más facilmente
                //una vez que tenemos el proxy generado no es necesario esto
                ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
                behavior.HttpGetEnabled = true;
                behavior.HttpGetUrl = new Uri(uri);

                host.Description.Behaviors.Add(behavior);

                //por último abrimos el host
                host.Open();

                Console.WriteLine("iniciado..");
                Console.ReadLine();
                //cuando se presiona enter ser cierra el servicio
                host.Close();
                Console.WriteLine("finalizado..");
            }
        }
    }
}

La clase ServiceHost se encarga de "hostear" nuestro servicio, entonces creamos un Endpoint con:

Contrato: ContractDescription.GetContract(typeof(MiServicio))

Binding: BasicHttpBinding para que sea compatible con un cliente asmx

Address: new EndpointAddress(url)

Los agregadomos al host con host.Description.Endpoints.Add(endPoint)

y listo, ya funciona haciendo host.Open().

Le agregué el ServiceMetadataBehavior para que se publique la metadata del servicio apra poder generar el proxy desde Visual Studio 2005 con un cliente asmx, después se puede quitar este behavior y diría que es casi recomendable, un detalle es que puedo publicar la metadata en otra url, yo lo hice en la misma por comodidad.

Consumiendo un servicio WCF desde ASMX

Y llegamos a la parte interesante, si tuvieramos un servicio publicado cuyos clientes con asmx y se nos ocurre actulizarlo a WCF por motivos valederos (como por ejemplo porque es más lindo) nada está perdido, el servicio que acabamos de generar es compatible con asmx y lo vamos a demostrar.

Agregamos la referencia como simpre

Un poco de código del lado del cliente para invocar al servicio WCF

class Program
{
    static void Main(string[] args)
    {
        WCFService.MiServicio servicio = new WCFService.MiServicio();
        Console.WriteLine(servicio.Saludo("leonardo"));

        Console.ReadLine();
    }
}

Y listo, mágico.Hasta la próxima

Referencias:

Visión global de la arquitectura de WCF MSDN

ABC de la programación de WCF