CustomBinding en ServiceEndpoint con el contrato ‘IServicioWeb’ carece de TransportBindingElement

El otro día generando un cliente .NET para un servicio web JAXWS basado en SOAP 1.2 me topé con el siguiente problema:

System.InvalidOperationException: 
CustomBinding en ServiceEndpoint con el contrato 'IServicioWeb' carece de TransportBindingElement. 
Todos los enlaces deben tener al menos un elemento de enlace que se derive de TransportBindingElement.

O lo que es lo mismo pero en la lengua de Shakespeare:

System.InvalidOperationException: 
The CustomBinding on the ServiceEndpoint with contract 'Contract Name' lacks a TransportBindingElement. 
Every binding must have at least one binding element that derives from TransportBindingElement.

El servicio web se había generado desde Visual Studio utilizando la herramienta Add Service Reference, que por debajo hace uso de SvcUtil.exe, siguiendo los pasos que en su momento dejé recogidos en la entrada «Generando clientes de servicios web JAX-WS desde .NET».

Leyendo detenidamente el mensaje de error, se llega a la conclusión de que la sección customBinding no tiene referencia a ningún TransportBindingElement.

TransportBindingElement es una clase abstracta que representa un tipo de binding (enlace) de transporte. Los tipos de binding de transporte más comunes son:

  • Peer to peer – PeerTransportBindingElement
  • Http – HttpTransportBindingElement
  • Https – HttpsTransportBindingElement
  • TCP – TcpTransportBindingElement
  • Named Pipe – NamedPipeTransportBindingElement
  • MS MQ – MsmqTransportBindingElement
  • MS MQ Integration – MsmqIntegrationBindingElement
  • Connection Oriented – ConnectionOrientedTransportBindingElement

En este caso, el acceso al servicio web se realiza utilizando protocolo HTTP, por lo que hay que añadir una referencia a HttpTransportBindingElement en la configuración del cliente.

La manera simple de incluir esta información es en el fichero app.config de la aplicación.

Tras la generación con la herramienta SvcUtil.exe de Visual Studio el app.config tiene el siguiente aspecto:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name="ServicioWebPortBinding">
                    <textMessageEncoding messageVersion="Soap12" />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:8080/ServicioWebWSImpl/ServicioWeb"
                binding="customBinding" bindingConfiguration="ServicioWebPortBinding"
                contract="ServicioWebSR.ServicioWeb" name="ServicioWebPort" />
        </client>
    </system.serviceModel>
</configuration>

La solución al problema es añadir la configuración <httpTransport> en la definición del <CustomBinding> del fichero app.config.

La sintaxis de esta configuración es:

<httpTransport
    allowCookies=Boolean"
    authenticationScheme="Digest/Negotiate/Ntlm/Basic/Anonymous"
    bypassProxyOnLocal=Boolean"
    hostnameComparisonMode="StrongWildcard/Exact/WeakWildcard"
    keepAliveEnabled="Boolean"
    maxBufferSize="Integer"
    proxyAddress="Uri"
    proxyAuthenticationScheme="None/Digest/Negotiate/Ntlm/Basic/Anonymous"
