Contract-First web services con Visual Studio 2008

0
20118

Contract-First web services con Visual Studio 2008

Índice

  1. Introducción
  2. Descriptor del contrato
    1. Definición de los tipos del documento con XML Schema
    2. Definición del contrato con WSDL
  3. Implementación con Visual Studio 2008. WSCF.blue
    1. Instalación de WSCF.blue
    2. Crear el servicio e importar el contrato
    3. Implementar el web service
    4. Publicar y acceder al servicio
  4. Probar el web service con soapUI
  5. Conclusión

Introducción

En el diseño y desarrollo de servicios web podemos utilizar dos aproximaciones:

  • Dirigido por contrato, o Contract-First. Primero se describen las funcionalidades del servicio web en cuanto a su interfaz, semántica y aspectos administrativos de la invocación, siendo la especificación WSDL el estándar. El WSDL, como documento XML, puede ser parseado por una herramienta para facilitar la implementación del web service proveedor o consumidor.
  • Contract-Last, en el que primero se implementa el servicio web en un determinado lenguaje y posteriormente se genera un WSDL que lo describa.

Cada cual tiene sus ventajas e inconvenientes [1] [2] [3]. En este tutorial veremos la primera de las dos opciones, implementando en C# un servicio web proveedor. Utilizaremos las siguientes herramientas:

  • Windows Vista Home Edition SP2, C2D@1.5, 3GB Ram
  • Microsoft Visual Studio 2008 Professional SP1
  • Microsoft .NET Framework 3.5 SP1
  • thinktercure WSCF.blue V1 Final (1.0.5) (*)
  • soapUI 3.0.1

(*) En el momento de escribir este tutorial no existe una versión para Visual Studio 2008 Express Edition

El código fuente de este tutorial puede descargarse aquí: wsEncriptacion_adictosaltrabajo.zip

Descriptor del contrato

Vamos a trabajar con un web service de encriptación mediante Triple-DES y MD5 que ofrece dos operaciones :

  • encriptar. Recibe tres tipos simples: la cadena de texto en claro, la clave de encriptación y un flag para el uso o no de hashing. Devuelve la cadena de texto encriptada.
  • desencriptar. Recibe un tipo complejo compuesto por tres tipos simples: la cadena de texto encriptada, la clave utilizada en la encriptación y el flag para indicar el uso de hashing. Devuelve la cadena de texto desencriptada.

El uso de tipos simples o complejos es una cuestión de diseño, y en nuestro caso lo hacemos así para ampliar la cobertura de capacidades de WSCF.blue.

Definición de los tipos del documento con XML Schema

La definición del tipo de datos complejo lo implementamos mediante un XML Schema. Se muestra a continuación, y puede descargarse aquí: cryptoSchema.xsd

<?xml version="1.0" encoding="UTF-8"?>
<!--
  cryptoSchema.xsd
  Tutorial: Contract-First web services con Visual Studio 2008
  www.adictosaltrabajo.com - Ivan Garcia Puebla
-->
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://tutorial.adictosaltrabajo.com/schema/cryptoSchema"
    xmlns:tns="http://tutorial.adictosaltrabajo.com/schema/cryptoSchema"
    elementFormDefault="qualified">
    <xsd:complexType name="datosDesencriptar">
        <xsd:sequence>
            <xsd:element name="encriptado" type="xsd:string"></xsd:element>
            <xsd:element name="clave" type="xsd:string"></xsd:element>
            <xsd:element name="usarHashing" type="xsd:boolean"></xsd:element>
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

Definición del contrato con WSDL

Implementamos el contrato del web service en un descriptor WSDL concreto. Utilizaremos el Schema anterior, SOAP sobre HTTP, y el resto de detalles se muestran a continuación:

cryptoService.WSDL
descriptor cryptoService.wsdl

El fuente del fichero puede descargarse aquí: cryptoService.wsdl.

<?xml version="1.0" encoding="UTF-8"?>
<!--
  cryptoServiceWSDL.wsdl
  Tutorial: Contract-First web services con Visual Studio 2008
  www.adictosaltrabajo.com - Ivan Garcia Puebla
