====== WCF ======
===== Conceptos =====
Un **Servicio WCF** es un programa que publica una colección de //endpoints//, estos son accedidos por las aplicaciones cliente.
==== Endpoints ====
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 clase''EndPointAddress'', esta está formada por un [[code:tools#uri|URI]], un identificador y una colección de cabeceras (proporcionan información adicional).
==== Contratos ====
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).
==== Binding ====
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]].
==== Behaviors ====
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''.
===== A quick look =====
==== Creación del servicio ====
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.
=== Definición del contrato ===
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 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;
}
}
}
=== Creando la lógica del servicio ===
Ú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 getListaCumples() {
List cumples = new List();
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;
}
}
==== Sirviendo y configurando el servicio ====
=== Configuración a partir de ficheros ===
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 '''') la tag ''services'' y la ''behaviors'', la primera (services) contiene tags ''service'' correspondientes a cada uno de los servicios.
* La tag **services** tendrá dos propiedades: ''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.
* Dentro de la tag service tendremos tags ''endpoints'' cada una con los atributos ''address'', ''binding'' y ''contract''.
* La tag **behaviors** contendrá a la **serviceBehaviors**, donde se definen todos los behaviors del servicio, cada uno a partir de tag **behavior** que contienen una propiedad ''name'' correspondiente a su identificador. Internamente existirían los elementos que definen la configuración del servicio, entre otros los siguientes:
* //externalMetadataLocation//, URI que contiene la dirección del fichero WSDL, el cual es devuelto al usuario en vez del autogenerado.
* //httpGetEnabled//, un valor booleano que especifica si los metadatos son publicados a partir de una petición HTTP/GET, por defecto está a ''false''.
* //httpGetUrl// indica la dirección del WSDL, si está activo por //httpGetEnabled//, si no es definido el atributo estará en la dirección del servicio agregándole ''?wsdl''.
Aquí un esquema de cómo se componen estos archivos de configuración:
{{sp:wcf:scheme_config.gif?200 |}}
=== Por IIS ===
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:
=== Aplicación Host ===
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''.
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''.
===== Seguridad =====
==== Modos ====
=== Niveles ===
* **Transporte**, se utilizan capas inferiores de la conexión para proteger los mensajes, por ejemplo utilizar una conexión cifrada por SSL.
* **Mensaje**, basada en los estándares //WS-Security//, es WCF quien protege cada uno de los mensajes que se envian por la red.
=== Credenciales ===
* **Basic**, un nivel de seguridad muy bajo de enviar usuario\password ya que se envian en modo texto.
* **Certificate**, es necesario que el cliente disponga de un certificado para validarse en el servidor.
* **Digest**, como el Basic pero lo que envía el cliente es un hash de credenciales.
* **Windows** al usar este valor el servidor ha de encontrarse en un dominio, las credenciales corresponderán a las de dicho dominio.
* **IssuedToken** autenticación basada en ADFS con tokens SAML.
=== En la configuración ===
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
==== Autentificación personalizada ====
=== Clase validadora de usuario ===
:?: \\ \\
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''.
=== Configuración ===
:?:
==== Autentificación en IIS ====
=== Conexión segura en IIS ===
Las conexiones seguras en IIS se basan en SSL y es por ello que tenemos que tenerlo [[code:tools#activar_ssl|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''.
A partir de ahora el servicio será público por ''https'' en el IIS.
===== Clientes =====
==== Creación de un cliente sencillo ====
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.
==== Creación de un cliente para un escenario seguro ====
=== Configuración para un canal cifrado ===
Para desarrollar un cliente que acceda a un servicio por un canal cifrado en SSL es necesario que en el archivo de configuración del cliente exista, en el apartado ''binding'' utilizado un apartado de seguridad, en este caso en modo ''Transport'' con un ''clientCredentialType'' como ''Basic''. \\ \\
En el siguiente ejemplo la aplicación accede por https a un servicio con validación de usuario (de ahí el ''message clientCredentialType="UserName"''):
=== Validación del cliente ===
En el apartado anterior (configuración para un canal cifrado) vemos la porción del archivo de configuración que se requiere para que el cliente acceda a un servicio que haga una petición de validación de usuario. Se agrega en el apartado de seguridad lo siguiente...
Pero es necesario indicar qué usuario\password se utilizará, para ello debemos modificar, antes de hacer la conexión con el servicio, la propiedad ''ClientCredentials'' del objeto cliente:
ServiceReference1.PIUServiceVi_DocumentClient client = new ServiceReference1.PIUServiceVi_DocumentClient();
client.ClientCredentials.UserName.UserName = "piusbcn";
client.ClientCredentials.UserName.Password = "atmpiubcn";
=== Saltarse el certificado del servicio ===
Cuando desarrollamos con certificados no firmados por una entidad de confianza el motor del cliente no permitirá la conexión por ello debemos escribir el evento de comprovación de certificado:
using System.Net;
using System.Security.Cryptography.X509Certificates;
...
ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback(customXertificateValidation);
...
private static bool customXertificateValidation(object sender, X509Certificate cert, X509Chain chain, System.Net.Security.SslPolicyErrors error) {
return true;
}
===== Otros =====
==== Activar la compatibilidad con ASP.NET ====
A veces necesitamos guardar información del usuario en, por ejemplo, sesiones, pero por defecto no lo podemos hacer simplemente accediendo a ''System.Web.HttpContext.Current.Session''. Para poder acceder al contexto web desde un servicio de WCF deberemos hacer dos cosas:
* Activar la compatibilidad desde el archivo de configuración:
...
* Agregar el atributo ''AspNetCompatibilityRequeriments'' a la clase del servicio (como ''Allowed''):
[System.ServiceModel.Activation.AspNetCompatibilityRequirements(RequirementsMode = System.ServiceModel.Activation.AspNetCompatibilityRequirementsMode.Allowed)]
public class PiuService : ISAMERWService {
...
===== Notas =====
* Si quisiesemos especificar exáctamente los nombres de los elementos del contrato, es decir, que se cambien en tiempo de ejecución, podemos hacerlo pasando parámetros a los atributos:
[ServiceContract (Namespace="ATM", Name="PIUService")]
public interface ISAMERWService {
[OperationContract]
...
* Para cambiar el usuario con el que se ejecuta el servicio en IIS deberemos [[sp:wcf#activar_la_compatibilidad_con_asp.net|activar la compatibilidad con asp.net]].
* Con la instalación de Visual Studio se nos instala una herramienta denominada //Microsoft Service Configuration Editor//, muy útil para editar de una forma clara los archivos de configuración de un proyecto que involucren servicios. Podemos acceder desde //Inicio -> Programas -> SDK -> Tools//.
==== IIS ====
* Si el web service ha de dar servicio en una intranet y en internet, será necesario [[code:tools#adsutil|configurar el ServerBinding]] del site, ya que si no las URI que se generen serán erroneas, apuntando todas a la máquina local.
=== Comprobar que IIS puede acceder correctamente al servicio ===
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");
%>
=== Indicar la dirección por defecto ===
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:
...