IntegratedWindowsAuthentication: Specifies Windows authentication"
    realm="String"
    transferMode="Buffered/Streamed/StreamedRequest/StreamedResponse"
        unsafeConnectionNtlmAuthentication="Boolean"
        useDefaultWebProxy="Boolean" />  
  • allowCookies – Un valor booleano que especifica si el cliente acepta las cookies y las propaga en solicitudes futuras. El valor predeterminado es false.
  • authenticationScheme – Especifica el protocolo utilizado para autenticar solicitudes de cliente que son procesadas por un agente de escucha HTTP. El valor predeterminado es anónimo. Protocolos de autenticación válidos pueden ser:
    • Digest: especifica la autenticación implícita.
    • Negotiate: negocia con el cliente para determinar el esquema de autenticación. Si cliente y el servidor son compatibles con Kerberos, se utiliza; de lo contrario, se utiliza NTLM
    • Ntlm: especifica la autenticación NTLM.
    • Basic: especifica la autenticación básica.
    • Anonymous: especifica la autenticación anónima.
  • bypassProxyOnLocal – Un valor booleano que indica si se omitirá el servidor proxy para las direcciones locales. El valor predeterminado es false.
  • hostnameComparisonMode – Especifica el modo de comparación de HostName HTTP usado para analizar los URI. El valor predeterminado es StrongWildcard. Para analizar las URIs se puede hacer de tres maneras:
    • StrongWildcard: («+») coincide con todos los posibles nombres del host en el contexto del esquema especificado, puerto y URI relativo.
    • Exact: ningún carácter comodín
    • WeakWildcard: («*») coincide con todo posible nombre del host en el contexto del esquema especificado, puerto y URI relativo con los que no se han coincidido explícitamente o a través del mecanismo del carácter comodín fuerte.
  • keepAliveEnabled – Un valor booleano que especifica si se debe establecer una conexión continua con el recurso de Internet.
  • maxBufferSize – Un entero positivo que especifica el tamaño máximo del búfer. El valor predeterminado es 524288.
  • proxyAddress – Un URI que especifica la dirección del proxy HTTP. Si useSystemWebProxy es true, este valor debe ser null. El valor predeterminado es null.
  • proxyAuthenticationScheme – Especifica el protocolo utilizado para autenticar solicitudes de cliente que son procesadas por un proxy HTTP. La opción por defecto es Anonymous. Las diferentes posibilidades de autenticación son:
    • None: no se lleva a cabo ninguna autenticación.
    • Digest: especifica la autenticación implícita
    • Negotiate: negocia con el cliente para determinar el esquema de autenticación. Si cliente y el servidor son compatibles con Kerberos, se utiliza; de lo contrario, se utiliza NTLM
    • Ntlm: especifica la autenticación NTLM
    • Basic: especifica la autenticación básica
    • Anonymous: especifica la autenticación anónima
    • IntegratedWindowsAuthentication: especifica la autenticación de Windows
  • realm – Una cadena que especifica el dominio kerberos que se utilizará en el proxy/servidor. El valor predeterminado es una cadena vacía.
  • transferMode – Especifica si los mensajes se almacenan en búfer, se transmiten o si son una solicitud o una respuesta. El valor por defecto es Buffered. Las diferentes opciones de transferencia son:
    • Buffered: los mensajes de respuesta y solicitud están almacenados en búfer.
    • Streamed: se transmiten los mensajes de solicitud y respuesta
    • StreamedRequest: se transmite el mensaje de solicitud y el mensaje de respuesta está almacenado en búfer
    • StreamedResponse: se transmite el mensaje de respuesta y el mensaje de solicitud está almacenado en búfer
  • unsafeConnectionNtlmAuthentication – Un valor booleano que especifica si la conexión compartida no segura está habilitada en el servidor. El valor predeterminado es false. Si está habilitado, la autenticación NTLM se realiza una vez en cada conexión TCP.
  • useDefaultWebProxy – Un valor que especifica si se utiliza la configuración del proxy del equipo en lugar de la configuración específica del usuario. El valor predeterminado es true.

Conclusión

Todavía no tengo claro porque la generación automática del cliente no incluye la definición del tipo de transporte a utilizar en la comunicación. Lo que si tengo claro es que la solución es tan simple como añadir esta definición en el fichero de configuración de la aplicación.

        <bindings>
            <customBinding>
                <binding name="ServicioWebPortBinding">
                    <textMessageEncoding messageVersion="Soap12" />
			<httpTransport />
                </binding>
            </customBinding>
        </bindings>

Para este caso, y en vista de que la comunicación se hace por un canal HTTP sin proxy ni ninguna cosa rara, basta con añadir la etiqueta <httpTransport /> para solucionar el problema. Si existiera algún proxy entre el cliente y el servicio web, o algún sistema de autenticación, o se quisiera limitar las llamadas, se pueden ir añadiendo las posibilidades descritas a esta configuración.

Si la comunicación no fuera HTTP habría que generar otro tipo de binding de transporte, con sus propias configuraciones explicadas en la biblioteca MSDN de Microsoft.

Links

Generando clientes de servicios web JAX-WS desde .NET

Es normal en mi trabajo que los servicios web que hacemos estén basados en tecnología Java y que, sin embargo, sean utilizados desde clientes con tecnología .NET. Si no se observan de forma rigurosa los estándares el utilizar diferentes plataformas tecnológicas puede llegar a dar problemas. Por eso es indispensable probar los servicios web en todas aquellas plataformas susceptibles de utilizarlos.

Esta entrada explica las dos herramientas más comunes para generar clientes .NET, que a la postre servirán para probar la compatibilidad entre plataformas; Wsdl.exe y Svcutil.exe.

Web Services Description Language Tool (Wsdl.exe)

Desde la versión .NET Framework 1.1 hasta la versión 2.x, para crear aplicaciones SOA, Microsoft ofrece el framework ASMX. Este framework simplifica la creación tanto de los servicios web como de sus clientes. Los servicios web creados a través de esta tecnología son invocados exclusivamente a través del protocolo HTTP. Es por eso que son conocidos como Web References.

Al crear un cliente de servicio web utilizando Web Reference, estamos utilizando por debajo la herramienta Wsdl.exe proporcionada por Microsoft.