-->
<definitions name="cryptoServiceWSDL" 
    targetNamespace="http://tutorial.adictosaltrabajo.com/wsdl/cryptoServiceWSDL"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:tns="http://tutorial.adictosaltrabajo.com/wsdl/cryptoServiceWSDL" 
    xmlns:ns="http://tutorial.adictosaltrabajo.com/schema/cryptoSchema" 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
    <types>
        <xsd:schema targetNamespace="http://tutorial.adictosaltrabajo.com/wsdl/cryptoServiceWSDL">
            <xsd:import namespace="http://tutorial.adictosaltrabajo.com/schema/cryptoSchema" 
            schemaLocation="cryptoSchema.xsd"/>
        </xsd:schema>
    </types>
    <message name="encriptarRequest">
        <part name="original" type="xsd:string"/>
        <part name="clave" type="xsd:string"/>
        <part name="usarHashing" type="xsd:boolean"/>
    </message>
    <message name="encriptarResponse">
        <part name="resultado" type="xsd:string"/>
    </message>
    <message name="desencriptarRequest">
        <part name="datosDesencriptar" type="ns:datosDesencriptar"/>
    </message>
    <message name="desencriptarResponse">
        <part name="resultado" type="xsd:string"/>
    </message>
    <portType name="cryptoServiceWSDLPortType">
        <operation name="encriptar">
            <input name="inputEncriptar" message="tns:encriptarRequest"/>
            <output name="outputEncriptar" message="tns:encriptarResponse"/>
        </operation>
        <operation name="desencriptar">
            <input name="inputDesencriptar" message="tns:desencriptarRequest"/>
            <output name="outputDesencriptar" message="tns:desencriptarResponse"/>
        </operation>
    </portType>
    <binding name="cryptoServiceWSDLBinding" type="tns:cryptoServiceWSDLPortType">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="encriptar">
            <soap:operation/>
            <input name="inputEncriptar">
                <soap:body use="literal" 
                namespace="http://tutorial.adictosaltrabajo.com/wsdl/cryptoServiceWSDL"/>
            </input>
            <output name="outputEncriptar">
                <soap:body use="literal" 
                namespace="http://tutorial.adictosaltrabajo.com/wsdl/cryptoServiceWSDL"/>
            </output>
        </operation>
        <operation name="desencriptar">
            <soap:operation/>
            <input name="inputDesencriptar">
                <soap:body use="literal" 
                namespace="http://tutorial.adictosaltrabajo.com/wsdl/cryptoServiceWSDL"/>
            </input>
            <output name="outputDesencriptar">
                <soap:body use="literal" 
                namespace="http://tutorial.adictosaltrabajo.com/wsdl/cryptoServiceWSDL"/>
            </output>
        </operation>
    </binding>
    <service name="cryptoServiceWSDLService">
        <port name="cryptoServiceWSDLPort" binding="tns:cryptoServiceWSDLBinding">
            <soap:address location="http://localhost:8080/cryptoService"/>
        </port>
    </service>
</definitions>

Implementación con Visual Studio 2008. WSCF.blue

A partir del descriptor generaremos los stubs en C# del servicio que ofrezca las dos operaciones; posteriormente damos cuerpo al webservice y añadimos la lógica de negocio. En .NET podemos hacerlo a través del plugin/add-in para Visual Studio 2008, WSCF.blue. En el MSDN hay un interesante artículo publicado, http://msdn.microsoft.com/en-us/magazine/ee335699.aspx, sobre Contract-First Web Services y WSCF.blue, e incluye el enlace http://se.ethz.ch/~meyer/publications/computer/contract.pdf al artículo original de Bertrand Meyer: Applying «Design by Contract», aparecido en el IEEE en 1992

Instalación de WSCF.blue

Descargamos el instalador del acceso Download de http://www.codeplex.com/WSCFblue/ y lo instalamos siguiendo el asistente, de manera habitual.

Crear el servicio e importar el contrato

En Visual Studio 2008 creamos un nuevo proyecto de tipo Web de Visual C#. Utilizaremos la plantilla: Aplicación de servicio web de ASP.NET y damos como nombre wsEncriptacion. Agregamos una nueva carpeta llamada wscfEncriptacion y a continuación nuestros WSDL y Schema:

Proyecto creado en Visual Studio 2008
Proyecto creado en Visual Studio 2008

Bien en el menú Herramientas | Web Services Contract-First o bien pulsando con el botón derecho sobre cryptoServiceWSDL.wsdl, accedemos a las funcionalidades de WSCF.blue y escogemos Generate Web Service Code:

Menu contextual de WSCF.blue en Visual Studio 2008
Menú contextual de WSCF.blue

Establecemos las opciones de generación de código siguientes:

Opciones de generacion de codigo de WSCF.blue
Generación de código con WSCF.blue

Como se observa en la imagen, hemos establecido las opciones siguientes:

  • Server-side stub: vamos a generar el web service proveedor
  • Format SOA Actions: determinamos que las acciones SOAP de cada operación del contrato sigan el formato estándar de contratos de web services: <namespace>/<service>/<operation>[Response]
  • Separate files: cada tipo de dato del servicio web será generado en un fichero de C# distinto
  • Adjust casing: los tipos de datos generados seguirán la notación de .NET
  • Use synchronization context: el contexto determinará en qué hilo se ejecutará el web service
  • Generate a regular service class …: permitiremos generar métodos en donde implementar la lógica del servicio

Estas opciones son expuestas a modo de ejemplo. Cada proyecto requerirá de la configuración más adecuada para sus propósitos. La documentación se encuentra en: https://www.thinktecture.com/

Pulsamos en Generate y en unos instantes se confirmará la ejecución correcta de la operación:

Codigo generado correctamente
Código generado correctamente

En el explorador de soluciones podemos observar el código que ha sido generado:

Stubs del web service
Stubs generados del web service proveedor

Implementar el web service

La clase cryptoServiceWSDLPortType es la que finalmente alberga los métodos donde se implementará la lógica de negocio de nuestro servicio web. En su definición vemos que es una clase derivada de la interfaz IcryptoServiceWSDLPortType. En este tutorial implementaremos el fichero ASP web service, Service1.asmx, directamente con estos métodos. Para ello editamos el código de Service1.asmx y trasladamos el código de cryptoServiceWSDLPortType.cs de la siguiente manera.

  • Los atributos de compilación de la clase cryptoServiceWSDLPortType los establecemos en la clase de Service1
  • La clase Service1 la convertimos en derivada de IcryptoServiceWSDLPortType
  • Los métodos encriptar y desencriptar de cryptoServiceWSDLPortType los copiamos como métodos públicos no virtuales en la clase de Service1 con el atributo [WebService]

Finalmente eliminamos de cada método la sentencia de lanzamiento de excepción y añadimos nuestra lógica de encriptación/desencriptación. En este caso me he basado en el post: http://www.csharper.net/blog/library_encrypt_and_decrypt_methods_using_tripledes_and_md5.aspx. En resumen, Service1.asmx.cs queda de la forma:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Text;
using System.Security.Cryptography;

namespace wsEncriptacion
{
    /// <summary>
    /// Service1.asmx
    /// Tutorial: Contract-First web services con Visual Studio 2008
    /// www.adictosaltrabajo.com - Ivan Garcia Puebla
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    [System.ServiceModel.ServiceBehaviorAttribute(InstanceContextMode = 
        System.ServiceModel.InstanceContextMode.PerCall, 
        ConcurrencyMode = System.ServiceModel.ConcurrencyMode.Single)]
    public class Service1 : System.Web.Services.WebService, IcryptoServiceWSDLPortType
    {

        [WebMethod]
        public OutputEncriptar Encriptar(InputEncriptar request)
        {

            string textoAEncriptar = request.Encriptar.Original;
            string clave = request.Encriptar.Clave;
            bool usarHashing = request.Encriptar.UsarHashing;

            byte[] keyArray;
            byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(textoAEncriptar);

            if (usarHashing)
            {
                MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
                keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(clave));
                hashmd5.Clear();
            }
            else
                keyArray = UTF8Encoding.UTF8.GetBytes(clave);

            TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
            tdes.Key = keyArray;
            tdes.Mode = CipherMode.ECB;
            tdes.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = tdes.CreateEncryptor();
            byte[] resultArray = 
               cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

            string textoEncriptado = Convert.ToBase64String(resultArray, 0, resultArray.Length);

            tdes.Clear();

            OutputEncriptar output = new OutputEncriptar();
            EncriptarResponse response = new EncriptarResponse();
            response.Resultado = textoEncriptado;
            output.EncriptarResponse = response;

            return output;
        }


