Servicios WCF

 

WPF = Windows Communication Foundation. Funciona tanto con plataformas .NET como con J2EE.

Un servicio es una aplicación que intercambia mensajes con otras aplicaciones, es una unidad ideal para crear soluciones distribuidas. Es decir soluciones orientadas a servicios donde los dos extremos de la comunicación pueden pertenecer a plataformas diferentes; es suficiente con que ambos extremos utilicen la misma política de comunicación.

Los servicios son autónomos y comparten esquemas (datos) y contratos (funcionalidad), no tienen que asumir nada de lo que hay al otro lado del extremo. Los clientes consumen servicios y los servicios ofrecen soluciones. Un servicio puede a su vez ser cliente de otro servicio.

En WPF los mensajes tienen una cabecera y un cuerpo y son definidos en XMLsegún el protocolo SOAP.

Veamos un mensaje SOAP:

<?xml version ="1.0">
<soap:Envelope xmlns:soap="http://www.w3.org/..." 
 soap:encodingStyle="http://www.w3.org/...">
  <soap:Header>
    ... aquí iría información específica de la  aplicación como puede ser la autenticación ...
  </soap:Header>
  <soap:Body>
    ... mensaje al punto final de la comunicación ...
    <soap:Fault>
       ... para indicar mensajes de error ...
    </soap:Fault>
  </soap:Body>

</soap>

Modelo de programación de WCF.

El cliente WCF es el que inicia la comunicación y el servicio WCF es el que está esperando que un cliente se comunique con él. Una única aplicación puede actuar como cliente y como servicio.

Este modelo de programación orientado a servicios está definido por las clases agrupadas bajo el namespace System.ServiceModel.

Implementar un servicio WCF


Un servicio es una aplicación que expone uno o más extremos, donde cada uno de ellos expone una o más operaciones de servicio.

