Herramientas de usuario

Herramientas del sitio


sp:wcf

¡Esta es una revisión vieja del documento!


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 claseEndPointAddress, esta está formada por un 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<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;
        }
    }
}

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<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;
    }
}

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 <configuration></<configuration>) 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:

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:

<?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>

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.

<?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.serviceModelservicesservice 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.

<userNameAuthentication 
   userNamePasswordValidationMode="Custom"
   customUserNamePasswordValidatorType="CustomValidator.UserValidator, SAMERWS"/>

Configuración

:?:

  <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>

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 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.

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”):

<system.serviceModel>
    <bindings>
        <basicHttpBinding>
            <binding name="BasicHttpBinding_PIUServiceVi_Document" >
                <security mode="Transport">
                    <transport clientCredentialType="Basic" proxyCredentialType="None" realm="" />
                    <message clientCredentialType="UserName" algorithmSuite="Default" />
                </security>
            </binding>
        </basicHttpBinding>
    </bindings>
    <client>
        <endpoint address="https://www.atm.cat:8080/piu/previsiones.svc"
            binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_PIUServiceVi_Document"
            contract="ServiceReference1.PIUServiceVi_Document" name="BasicHttpBinding_PIUServiceVi_Document" />
    </client>
</system.serviceModel>

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…

<message clientCredentialType="UserName" algorithmSuite="Default" />

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 compativilidad con ASP.NET

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]
...

IIS

  • Si el web service ha de dar servicio en una intranet y en internet, será necesario 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:

<system.serviceModel>
  <serviceHostingEnvironment>
    <baseAddressPrefixFilters>
        <add prefix="http://www.example.com"/>
    </baseAddressPrefixFilters>
  </serviceHostingEnvironment>
  ...
</system.serviceModel>
sp/wcf.1236268810.txt.gz · Última modificación: 2020/05/09 09:24 (editor externo)