Si en la plataforma donde vamos a ejecutar las aplicaciones estamos limitados a la versión 2.x, entonces el uso de esta herramienta es obligatoria. Sin embargo, si se dispone de la versión 3.x o superiores de .NET Framework, ASMX está obsoleto y conviene reemplazarlo por WCF.

Wsdl.exe genera los objetos .NET necesarios para crear un cliente que invoque las operaciones que publica el servicio web. Esta invocación se puede hacer, entre otras, a través del wizard que proporciona Visual Studio o bien a través de línea de comando.

Visual Studio 2005, al estar basado en .NET Framework 2.0, sólo genera clientes mediante ASMX (wsdl.exe).  Visual Studio 2008 a la hora de crear el cliente permite crearlo tanto con ASMX (wsdl.exe) como con WCF (svcutil.exe). La versión de 2010 directamente ya no permite crearlo con ASMX y se oculta esta opción en un botón de opciones avanzadas.

También se pueden obtener los objetos ASMX ejecutando la herramienta Wsdl.exe desde la consola. Esta herramienta se encuentra en el directorio <Program Files>\Microsoft SDKs\Windows\<version>\Bin\wsdl.exe.

La sintaxis de ejecución es:

wsdl [options] {URL | path}

ServiceModel Metadata Utility Tool (Svcutil.exe)

Windows Communication Foundation, conocido como WCF, es el nuevo framework que a partir de la versión 3.x proporcionó Microsoft para desarrollar aplicaciones SOA.

WCF viene a sustituir al framework ASMX y aunque está disponible desde la versión 3.x del .NET Framework no es sino hasta la versión 4.x donde se simplifica y generaliza su uso.

Los clientes de servicios web que se crean con ésta tecnología son conocidos como Service References.

La herramienta que Microsoft proporciona para la generación de Service References es ServiceModel Metadata Utility Tool (svcutil.exe). Esta herramienta sirve tanto para generar los objetos .NET a partir de un fichero wsdl, como para hacer el proceso inverso, generar un wsdl a partir de objetos .NET.

Svcutil.exe se encuentra en el directorio <Program Files>\Microsoft SDKs\Windows\<version>\Bin\svcutil.exe.

La sintaxis de ejecución es:

 svcutil.exe [/t:code] <metadataDocumentPath>* | <url>* | <epr>

El servicio web de ejemplo.

Para probar las generación del cliente .NET con las herramientas descritas, el servicio web de ejemplo es un viejo conocido del blog, ya que es el servicio CalculadoraService que creé en la entrada «Creando un servicio web mediante anotaciones JAX-WS utilizando un enfoque ascendente (bottom-up)«. En esta entrada se creaba un servicio web JAXWS que proporcionaba cuatro operaciones aritméticas básicas: suma, resta, multiplicación y división.

Este servicio web está publicado en un servidor de Weblogic y se puede acceder a su descriptor a través de la URL: http://localhost:7001/JaxWSEjemploWAR/CalculadoraService?WSDL.

Generando el cliente .NET desde Visual Studio con svcutil.exe.

La generación del cliente .NET utilizando Visual Studio es la manera más gráfica y sencilla. Para realizar esta operación se utiliza una versión de prueba de Visual Studio 2010 y el framework .NET versión 4.0. El lenguaje elegido para la implementación es C# por la similitud que tiene con Java.

Para no complicar en exceso el ejemplo se elige crear una aplicación de consola, ya que lo que importa es ver que la llamada al servicio web se hace correctamente, dejando de lado las dificultades que podrían suponer añadir elementos gráficos.

El nombre de la aplicación de consola será CalculadoraServiceServiceReference.

Console Application para probar el cliente mediante Service References

El siguiente paso es añadir una referencia de servicio web a la aplicación. Para ello en la ventana Solution Explorer se hace click botón derecho en el apartado References y se selecciona Add Service Reference.

Botón derecho sobre References y seleccionar Add Service Reference...

Se abre el wizard para añadir referencias a servicios. En el campo Address se escribe la URL del WSDL del servicio web que se quiere añadir como referencia; http://localhost:7001/JaxWSEjemploWAR/CalculadoraService?WSDL. El nombre de la referencia es CalculadoraServiceReference.

Se pega el WSDL del servicio al que hay que acceder

Automáticamente se generan los objetos .NET que se necesitan para comunicarse con el servicio web. La clase del cliente propiamente dicha y que se instanciará para obtener la funcionalidad del servicio web es CalculadoraPortTypeClient.cs.

Objetos generados por svcutil.exe