El extremo o punto final proporciona la única manera de comunicación con el servicio. Está compuesto por:

  • Dirección: define la ubicación del servicio (por ejemplo una URL, una dirección FTP o una ruta de acceso local o de red).
  • Enlace: define la manera de establecer la comunicación con el servicio (BasicHttpBonding, PollingDuplexHttpBinding o WsHttpBinding). Los enlaces WCF permiten especificar con facilidad un protocolo (HTTP o FTP), un mecanismo de seguridad (autenticación Windows o nombres de usuario y contraseña.
  • Contrato: incluye las operaciones expuestas por la clase del servicio WCF.

Un servicio WCF podría ser la consulta de notas, donde tendríamos un extremo para profesores y otro para alumnos. Cada uno con una dirección, enlace y contrato diferentes.

Un servicio WCF puede construirse como una aplicación de servicios WCF hospedada en IIS o bien como biblioteca de servicios  WCF e iniciarlo como un servicio en nuestra máquina (manual o automática).

Definir un contrato

El contrato se corresponde con una interfaz, marcada con el atributo ServiceContract, que especifica las operaciones que ofrece el servicio. Cada una de estas operaciones será proporcionada por un método de la clase del servicio marcado con el atributo OperationContract. Los métodos que no estén marcados con este atributo no serán presentados a los clientes.

[ServiceContract]
public interface IConvertirGrados {
  // Operaciones ofrecidas por el servicio
  [OperationContract]
  double ConvCentAFahr(double gCent);

  [OperationContract]
  double ConvFahrACent(double gFahr);
}
  • ServiceContract se corresponde con la clase ServiceContractAttribute.
  • OperationContract se corresponde con la clase OperationContractAttribute. De esta forma indicamos que un método como ConvCentAFahr o ConvFahrAcent definen una operación que forma parte de un contrato.

Ejemplo: conversor de temperatura.

Queremos crear un servicio WCF que muestre una interfaz para que una aplicación Silverlight cliente pueda solicitar convertir un valor en grados Fahrenheit a Centígrados y viceversa.



A continuación, vamos a definir el contrato del servicio (ServiceContract), esto es, las operaciones que ofrece el servicio.

using System.ServiceModel;
[ServiceContract(Namespace="http://wpf.com.es/ServiciosWeb/")]
public interface ISerWebWCFConverTemps {
  // Operaciones del servicio
  [OperationContract]
  double ConvCentAFahr(double gCent);

  [OperationContract]
  double ConvFahrACent(double gFahr);
}

También puede haber un DataContract para agregar tipos de datos.

[DataContract(Namespace="http://csharp.com.es/ServiciosWeb/")]
public class Detalles {
  private bool _sonCentigrados;
  private string _literal;
  private double _grados;

  [DataMember]
  public bool SonCentigrados {
    get {return _sonCentigrados;}
    set {_sonCentigrados = value;}
  }

  [DataMember]
  public string Literal {
    get {return _literal;}
    set {_literal = value;}
  }

  [DataMember]
  public double Grados {
    get {return _grados;}
    set {_grados = value;}
  }
}

Ahora, apoyándonos en esta clase, añadimos un nuevo método, a la interfaz IServWebWCFConverTemps:

[OperationContract]
Detalles ResultadoDetallado (Detalles detalle);

Ahora hay que definir la clase del servicio

using System.ServiceModel;
public class SerWebWCFConverTemps: IServWebWCFConverTemps {
  public double ConvCentAFahr(double gCent) {
    double grados = Math.Round(9.0/5.0*gCent + 32.0,2);
    return grados;
  }
  public  double ConvFahrACent (double gFahr) {
    double grados = Math.Round (((gFahr-32.0)*5.0)/9.0,2);
    return grados;
  }
  public Detalles ResultadoDetallado(Detalles detalle) {
    if (detalle==null) {
      throw new ArgumentNullException("detalle");
    }
    if (detalle.SonCentigrados) 
      detalle.Literal = ConvCentAFahr(detalle.Grados) + " grados fahrenheit.";
    else
      detalle.Literal = ConvFahrACent(detalle.Grados) + " hgrados centígrados.";
    return detalle;
  }
}

Como vimos anteriormente una clase servicio WCF puede implementar varios contratos de servicio.

Ahora toca consumir el servicio.

Es decir implementar un cliente WCF. Un cliente está compuesto de un proxy que habilita a una aplicación para poder establecer comunicación con un servicio WCF y un extremo. para los servicios que exponen varios extremos , el cliente selecciona el que más se ajusta a sus necesidades; por ejemplo uno que permita establecer una comunicación a través de HTTP y sin autenticación.

Un proxy se genera a partir de los metadatos obtenidos del servicio WCF e incluye información sobre los tipos y métodos expuestos por el servicio.

Siguiendo con el ejemplo del servicio de la temperatura vamos a crear una interfaz parecida a esta:
Convertir: en función del tipo de conversión seleccionado, invocará al método ConvCentAFahr o ConvFahraCent pasando como argumento el valor de la caja de texto.

Detalles: en función del tipo de conversión seleccionado, invocará al método ResultadoDetallado pasando como argumento el objeto Detalles que devolverá el resultado detallado en función en función de los valores fijados para sus propiedades SonCentigrados y Grados.

Para llevar a cabo estos dos procesos el cliente debe tener un medio de comunicarse con el servicio WCF. Esto lo permite una infraestructura (proxy) que se genera cuando agregamos al proyecto Silverlight una referencia al servicio. El proxy no es más que una clase que proporciona al cliente una representación local del servicio para interactuar con él.



<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_ISerWebWCFConverTemps1" maxBufferSize="2147483647"
 maxReceivedMessageSize="2147483647">
           <security mode="None" />
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="../Temperatura.svc" binding="basicHttpBinding"
 bindingConfiguration="BasicHttpBinding_ISerWebWCFConverTemps1"
 contract="RefServWCFConverTemps.ISerWebWCFConverTemps" name="BasicHttpBinding_ISerWebWCFConverTemps1" />
    </client>
  </system.serviceModel>
</configuration>

La sección <bindings> describe los enlaces; un cliente Silverlight se comunica con un servicio utilizando el enlace BasicHttpBinding o el enlace PollingDuplexHttpBinding.

La sección <client> contiene una lista de extremos (endpoint) que usa el cliente para conectarse a un servicio.

Obtener acceso al servicio

El proxy es el que gestiona las comunicaciones entre la aplicación cliente y el servicio WCF.

using SilverlightApplication1.RefServWCFConverTemps;
...
public partial class mainPage : UserControl {
  private ServWebWCFConverTempsClient cliente = null;
  public MainPage() {
    InitializeComponent();
    etError.Content = "";
    cliente = new ServWebWCFConverTempsClient();
  }
  ...
}

Todas las llamadas son asíncronas, es decir ConvCentAFahr implicará llamar al método ConvCentAFahrAsync devolviendo el control a la aplicación, cuando el servicio devuelva los datos que solicitamos, lo notifica generando el evento ConvCentAFahrCompleted, entonces hay que interceptar este evento para poder recoger el resultado. Procederemos de igual forma para ConvFahrACent y con ResultadoDetallado.

using SilverlightApplication1.RefServWCFConverTemps;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SilverlightApplication1
{
    public partial class MainPage : UserControl
    {
        private SerWebWCFConverTempsClient cliente = null;
        private double nGrados;
        public MainPage()
        {

            InitializeComponent();
            this.etError.Content = "";
            cliente = new SerWebWCFConverTempsClient();
            cliente.ConvCentAFahrCompleted +=
                new EventHandler(cliente_ConvCentAFahrCompleted);
            cliente.ConvFahrACentCompleted +=
                new EventHandler(cliente_ConvFahrACentCompleted);
            cliente.ResultadoDetalladoCompleted +=
                new EventHandler(cliente_ResultadoDetalladoCompleted);

        }

        private void cliente_ResultadoDetalladoCompleted(object sender, ResultadoDetalladoCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                etError.Content = "Error al obtener detalles del resultado";
            }
            else
            {
                ctGrados.Text = "";
            }
        }

        private void cliente_ConvFahrACentCompleted(object sender, ConvFahrACentCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                etError.Content = "Error en la conversión de grados F a C";
            }
            else
            {
                ctGrados.Text = Convert.ToString(e.Result);
            }
        }

        private void cliente_ConvCentAFahrCompleted(object sender, ConvCentAFahrCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                etError.Content = "Error en la conversión de grados C a F";
            }
            else
            {
                ctGrados.Text = Convert.ToString(e.Result);
            }
        }

        private void btConvertir_Click(object sender, RoutedEventArgs e)
        {

            etError.Content = "";
            //Obtenemos el valor escrito en la caja de texto
            try
            {
                nGrados = Convert.ToDouble(ctGrados.Text);
                //Realizar la conversión invocando al método correspondiente
                if (boConvCF.IsChecked == true)
                {
                    cliente.ConvCentAFahrAsync(nGrados);
                }
                if (boConvFC.IsChecked == true)
                {
                    cliente.ConvFahrACentAsync(nGrados);
                }
            }
            catch (Exception exc)
            {
                etError.Content = exc.Message;
            }

        }

        private void btDetalles_Click(object sender, RoutedEventArgs e)
        {

            etError.Content = "";
            try
            {
                nGrados = Convert.ToDouble(ctGrados.Text);
                Detalles detalle = new Detalles();
                detalle.Grados = nGrados;
                detalle.SonCentigrados = (bool)boConvCF.IsChecked;
                cliente.ResultadoDetalladoAsync(detalle);
            }
            catch (Exception exc)
            {
                etError.Content = exc.Message;
            }
        }
    }
}

