Contract-First web services con Visual Studio 2008
Índice
- Introducción
- Descriptor del contrato
- Implementación con Visual Studio 2008. WSCF.blue
- Probar el web service con soapUI
- 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:
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
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:
Menú contextual de WSCF.blue
Establecemos las opciones de generación de código siguientes:
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:
Código generado correctamente
En el explorador de soluciones podemos observar el código que ha sido generado:
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.