Finalmente, en el método Main de la aplicación de consola se instancia esta clase del cliente y se llama a cualquiera de sus operaciones. En el ejemplo, se llama a la operación suma mostrando su resultado por consola.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CalculadoraServiceServiceReference.CalculadoraServiceReference;
namespace CalculadoraServiceServiceReference
{
  class Program
  {
    static void Main(string[] args)
    {
      int operando1 = 10;
      int operando2 = 13;
      CalculadoraPortTypeClient cliente = new CalculadoraPortTypeClient();
      int resultado = cliente.suma(operando1, operando2);
      Console.WriteLine("CalculadoraServiceSvcutil Prueba Empírica");
      Console.WriteLine(operando1.ToString() + "+" + operando2.ToString() + "=" + resultado.ToString());
      Console.ReadKey(true);
    }
  }
}

El resultado es:

Resultado llamada mediante Service Reference

Generando el cliente .NET desde Visual Studio con wsdl.exe.

Para generar el mismo ejemplo, pero utilizando ASMX con la versión de Visual Studio 2010, se accede a la generación Web Reference de las opciones Avanzadas que presenta el wizard que añade referencias de servicio. Esta opción avanzada no hace otra cosa que lanzar el wizard para añadir referencias web que se basa en la herramienta wsdl.exe.

Para realizar este ejemplo, al igual que el anterior se crea una aplicación de consola.

El nombre de la aplicación de consola será CalculadoraServiceWebReference.

Se selecciona una aplicación de consola para crear el cliente mediante web references.

Se añade una referencia de servicio web a la aplicación. Para ello en la ventana Solution Explorer se hace click botón derecho en el apartado References y se selecciona Add Service Reference.

Botón derecho sobre References y se selecciona Add Service Reference...

En el wizard que se abre se pincha directamente en el botón Advanced….

Se pincha el botón Advanced...

Las opciones avanzadas permiten personalizar la creación del cliente, pero todavía basándose en svcutil.exe, que no es lo que se pretende. Para utilizar wsdl.exe se pincha el botón Add Web Reference.

Click en el botón Add Web Reference...

Se abre el wizard antiguo para la generación de clientes mediante Web Reference. En el campo URL se introduce el WSDL del servicio para el que se genera el cliente. En el campo Web reference name se introduce el nombre del objeto .NET que va a encapsular este cliente; CalculadoraServiceWebReference. Click en el botón Add Reference.

Se rellena la URL con el WSDL y automáticamente detecta las operaciones del Web Service. Click en el botón Add Reference.

Automáticamente se generan los objetos .NET que se necesitan para comunicarse con el servicio web. La clase que se instancia para hacer uso del cliente es CalculadoraService.

Objetos .NET del cliente basado en Web Reference.

Finalmente, en el método Main de la aplicación de consola se instancia la clase del cliente y se llama a cualquiera de sus operaciones. En este ejemplo, y para hacer que se parezca lo mejor posible al anterior, también se elige la operación suma.

El cliente generado con wsdl.exe, sin embargo, difiere del generado con la herramienta svcutil.exe. La llamada a la operación suma necesita dos parámetros booleanos, que antes no aparecían, y que indican si se han informado o no los operandos. Además, el resultado no es un retorno de función, si no que se devuelve en un parámetro output de entrada.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CalculadoraServiceWebReference.CalculadoraServiceWebReference;
namespace CalculadoraServiceWebReference
{
  class Program
  {
    static void Main(string[] args)
    {
      int operando1 = 10;
      bool operando1Informado = true;
      int operando2 = 13;
      bool operando2Informado = true;
      int resultado;
      bool resultadoInformado;
      CalculadoraService cliente = new CalculadoraService();
      cliente.suma(operando1, operando1Informado, operando2, operando2Informado, out resultado, out resultadoInformado);
      Console.WriteLine("CalculadoraServiceWebReference Prueba Empírica");
      Console.WriteLine(operando1.ToString() + "+" + operando2.ToString() + "=" + resultado.ToString());
      Console.ReadKey(true);
    }
  }
}

Pese a la diferencia de generación del cliente, el servicio web se invoca correctamente y el resultado de la suma se puede ver por la consola:

Resultado de la invocación al servicio web vía web references (ASMX)

Conclusión

Microsoft dió un paso adelante en la interoperabilidad entre plataformas y en el framework .NET 3.x incluyó WCF para el manejo de servicios web. Esta nueva tecnología sustituye al framework ASMX que ha quedado relegado a ser utilizado en aquellas arquitecturas donde todavía se emplea el framework .NET 1.1 o el 2.x.

De todas formas, ya hemos visto que Microsoft en su plataforma de desarrollo, permite desarrollar todavía con las dos tecnologías ASMX y WCF. Pero, salvo que estemos limitados por la versión de framework .NET, lo recomendable es utilizar WCF, ya que nos aportará más compatibilidad entre plataformas y una aproximación a los estándares mucho más fiable que ASMX.

Links