Y donde el interface XAML sería algo así como:

<UserControl
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="SilverlightApplication1.MainPage"
 mc:Ignorable="d"
 d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
  <StackPanel HorizontalAlignment="Left" Height="63" VerticalAlignment="Top" Width="400">
    <sdk:Label Height="28" Width="400" FontSize="14">Conversión entre grados Centígrados y Fahrenheit</sdk:Label>
  </StackPanel>
  <StackPanel HorizontalAlignment="Left" Height="232" Margin="0,68,0,0" VerticalAlignment="Top" Width="400">
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="80"></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
      </Grid.ColumnDefinitions>
      <TextBlock Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top">Grados</TextBlock>
      <StackPanel Grid.Column="1">
        <TextBox x:Name="ctGrados" Background="Azure"></TextBox>
        <Button x:Name="btConvertir" Content="Convertir" Click="btConvertir_Click"/>
        <Button x:Name="btDetalles" Content="Detalles" Click="btDetalles_Click"/>
        <RadioButton x:Name="boConvCF">Centígrados a Fahrenheit</RadioButton>
        <RadioButton x:Name="boConvFC">Fahrenheit a Centígrados</RadioButton>
        <sdk:Label x:Name="etError">Error:</sdk:Label>
      </StackPanel> 
    </Grid>
  </StackPanel>
</Grid>
</UserControl>

Taller WCF de Carlos Milán.

Servicios Web utilizando Sockets

Deja un comentario

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

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.