        [WebMethod]
        public OutputDesencriptar Desencriptar(InputDesencriptar request)
        {

            string textoADesencriptar = request.Desencriptar.DatosDesencriptar.Encriptado;
            string clave = request.Desencriptar.DatosDesencriptar.Clave;
            bool usarHashing = request.Desencriptar.DatosDesencriptar.UsarHashing;

            byte[] keyArray;
            byte[] toEncryptArray = Convert.FromBase64String(textoADesencriptar);

            if (usarHashing)
            {
                MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
                keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(clave));
                hashmd5.Clear();
            }
            else
                keyArray = UTF8Encoding.UTF8.GetBytes(clave);

            TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
            tdes.Key = keyArray;
            tdes.Mode = CipherMode.ECB;
            tdes.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = tdes.CreateDecryptor();
            byte[] resultArray = 
               cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

            string textoDesencriptado = UTF8Encoding.UTF8.GetString(resultArray);
            
            tdes.Clear();

            OutputDesencriptar output = new OutputDesencriptar();
            DesencriptarResponse response = new DesencriptarResponse();
            response.Resultado = textoDesencriptado;
            output.DesencriptarResponse = response;

            return output;
        }
    }
}

Publicar y acceder al servicio

Iniciamos la depuración del proyecto y en la URL http://localhost:49193/Service1.asmx accedemos a la descripción del servicio web:

Probar el web service con soapUI

Para probar nuestro servicio utilizaremos soapUI (ejemplo de uso en el tutorial de Roberto Canales: Servicio Web con NetBeans 6 y prueba con SoapUI). El descriptor de nuestro servicio es http://localhost:49193/Service1.asmx?wsdl.

Creamos un mensaje SOAP request hacia la operación Encriptar con valores de ejemplo:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:tem="http://tempuri.org/">
   <soap:Header/>
   <soap:Body>
      <tem:Encriptar>
         <tem:request>
            <tem:Encriptar>
               <original>Lorem ipsum dolor sit</original>
               <clave>autentia2009</clave>
               <usarHashing>true</usarHashing>
            </tem:Encriptar>
         </tem:request>
      </tem:Encriptar>
   </soap:Body>
</soap:Envelope>

y obtenemos como mensaje SOAP response:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <EncriptarResponse xmlns="http://tempuri.org/">
         <EncriptarResult>
            <EncriptarResponse>
               <resultado xmlns="">m2K6Pf20MrNvX3uKR1e54KqpzLxnHmR0</resultado>
            </EncriptarResponse>
         </EncriptarResult>
      </EncriptarResponse>
   </soap:Body>
</soap:Envelope>

Realizamos la operación inversa, desencriptando el resultado anterior. El mensaje SOAP request será:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:tem="http://tempuri.org/" 
 xmlns:cry="http://tutorial.adictosaltrabajo.com/schema/cryptoSchema">
   <soap:Header/>
   <soap:Body>
      <tem:Desencriptar>
         <tem:request>
            <tem:Desencriptar>
               <datosDesencriptar>
                  <cry:encriptado>m2K6Pf20MrNvX3uKR1e54KqpzLxnHmR0</cry:encriptado>
                  <cry:clave>autentia2009</cry:clave>
                  <cry:usarHashing>true</cry:usarHashing>
               </datosDesencriptar>
            </tem:Desencriptar>
         </tem:request>
      </tem:Desencriptar>
   </soap:Body>
</soap:Envelope>

El mensaje de respuesta obtenido concuerda con el esperado:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <DesencriptarResponse xmlns="http://tempuri.org/">
         <DesencriptarResult>
            <DesencriptarResponse>
               <resultado xmlns="">Lorem ipsum dolor sit</resultado>
            </DesencriptarResponse>
         </DesencriptarResult>
      </DesencriptarResponse>
   </soap:Body>
</soap:Envelope>

Nuestro web service ha sido implementado correctamente partiendo de la descripción de un contrato.

Conclusión

En el Microsoft SDK existe la utilidad ServiceModel Metadata Utility Tool (Svcutil.exe) que permite asimismo generar las clases proxy del web service a partir del descriptor. Utilizar WSCF.blue no requiere de la instalación del SDK, no obstante, y aporta unas capacidades añadidas al propio IDE Visual Studio bastante interesantes para propósitos Contract-first.

 

 

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad