¡Esta es una revisión vieja del documento!
Un Servicio WCF es un programa que publica una colección de endpoints, estos son accedidos por las aplicaciones cliente.
Un endpoint (o extremo) del servicio indica una ruta para acceder al servicio web mediante un protocolo y formato de datos específico… Indica a los clientes una combinación de lugar\mecanismo para conectarse con el servicio web. Se forma a partir de una dirección (Address), un enlace (Binding) y un contrato (Contract).
La clase que respresenta al endpoint es la ServiceEndPoint, la cual engloba un objeto EndPointAddress, Binding y un ContractDescription.
La dirección donde se encuentra un endpoint está representada por la claseEndPointAddress, esta está formada por un URI, un identificador y una colección de cabeceras (proporcionan información adicional).
El contrato corresponde a la funcionalidad (métodos) y datos (clases, estructuras…) que utilizará el servicio. En código no es más que una interface que han de implementar las clases que representen la lógica del servicio.
Representado por la clase ContractDescription, las operaciones son OperationDescription y estas, a la vez, describen los mensajes mediante MessageDescriptions. Pueden existir los contratos Duplex donde tanto el servicio como el cliente pueden invocar acciones entre ellos. Los contratos tienen un nombre y un namespace que los identifica en los metadatos, además de una colección de behaviors (módulos que modifican los comportamientos del contrato).
Un binding especifica un mecanimo para comunicar el endpoint con el resto del mundo (por ejemplo protocolo (tcp, http…), codificación (binaria, texto…), seguridad (ssl, por mensajes SOAP…).
El binding (está representado por la clase Binding) tiene un nombre y un namespace que lo identifica dentro de los metadatos del servicio, además de contener una cola elementos que indican cómo se comunica el endpoint. El orden de esta cola es importante, ya que los mensajes pasarán por el primer elemento hasta el último. Entre otros los elementos pueden ser:
TcpTransportBindingElement indica que el endpoint se comunica mediante TCP.ReliableSessionBindingElement indica que el endpoint hace uso de la mensajería confiable para asegurar la entrega de mensajes.SecurityBindingElement indica que el endpoiint hace uso de seguridad basada en SOAP.Una lista de los bindings ofrecidos por .NET la encontrarás en http://msdn.microsoft.com/en-us/library/ms731092.aspx.
Controlan la funcionalidad del servicio, por ejemplo el ServiceMetadataBehavior que controla la publicación de metadatos. Existen comportamientos de seguridad, autorización, construcción del canal… Los comportamientos implementan la IServiceBehavior.
Para realizar este artículo se han seguido los siguientes artículos:
Un WebService podremos crearlo en Visual Studio .NET 2008 a partir Biblioteca de Servicios WCF o a partir de una Biblioteca de clases normal y corriente.
Para poder acceder a los atributos y clases que se utilizan deberemos agregar una referencia a System.ServiceModel y un using System.ServiceModel;. También a System.Runtime.Serialization.
VS 2008 también dispone de un cliente de pruebas de servicios web que se ejecuta lanzando el WcfTestClient.exe. Al debugar un servicio web wcf se lanza automáticamente.
El primer paso para la creación de un servicio es la definición del contrato, este no es más que una interface con el atributo ServiceContract, sus métodos utilizarán el OperationContract.
Cuando una clase se utilice en el Web Service deberá ser serializable (ya que debe de poder ser enviada mediante SOAP\XML), y los atributos que se requerirán serán el DataContract para la definición de la clase y DataMember para sus propiedades. Es importante que las propiedades marcadas como DataMember contengan set y get.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.Serialization; using System.ServiceModel; namespace BlogWCF { [ServiceContract] public interface IMiServicio { [OperationContract] List<Cumple> getListaCumples(); [OperationContract] int DimeEdad(Cumple cu); } [DataContract] public class Cumple { private string nombre = ""; [DataMember] public string Nombre { get { return nombre; } set { nombre = value; } } private DateTime fecha = new DateTime(); [DataMember] public DateTime Fecha { get { return fecha; } set { fecha = value; } } public Cumple(string nombre, DateTime fecha) { this.nombre = nombre; this.fecha = fecha; } } }
Únicamente debemos crear una clase que implemente la interface para agregarle la lógica al servicio, todos los métodos no definidos en el contrato no serán visible en el propio servicio.
public class MiServicio : IMiServicio { public List<Cumple> getListaCumples() { List<Cumple> cumples = new List<Cumple>(); cumples.Add(new Cumple("Juan", new DateTime(1982, 10, 8))); cumples.Add(new Cumple("Juan", new DateTime(1981, 9, 6))); cumples.Add(new Cumple("Juan", new DateTime(1981, 4, 12))); return cumples; } public int DimeEdad(Cumple cu) { int years = DateTime.Now.Year - cu.Fecha.Year - (cu.Fecha.Month > DateTime.Now.Month ? 1 : (cu.Fecha.Month != DateTime.Now.Month) ? 0 : (cu.Fecha.Day > DateTime.Now.Day) ? 1 : 0); return years; } }
Según la forma (a partir de IIS, de una aplicación host…) de servir un servicio escogeremos una forma u otra de configurarlo, aunque básicamente se hace a partir de los ficheros de configuración de .NET.
Debemos definir en la tag system.serviceModel (dentro de <configuration></<configuration>) la tag services y la behaviors, la primera (services) contiene tags service correspondientes a cada uno de los servicios.
name y behaviorConfiguration, la primera corresponde a la identificación (namespace.clase) del servicio y la segunda al nombre de la configuración de behavior que postiriormente crearemos. endpoints cada una con los atributos address, binding y contract.name correspondiente a su identificador. Internamente existirían los elementos que definen la configuración del servicio, entre otros los siguientes:false. ?wsdl.
Aquí un esquema de cómo se componen estos archivos de configuración:
Es importante entender que la configuración de un Web Service se realiza como si de una aplicación ASP.NET se tratase. Las librerías han de ir en un directorio bin dentro del directorio principal de la aplicación, y la lógica del servicio web, al ser también una librería deberá colocarse en este directorio.
Para servir el servicio por IIS necesitaremos dos archivos de configuración, el Web.config y uno con extensión .svc, el que define que es un servicio lo que está sirviendo y dónde se encuentra. Luego, para acceder al sercio escribiremos la dirección de este fichero. Su contenido será:
<%@ServiceHost Service="namespace.className" %>
Para acceder al WSDL pondremos la dirección de este fichero más ?wsdl.
Ahora únicamente falta editar el Web.config:
<?xml version="1.0"?> <configuration> <system.serviceModel> <services> <service name="namespace.className" behaviorConfiguration="MiBehavior"> <endpoint address="" binding="basicHttpBinding" contract="namespace.interfaceName" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="MiBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
Podemos servir el servicio desde cualquier tipo de aplicación (una de consola, un servicio del sistema, una aplicación con GUI…), para ello únicamente necesitaremos agregar una referencia al proyecto (recordemos que el proyecto es físicamente una dll) y otra a System.ServiceModel y sus using pertinentes.
Para crear una aplicación host necesitaremos un objeto System.ServiceModel.ServiceHost, a este le pasamos por parámetro el tipo del servicio (con typeof) y usaremos sus métodos Open y Close para activarlo y desactivarlo respectivamente:
System.ServiceModel.ServiceHost host = new System.ServiceModel.ServiceHost(typeof(BlogWCF.MiServicio)); host.Open(); ... host.Close();
Para configurar la forma en la que el servicio es servido podemos hacerlo manualmente, agregando contratos al ServiceHost o a partir del archivo App.config.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="BlogWCF.MiServicio" behaviorConfiguration="myBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:8000/BlogWCF"/> <add baseAddress="net.tcp://localhost:8080/BlogWCF"/> </baseAddresses> </host> <endpoint address="Servicio" binding="basicHttpBinding" contract="BlogWCF.IMiServicio"/> <endpoint address="Servicio" binding="netTcpBinding" contract="BlogWCF.IMiServicio"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="myBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
A la tag system.serviceModel → services → service se ha agregado otra, la host, donde se indican las direcciones para acceder al servicio. También, a diferencia de con IIS, se ha puesto un valor en el atributo address de la tag endpoint donde se indica la ruta para acceder al servicio, es decir, para acceder a este servicio haríamos: http://localhost:8080/BlogWCF/Servicio.
En la configuración indicaremos el modo de seguridad en el binding adecuado. En el ejemplo siguiente se define una configuración para un binding wsHttpBinding llamada myWsBinding y que es indicada como la que se utilizará en el endpoint del servicio. Define un
Una clase validadora es la que se encarga de la validación de usuario\contraseña dentro del WebService. Esta ha de heredar de UserNamePasswordValidator (dentro del ensamblado System.IdentityModel.dll). Cuando la validación falle tendrá que ser lanzada una SecurityTokenException.
namespace CustomValidator { public class UserValidator: UserNamePasswordValidator { public override void Validate(string userName, string password) { if ((userName != "test") || (password != "test")) { throw new SecurityTokenException("Validation Failed!"); } } } }
Luego, en la configuración deberemos indicar que la autentificación se hace de forma custom e indicar también la clase validadora: namespace.clase, ensamblado.
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="CustomValidator.UserValidator, SAMERWS"/>
<system.serviceModel> <services> <service name="SAMERWS.SAMERWService" behaviorConfiguration="CustomValidator"> <host> <baseAddresses> <add baseAddress = "http://localhost:8005/AuthenticationSimpleService" /> </baseAddresses> </host> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpWithMessageSecurity" contract="SAMERWS.ISAMERWService"> </endpoint> </service> </services> <bindings> <wsHttpBinding> <binding name="wsHttpWithMessageSecurity"> <security mode="Message" > <message clientCredentialType="UserName"/> </security> </binding> </wsHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="CustomValidator"> <serviceMetadata httpGetEnabled="true" /> <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="SAMERWS.UserValidator, SAMERWS"/> <serviceCertificate findValue="alfredsamd" x509FindType="FindBySubjectName" storeLocation="CurrentUser" storeName="My" /> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
Las conexiones seguras en IIS se basan en SSL y es por ello que tenemos que tenerlo activo en nuestro servidor. Una vez esto sea así, configuraremos la seguridad a partir de la capa de transporte, sin ningún tipo de validación de usuario… Todavía
Es de remarcar el hecho que en la configuración tengamos que tener httpsGetEnabled (en vez de httpGetEnabled) a true.
<system.serviceModel> <services> <service behaviorConfiguration="CustomValidator" name="SAMERWS.SAMERWService"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wthoutAuth" contract="SAMERWS.ISAMERWService" /> </service> </services> <bindings> <wsHttpBinding> <binding name="wthoutAuth"> <security mode="Transport"> <transport clientCredentialType="None" realm="" /> </security> </binding> </wsHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="CustomValidator"> <serviceMetadata httpsGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
A partir de ahora el servicio será público por https en el IIS.
Desde VS .NET 2008 es una tarea muy sencilla, podemos hacerlo desde la línea de comandos del SDK gracias al comando svcutil o agregando una referencia a un servicio (haciendo botón derecho sobre el proyecto de la aplicación). Estas dos operaciones crearían una clase que se utilizaría para acceder al web service como si de una clase normal se tratase, sólo que esta está enlazada con el exterior.
[ServiceContract (Namespace="ATM", Name="PIUService")] public interface ISAMERWService { [OperationContract] ...
Agrega un archivo .aspx al directorio virtual y, mediante código, intenta cargar una clase del ensamblado del servicio.
Por ejemplo si el servicio es BlogWCF.MiServicio y el ensamblado es BlogWCF.dll pondremos el siguiente código:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Runtime.Remoting" %>
<%
ObjectHandle oh = Activator.CreateInstance("BlogWCF", "BlogWCF.MiServicio");
Response.Write(oh.Unwrap().ToString() + " loaded OK");
%>
Cuando el IIS soporta múltiples bindings (varias direcciones que acceden al servicio) debemos escoger una base address que será la única que lo pueda hacer, para ello, en la configuración del servicio haremos:
<system.serviceModel> <serviceHostingEnvironment> <baseAddressPrefixFilters> <add prefix="http://www.example.com"/> </baseAddressPrefixFilters> </serviceHostingEnvironment> ... </system.serviceModel>