martes, 27 de diciembre de 2011

Logging con TraceSource

En el post anterior Log con Trace y Debug, trabajamos con las clases Trace y Debug para llevar a cabo la tarea de registrar la información de traza de nuestra aplicación. Aunque sigue siendo una práctica válida, ahora se recomienda usar la clase TraceSource para realizar el seguimiento de nuestra aplicación.

Como veremos a continuación, TraceSource mejora notablemente la configuración y uso que hacemos de las trazas en nuestras aplicaciones .NET, respecto a sus antecesores Debug y Trace.

La principal diferencia al trabajar con TraceSource es que necesitamos definir un origen de seguimiento. Una vez definido este origen, podremos agregar agentes de escucha (TraceListeners) que siguen al origen definido. Además, con TraceSource también configuraremos el nivel de información que queremos registrar a través de Switches, de forma mucho más intuitiva que con Debug y Trace.

De nuevo, también es posible trabajar con TraceSource tanto creándolo desde cero en nuestro código, cómo configurándolo a través de ficheros de configuración. En nuestro caso trabajaremos desde el fichero de configuración de nuestra aplicación (app.config).

Veamos un primer ejemplo para entrar en calor.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <system.diagnostics>

    <sources>

      <source name="MyApp" switchName="MySwitch" switchType="System.Diagnostics.SourceSwitch">

        <listeners>

          <add name="MyListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="C:\Sergio.txt">

          </add>

        </listeners>

      </source>

    </sources>

    <switches>

      <add name="MySwitch" value="Warning"/>

    </switches>

  </system.diagnostics>

</configuration>


En este ejemplo hemos realizado las siguientes acciones:

  • Crear un origen de seguimiento llamado “MyApp”.
  • Determinar que el nivel de información registrada para nuestro origen, estará determinado por un Switch  llamado “MySwitch”, del tipo SourceSwitch.
  • Determinar que nuestro origen será escuchado por un agente de escucha del tipo llamado “MyListener”, del tipo TextWriterTraceListener.
  • Controlar que el nivel concreto de información registrada a través de “MySwitch” será el nivel “Warning”.

Aunque a priori esta solución podría parecer “el mismo perro pero con distinto collar”, la principal diferencia es que podemos crear tantos orígenes de seguimiento como creamos oportunos (varios en una misma aplicación, un origen por librería o componente, etc.), y de este modo configurar individualmente los agentes de escucha que registran la información del origen y que nivel de información se registra por cada origen de forma independiente.

Además, con TraceSource ya no utilizaremos métodos cómo WriteIf o WriteLineIf, sino que al escribir la información de traza especificaremos simplemente en que nivel está situado y el Switch hará el resto.

Ahora nuestro código C# será como el que sigue:

private static TraceSource ts = new TraceSource("MyApp");

 

static void Main(string[] args)

{

    // Con TraceEvent especificamos:

    //  Tipo de evento

    //  Identificador numérico libre

    //  Mensaje

    ts.TraceEvent(TraceEventType.Warning, 1, "Hola mundo!");

    ts.Close();

}


En este código:

  • Creamos una instancia de TraceSource para el origen de seguimiento “MyApp”.
  • Registramos información con el método TraceEvent, en el que especificamos además el nivel de la información registrada.
    • Además de TraceEvent también podemos utilizar métodos más especializados como TraceInformation o TraceData.
  • Finalmente, cerramos la instancia de TraceSource para que se vuelque la información al disco.
    • Si no quieres tener que preocuparte de llamar al método Flush o Close, podrías agregar <trace autoflush=”true” /> en <system.diagnostics> y funcionará igualmente.

El resultado del fichero es el siguiente:

MyApp Warning: 1 : Hola mundo!


Los valores más habituales de System.Diagnostics.TraceEventType y que a su vez son posibles valores para el atributo value del Switch de nuestro fichero app.config:

  • Critical
  • Error
  • Warning
  • Information
  • Verbose

Por supuesto también podemos en cualquier momento desactivar la traza con la siguiente configuración:

    <switches>

      <add name="MySwitch" value="Off"/>

    </switches>


Aunque en estos últimos dos posts hayamos visto Debug, Trace y TraceSource, lo cierto es que para operaciones de seguimiento también tenemos disponibles otro tipo de soluciones que no facilitarán la vida. Por ejemplo, log4net o Microsoft Enterprise Library con la sección Logging Application Block.

Un saludo!

jueves, 22 de diciembre de 2011

Log con Trace y Debug

Aunque Microsoft recomienda usar TraceSource en detrimento de las clases Debug y Trace, en este post veremos estas clases para en un siguiente post poder abordar TraceSource.

¿Qué es una traza?

La traza de un programa indica la secuencia de acciones o instrucciones de su ejecución, así como el valor de las variables utilizadas después de cada acción.

Lo usual es que un conjunto de trazas se guarde en un fichero de log para poder después consultar esta información.

¿Quién genera información de traza?

La información de traza es generada por las clases Debug y Trace del espacio de nombres System.Diagnostics.

¿Cómo se genera la información de traza?

Llamando a los métodos adecuados de la clase Debug y Trace.

Algunos de los métodos más habituales son:

  • Write
  • WriteLine
  • WriteIf
  • WriteLineIf

¿Qué diferencias hay entre Debug y Trace?

Debug sólo genera información de traza cuando estamos en modo de depuración, mientras que Trace la genera tanto en modo de depuración como en modo de despliegue (Release).

Además, ambas clases son iguales y comparten los mismos datos. Esto significa que un cambio en la clase Debug estará automáticamente sincronizado con su propiedad equivalente en la clase Trace e igualmente al revés.

¿Quién consume la información de traza?

La información que generan las clases Debug y Trace es consumida por los TraceListeners, que son clases concretas que heredan de la clase abstracta TraceListener.

Aunque a mi no me gusta, la traducción al español que hace Microsoft de los TraceListeners es “agentes de escucha” y la información de traza la llama “mensajes de seguimiento”.

Tanto la clase Debug como la clase Trace disponen de una referencia a los TraceListeners que consumirán sus datos a través de la propiedad Listeners.

Un TraceListener es un objeto que recibe la información de traza y la vuelca en algún medio. Este medio podría ser una ventana del IDE, un fichero en disco, una base de datos o cualquier otro destino válido.

¿Qué TraceListeners hay disponibles por defecto?

Por defecto, tenemos los siguientes TraceListeners que heredan directamente de la clase TraceListener.

  • DefaultTraceListener
  • EventLogTraceListener
  • EventProviderTraceListener
  • TextWriterTraceListener

Además, hay otra serie de TraceListeners que heredan de la clase TextWriterTraceListener.

  • XmlWriterTraceListener
  • DelimitedListTraceListener
  • ConsoleTraceListener
  • EventSchemaTraceListener

El único TraceListener que se agrega por defecto a cualquier proyecto es DefaultTraceListener.

¿Cómo agregamos TraceListeners a nuestra aplicación?

Hay dos vías posibles: por código o a través de los ficheros de configuración (web.config, app.config, etc.)

La forma preferida es a través de los ficheros de configuración porque esto supone que seremos capaces de agregar o eliminar TraceListeners a nuestra aplicación, una vez ha sido desplegada y sin tener que modificar nuestro código.

¿Puede crear mis propios TraceListeners?

Sí, es tan sencillo como crear una clase que herede de TraceListener e implementar los métodos necesarios para llevar a cabo la tarea de consumir la información de traza y guardarla en algún medio.

El ejemplo más definitivo es crear un TraceListener que guarda la información en una base de datos.

¿Un ejemplo, por favor?

Para nuestro ejemplo partiremos de una aplicación de consola en C# con el siguiente código:

using System;

using System.Collections.Generic;

using System.Text;

using System.Diagnostics;

 

namespace Logging

{

    class Program

    {

        static void Main(string[] args)

        {

            Debug.WriteLine("Primero");

            Trace.WriteLine("Segundo");

            Console.ReadLine();

        }

    }

}


Si ejecutamos el programa, veremos como por defecto nuestros mensajes (tanto Debug como Trace) son volcados a la ventana de resultados del IDE.

clip_image001[4]

Este comportamiento es debido a que por defecto, el programa viene con un TraceListener configurado de la clase DefaultTraceListener, que vuelva la información de traza en la ventana de resultados del IDE.

Podemos ver la lista de TraceListeners ejecutando el siguiente código:

static void PrintListeners()

{

    IEnumerator enumerador = Debug.Listeners.GetEnumerator();

    TraceListener tl;

    while (enumerador.MoveNext())

    {

        tl = (TraceListener)enumerador.Current;

        Console.WriteLine(String.Format("{0}, {1}", tl.Name, tl.GetType().Name));

    }

}

 

clip_image002[4]

Esto lo podemos ver claramente si eliminamos el contenido de la colección Listeners y observamos que ahora nuestra información de traza no es consumida por ningún TraceListener.

Debug.Listeners.Clear();


Una vez hemos visto que tenemos disponible por defecto DefaultTraceListener, es el momento de agregar más TraceListeners a nuestra aplicación, ya que DefaultTraceListener podría resultar útil durante el desarrollo y desde el IDE, pero no parece de mucha utilidad para una aplicación en producción.

Si optamos por la vía de agregar TraceListeners desde código (insisto en que esta no es la opción preferida) tendremos que hacer lo siguiente:

TextWriterTraceListener twl = new TextWriterTraceListener("C:\\log.txt");

Debug.Listeners.Add(twl);

 

Debug.WriteLine("Primero");

Trace.WriteLine("Segundo");

 

twl.Flush();


Con este código ya tendremos un fichero log.txt en nuestra raíz de C:\ que tendrá el texto “Primero Segundo” si estamos en Debug o “Segundo” si estamos en Release.

También es importante ver cómo es necesario llamar al método Flush() para que los cambios se guarden en disco.

Cómo hemos dicho antes, la opción preferida es agregar o eliminar los TraceListeners desde los ficheros de configuración.

Un fichero de configuración para agregar TextWriterTraceListener a nuestra aplicación, sería el siguiente:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <system.diagnostics>

    <trace autoflush="true">

      <listeners>

        <add name="TextWriterTraceListener"

         type="System.Diagnostics.TextWriterTraceListener" initializeData="C:\log.txt"/>

      </listeners>

    </trace>

  </system.diagnostics>

</configuration>


¿Qué es un Switch?

La clase Switch es la clase abstracta base para las clases concretas BooleanSwitch y TraceSwitch.

Un Switch representa una clase con la podemos modificar el comportamiento de nuestro registro de traza en una aplicación en producción.

De nuevo, la traducción al español que hace Microsoft de Swtich es “modificadores”.

Hasta ahora hemos volcado a disco de forma incondicional toda la información de traza que hemos recibido, pero:

  • ¿Cómo podría desactivar el registro de la traza?
  • ¿Cómo podría sólo registrar cierta información?

Estas preguntas obtienen respuesta utilizando Switches.

Primero abordaremos la clase BooleanSwitch que representa un valor booleano para poder determinar desde nuestro código si está o no activo el registro de la traza.

Para utilizar BooleanSwitch tenemos que:

  • Agregar BooleanSwitch a nuestro fichero de configuración.
  • Leer la configuración de BooleanSwitch desde nuestro código.
  • Registrar la información de traza en función de la propiedad Enabled de BooleanSwitch.

Veamos un ejemplo.

Primero mostraremos el fichero de configuración:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <system.diagnostics>

    <switches>

      <add name="Activated" value="1" />

    </switches>

    <trace autoflush="true">

      <listeners>

        <add name="TextWriterTraceListener"

         type="System.Diagnostics.TextWriterTraceListener" initializeData="C:\log.txt"/>

      </listeners>

    </trace>

  </system.diagnostics>

</configuration>


Fíjate que no hemos especificado el tipo de Switch porque será en nuestro código donde después haremos el casting al tipo adecuado. Además, el nombre del Switch puede ser cualquiera y la única imposición es que value debe ser 1 o 0.

Ahora nuestro código del programa:

BooleanSwitch bs = new BooleanSwitch("Activated", "Registro activo");

 

Debug.WriteLineIf(bs.Enabled, "Primero");

Trace.WriteLineIf(bs.Enabled, "Segundo");


Como podemos ver, casteamos el Switch con nombre “Activated” a un BooleanSwitch y después utilizamos el método WriteLineIf que espera una condición como primer parámetro.

Después de ver BooleanSwitch (que lo cierto es que es muy sencilla) veremos ahora TraceSwitch que nos permite afinar no sólo si está o no activo el registro, sino también que información queremos registrar.

El único cambio que haremos en nuestro fichero app.config será en la sección de switches. Ahora pasaremos a tener lo siguiente:

<add name="WhatInfo" value="2" />


En realidad, agregar un TraceSwitch no difiere en exceso de agregar un BooleanSwitch. La única diferencia es que la propiedad value ahora es un enumerado con los siguientes posibles valores:

  • Off. 0
  • Error. 1
  • Warning. 2
  • Info. 3
  • Verbose. 4

Con TraceSwitch lo que conseguimos es determinar qué nivel de información queremos guardar.

Podemos desactivar directamente el registro y conseguir el mismo efecto que con BooleanSwitch si utilizamos el valor 0 (Off), pero también podemos determinar que nivel de información queremos registrar. Cabe mencionar al respecto que cualquier nivel de información incluye automáticamente a niveles inferiores. Es decir, si elegimos Warning (2), también registraremos Error (1).

Los distintos niveles de traza son expuestos con propiedades booleanas en TraceSwitch (por ejemplo, TraceError, TraceWarning, etc.)

Ahora bien, que hayamos agregado un TraceSwitch a nuestro fichero app.config no significa que ocurra magia y, es por ello, que somos nosotros mismos quienes desde código tenemos que utilizar TraceSwitch para registrar el nivel de información especificado.

Veamos un ejemplo:

TraceSwitch ts = new TraceSwitch("WhatError", "¿Qué registrar?");

 

Debug.WriteLineIf(ts.TraceError, "Error");           

Debug.WriteLineIf(ts.TraceWarning, "Advertencia");

Debug.WriteLineIf(ts.TraceInfo, "Información");

Debug.WriteLineIf(ts.TraceVerbose, "Ampliado");


Podemos ver que si no acompañamos nuestro TraceSwitch con métodos que esperan una condición como WriteLineIf, el efecto no será ninguno. Siendo así, la conclusión es que debemos preparar nuestro código para poder trabajar con los distintos Switches.

Un saludo!

lunes, 19 de diciembre de 2011

Servicios web en ASP.NET

En este post veremos los servicios web de ASP.NET.

Los servicios web de ASP.NET, también llamados simplemente servicios web o servicios web XML, son los ficheros con extensión .asmx.

No hay que confundir los servicios web de ASP.NET con los servicios WCF.

Lo cierto es que este post pretende ser una breve referencia para poder después comparar los servicios web con los servicios WCF. A este propósito, te recomiendo encarecidamente la lectura del libro Introducción a Windows Communication Foundation de Hadi Hariri en campusmvp.

Dicho esto, el siguiente post contendrá los siguientes puntos:

  • Crear el servicio web
  • Probar el servicio web
  • Consumir el servicio web
    • Aplicación de consola
    • Sitio web
    • ASP.NET AJAX
    • jQuery
  • Configurar el servicio web

Crear el servicio web

Para añadir un servicio web a nuestro proyecto:

Agregar > Nuevo elemento > Servicio Web

En una aplicación web:

clip_image002[6]

En un sitio web:

clip_image004[6]

El código del servicio web está:

  • WebService1.asmx.vb (aplicación web) .
  • WebService1.vb (sitio web).

Para nuestro ejemplo, trabajaremos con la aplicación web y crearemos 2 métodos:

  • Un método que recibe un tipo simple.
  • Un método que recibe un tipo complejo.

Imports System.Web.Services

Imports System.Web.Services.Protocols

Imports System.ComponentModel

' Para permitir que se llame a este servicio Web desde un script, usando ASP.NET AJAX, quite la marca de comentario de la línea siguiente.

' <System.Web.Script.Services.ScriptService()> _

<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _

<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _

<ToolboxItem(False)> _

Public Class WebService1

    Inherits System.Web.Services.WebService

    <WebMethod()> _

    Public Function Saludar(ByVal nombre As String) As String

        Return String.Format("Hola {0}", nombre)

    End Function

    <WebMethod()> _

    Public Function SaludarPersona(ByVal persona As Persona) As String

        Return String.Format("Hola {0}, {1}", persona.Nombre, persona.Apellidos)

    End Function

End Class


Public
Class Persona

    Public Property Nombre As String

    Public Property Apellidos As String

End Class


Como vemos en el código anterior, la clase del servicio web hereda de
System.Web.Services.WebService. Esta clase nos da acceso a los objetos comunes de ASP.NET como Server, Session, etc. Aunque no es obligatorio que nuestro servicio herede de esta clase, está claro que nos será de gran ayuda.

Probar el servicio web

Para probar el servicio web simplemente tenemos que acceder a través del navegador a nuestro fichero WebService1.asmx y ASP.NET nos devolverá automáticamente una página con la lista de métodos disponibles.

clip_image006[6]

Esta página autogenerada por el runtime de ASP.NET es posible porque a través de WSDL (Web Service Description Language), es posible descubrir que operaciones expone un servicio web.

De hecho, si escribimos en el navegador la siguiente dirección: http://localhost/WebApplication1/WebService1.asmx?WSDL, podemos ver como nos devuelve un fichero XML que corresponde con la información del servicio web, que es la información que utiliza ASP.NET para autogenerar la página anterior y que utilizará posteriormente para generar automáticamente un proxy que nos permite consumir el servicios desde las aplicaciones cliente.

clip_image008[6]

Si pulsamos en el método “Saludar”, navegaremos a la dirección WebService1.asmx?op=Saludar y de nuevo ASP.NET genera una página donde podemos probar el método en cuestión.

clip_image010[6]

Si utilizamos Fiddler para investigar la petición y respuesta, obtenemos los siguientes resultados

POST http://localhost:58638/WebService1.asmx/Saludar HTTP/1.1

Accept: text/html, application/xhtml+xml, */*

Referer: http://localhost:58638/WebService1.asmx?op=Saludar

Accept-Language: es-ES

User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

Host: localhost:58638

Content-Length: 13

Connection: Keep-Alive

Pragma: no-cache

nombre=Sergio

HTTP/1.1 200 OK

Server: ASP.NET Development Server/10.0.0.0

Date: Mon, 19 Dec 2011 07:04:30 GMT

X-AspNet-Version: 4.0.30319

Cache-Control: private, max-age=0

Content-Type: text/xml; charset=utf-8

Content-Length: 96

Connection: Close

<?xml version="1.0" encoding="utf-8"?>

<string xmlns="http://tempuri.org/">Hola Sergio</string>


A partir de estos datos podemos concluir que en la página de pruebas autogenerada por ASP.NET:
 

  • No se utiliza SOAP ni en la petición ni la respuesta.
  • La petición es un simple envío del formulario a través del método POST.
  • La respuesta es devuelta en XML.

También es importante ver cómo es posible llamar a un método web con a través de la url ServicioWeb/Método. En nuestro caso WebService1.asmx/Saludar (esto será importante más adelante cuando queramos consumir el servicio desde jQuery)

Para el método SaludarPersona y puesto que trabaja con un tipo complejo, ASP.NET no genera ningún formulario de forma automática para probar nuestro método.

Si por algún motivo no deseas que ASP.NET genere automáticamente la página de pruebas o el WSDL, puedes hacerlo. Más información en este blog.

Consumir el servicio web

El primer paso para consumir el servicio será publicarlo en nuestro IIS local.

Una vez publicado creamos los siguientes clientes:

  • Una aplicación de consola.
  • Un sitio web.
  • ASP.NET AJAX.
  • jQuery.

Aplicación de consola

Agregar referencia de servicio… > Avanzadas… > Agregar referencia web …

clip_image012[6]

Al agregar la referencia, se agregan referencias a los siguientes ensamblados:

  • System.EnterpriseServices
  • System.Web.Services

Además, también se crea la carpeta “Web References”:

clip_image014[6]

El fichero más relevante es Reference.vb donde se ha creado una clase Proxy para llamar a nuestro servicio, además de las clases dependientes como por ejemplo Persona.

Por último, se ha agregado el siguiente código a nuestro fichero app.config, para configurar en tiempo de ejecución la url donde se aloje el servicio web.

<applicationSettings>

  <ConsoleApplication1.My.MySettings>

    <setting name="ConsoleApplication1_ReferenciaWeb_WebService1" serializeAs="String">

      <value>http://localhost/WebApplication1/WebService1.asmx</value>

    </setting>

  </ConsoleApplication1.My.MySettings>

</applicationSettings>


Llamar al servicio web no puede ser más sencillo.

Dim p As New ReferenciaWeb.Persona

p.Nombre = "Sergio"

p.Apellidos = "León"

Dim s As New ReferenciaWeb.WebService1

s.SaludarPersona(p)

 

Para ver con Fiddler el tráfico generado por la llamada, es necesario cambiar localhost por nuestro nombre de máquina en el setting del fichero app.config. Más información en http://fiddler2.com/fiddler/help/hookup.asp#Q-LocalTraffic

POST http://lobezno/WebApplication1/WebService1.asmx HTTP/1.1

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 4.0.30319.488)

Content-Type: text/xml; charset=utf-8

SOAPAction: "http://tempuri.org/SaludarPersona"

Host: lobezno

Content-Length: 377

Expect: 100-continue

Connection: Keep-Alive

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SaludarPersona xmlns="http://tempuri.org/"><persona><Nombre>Sergio</Nombre><Apellidos>León</Apellidos></persona></SaludarPersona></soap:Body></soap:Envelope>

HTTP/1.1 200 OK

Cache-Control: private, max-age=0

Content-Type: text/xml; charset=utf-8

Server: Microsoft-IIS/7.5

X-AspNet-Version: 4.0.30319

X-Powered-By: ASP.NET

Date: Mon, 19 Dec 2011 07:37:14 GMT

Content-Length: 386

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SaludarPersonaResponse xmlns="http://tempuri.org/"><SaludarPersonaResult>Hola Sergio, León</SaludarPersonaResult></SaludarPersonaResponse></soap:Body></soap:Envelope>

 

Ahora podemos ver cómo se utiliza SOAP tanto para llamada como la respuesta.

En cualquier momento podemos actualizar la referencia al servicio web desde el explorador de soluciones:

clip_image016[6]

Sitio web

Al agregar la referencia al servicio web se crean las siguientes carpetas y ficheros:

clip_image018[6]

Además, en el fichero web.config se añade la siguiente información:

<appSettings>

  <add key="ReferenciaWeb.WebService1" value="http://localhost/WebApplication1/WebService1.asmx"/>

</appSettings>

 

En un sitio web, de nuevo utilizaremos SOAP tanto para la llamada como para la respuesta y el código para llamar al servicio es el mismo que el especificado para la aplicación de consola.

ASP.NET AJAX

Para consumir un servicio web desde un formulario web a través ASP.NET AJAX hay que tener en cuenta que hay que hacerlo desde el mismo proyecto en el que está alojado el servicio web.

Los pasos necesarios son:

Descomentar la línea de código del servicio web:

<System.Web.Script.Services.ScriptService()> _

 

Agregar a la página un control ScriptManager y agregar un elemento ServiceReference a nuestro control ScriptManager.

<asp:ScriptManager ID="Scriptmanager1" runat="server">

<Services>

<asp:ServiceReference Path="~/WebService1.asmx" />

</Services>

</asp:ScriptManager>

Escribir el siguiente código Javascript:

function pageLoad() {

    //WebApplication1.WebService1.Saludar(nombre, onSuccess, onFailed, userContext)

    WebApplication1.WebService1.Saludar("Sergio", function (result, userContext, methodName) {

        alert(result);

    }, function (result, userContext, methodName) {

        alert(result.get_message());

        alert(result.get_stackTrace());

        alert(result.get_exceptionType());

        alert(result.get_statusCode());

    });

    //WebApplication1.WebService1.Saludar(persona, onSuccess, onFailed, userContext)

    var p = new WebApplication1.Persona();

    p.Nombre = "Sergio";

    p.Apellidos = "León";

    WebApplication1.WebService1.SaludarPersona(p, function (result, userContext, methodName) {

        alert(result);

    }, function (result, userContext, methodName) {

        alert(result.get_message());

        alert(result.get_stackTrace());

        alert(result.get_exceptionType());

        alert(result.get_statusCode());

    });

}

 

Como podemos ver, ASP.NET AJAX ha creado una clase Proxy para llamar a nuestro servicio web (la clase WebApplication1.WebService1) y también ha generado las clases dependientes como la clase Persona.

Aquí cabe mencionar que si el proyecto es de tipo aplicación web, entonces se creará la clase <NombreProyecto>.<NombreServicio>, mientras que si hablamos de un sitio web, el nombre de clase será simplemente <NombreServicio>.

Todo esto es posible porque el control ScriptManager incluye automáticamente el siguiente código en la página:

<script src=" /WebApplication1/WebService1.asmx/jsdebug" type="text/javascript"></script>


Este código descarga un fichero javascript que es un Proxy de nuestro servicio web para utilizar el servicio desde Javascript.

El parámetro /jsdebug estará activo si estamos depurando la aplicación, en modo Release el parámetro será /js que devuelve una versión minimizada y optimizada para producción.

La traza con Fiddler para la llamada al método Saludar es la siguiente: 

POST http://localhost:58638/WebService1.asmx/Saludar HTTP/1.1

Accept: */*

X-Requested-With: XMLHttpRequest

Content-Type: application/json; charset=utf-8

Referer: http://localhost:58638/WebForm1.aspx

Accept-Language: es

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)

Host: localhost:58638

Content-Length: 19

Connection: Keep-Alive

Pragma: no-cache

{"nombre":"Sergio"}

{"persona”: {__type”:”Persona”,”Nombre”:”Sergio”,”Apellidos”:”León”}}

HTTP/1.1 200 OK

Server: ASP.NET Development Server/10.0.0.0

Date: Mon, 19 Dec 2011 09:35:15 GMT

X-AspNet-Version: 4.0.30319

Cache-Control: private, max-age=0

Content-Type: application/json; charset=utf-8

Content-Length: 19

Connection: Close

{"d":"Hola Sergio"}

{"d":"Hola Sergio, León"}

 

Se ha resaltado en amarillo la información clave y además en naranja la llamada y respuesta para el método SaludarPersona.

Como podemos observar, al llamar a nuestro servicio web desde ASP.NET AJAX, tanto la llamada como la respuesta han utilizado JSON en vez de SOAP.

jQuery

Nuestro último cliente será jQuery y lo cierto es que el más sencillo de todos.

La ventaja de jQuery frente a ASP.NET AJAX es que puede consumir servicios que no estén en la solución actual y además tampoco es necesario ningún cambio en el servicio web, es decir, no es necesario descomentar la línea que sí tuvimos que descomentar para trabajar con ASP.NET AJAX.

Además, para proyectos de ASP.NET MVC sólo tenemos disponible está opción

$(document).ready(function () {

    var options = {

        type: "POST",

        url: "/WebService1.asmx/Saludar",

        data: "{ 'nombre': 'Sergio' }",

        contentType: "application/json; charset=utf-8",

        dataType: "json",

        success: function (data, textStatus, jqXHR) { //do something...
        },

        error: function (jqXHR, textStatus, errorThrown) { //do something...
        }

    }

    $.ajax(options);

});

 

Lo más relevante del este código es:

  • Se utiliza JSON para la llamada según indica el parámetro contentType.
  • Se pasan los datos en la propiedad data, de nuevo como un objeto JSON pero en una cadena.
  • El nombre del parámetro es case-sensitive, así que nombre funcionará pero Nombre fallará.
  • Se espera que la respuesta sea en formato JSON según indica el parámetro dataType.

Si quisiéramos llamar al método SaludarPersona que recibe un objeto del tipo Persona, habría que escribir lo siguiente en la propiedad data:

data: "{ 'persona' : { 'Nombre' : 'Sergio' , 'Apellidos' : 'León' } }",

 

Configurar el servicio web

Hasta ahora, la única configuración que hemos realizado en el servicio web ha sido la siguiente:  

  • Agregar el atributo WebMethod para aquellos métodos que queremos exponer al cliente.
  • Descomentar <System.Web.Script.Services.ScriptService()> si queremos habilitar nuestro servicio web para ser consumido por ASP.NET AJAX.

Lo cierto es que en la mayoría de los casos será suficiente con estos parámetros, pero es recomendable conocer que posibilidades de configuración tenemos a nuestra disposición para un servicio web.

 

BufferResponse

  • Por defecto True.
  • Esta propiedad tiene un comportamiento idéntico a la propiedad de la directiva @Page de una página .aspx. Determina si está activo el buffer durante la respuesta HTTP al cliente. Sino está activo se envía la salida en trozos de 16KB, en caso contrario sólo se envía una única respuesta con todo el contenido

CacheDuration

  • Por defecto 0 (no caché).
  • Determina cuantos segundos se cacheará la salida del método web.
  • Cabe mencionar que se cachea por cada conjunto único de parámetros de entrada del método.

Description

  • Por defecto “”.
  • Es un texto descriptivo asociado al método para el WSDL asociado al servicio web.

EnableSession

  • Por defecto True.
  • Determina si la sesión estará disponible en el servicio web.
  • En caso de estar activada podremos utilizar HttpContext.Current.Session o Session directamente si nuestro servicio web hereda de la clase WebService.

ResponseFormat

  • Especifica el formato de la respuesta cuando el servicio es consumido desde Javascript.
  • Por defecto la respuesta es devuelta en Json.
  • Si se utiliza Json, los datos de respuesta son serializados con la clase JavascriptSerializer, si se especifica Xml los datos de respuesta son serializados con la clase XmlSerializer.

UseHttpGet

  • Por defecto False.
  • Especifica si ASP.NET AJAX llama a los métodos del servicio web con el verbo GET, en modo contrario lo llama con el verbo POST (comportamiento predeterminado).

La mayoría de estas propiedades hablan por sí solas, pero quizás ResponseFormat y UseHttpGet requieran su propia explicación.

 

Por defecto, las llamadas a métodos de los servicios web se harán con el verbo POST (en ASP.NET AJAX), mientras que en jQuery somos nosotros mismos quien especifica el verbo en llamada a $.ajax(). En este situación, si intentamos con jQuery llamar a un método web con el verbo GET obtendremos un error diciéndonos que no está habilitado el método para el verbo seleccionado (en ASP.NET AJAX simplemente no podemos decidir el verbo puesto que está “harcodeado” en la clase proxy que genera automáticamente el control ScriptManager). Siendo así, si utilizamos UseHttpGet con el valor True lo que estamos diciendo es que ahora el único verbo permitido será GET (en vez de POST), luego se “harcodeará” en ASP.NET AJAX y lo tendremos que escribir a mano con jQuery y $.ajax(). Es decir, o va por POST o va por GET, pero no por ambos (todo esto hablando de peticiones de cliente, porque clientes pesados – como una aplicación de consola - siempre harán peticiones POST con independencia de UseHttpGet).


Respecto a ResponseFormat, cambiaremos el método SaludarPersona para que la respuesta sea en Xml.

 

Si especificamos que la respuesta será Xml (con ResponseFormat) podemos, bien devolver un XmlDocument o bien delegar en el framework para que devuelva Xml bien formado.

La ventaja de devolver un XmlDocument es que nosotros estamos controlando el Xml devuelto, mientras que si simplemente devolvemos un tipo básico o complejo lo que haremos es delegar en el framework la respuesta. Si por ejemplo devolvemos un entero sin más, el Xml devuelto sería <?xml version="1.0" encoding="utf-8"?><int>5</int>, y para una cadena podría ser <?xml version="1.0" encoding="utf-8"?><string>Hola Sergio</string> o simplemente “Hola Sergio”, en función del atributo XmlSerializeString (por defecto False, luego “Hola Sergio”). Para un tipo complejo devolvería el resultado de serializar el tipo con XmlSerializer. Por ejemplo y para nuestro tipo Persona:

<?xml version="1.0" encoding="utf-8"?>
<Persona xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Nombre>panico</Nombre>
<Apellidos>enlaxbox</Apellidos>
</Persona>

A continuación un ejemplo de como devolver un XmlDocument en vez de delegar en la serialización de XmlSerializer

<WebMethod()> _
<Script.Services.ScriptMethod(ResponseFormat:=Script.Services.ResponseFormat.Xml)> _
Public Function SaludarPersona(ByVal persona As Persona) As XmlDocument

        Dim xmlDoc As New XmlDocument

        Dim xmlString As String = _

            "<?xml version=""1.0"" encoding=""utf-8"" ?>" + _

            " <respuesta>" + _

            " <saludo>" + _

            " " & persona.Nombre & " " & persona.Apellidos & _

            " </saludo>" + _

            " </respuesta>"

        xmlDoc.LoadXml(xmlString)

        Return xmlDoc

    End Function


Si ahora vemos ahora la respuesta con Fiddler, podemos observar que la respuesta para ASP.NET AJAX y para jQuery ha sido la siguiente:

HTTP/1.1 200 OK

Server: ASP.NET Development Server/10.0.0.0

Date: Mon, 19 Dec 2011 10:34:01 GMT

X-AspNet-Version: 4.0.30319

Cache-Control: private, max-age=0

Content-Type: text/xml; charset=utf-8

Content-Length: 100

Connection: Close

<?xml version="1.0" encoding="utf-8"?><respuesta><saludo>Sergio León</saludo></respuesta>

 

En jQuery ha sido necesario cambiar dataType: “json” por dataType: “xml” para indicar que la respuesta esperada es Xml (aunque la llamada se sigue realizando en formato Json).

Lo cierto es que no veo mucha utilidad a la respuesta en Xml cuando estamos trabajando desde Javascript, pero es bueno saber que está ahí por si algún día es necesaria.

En cualquier caso, te dejo aquí un enlace donde muestran como trabajar con Xml desde Javascript.

Resumiendo que es gerundio:

  • Si nuestro cliente es ASP.NET AJAX o jQuery, por defecto tanto la llamada como la respuesta utilizarán JSON, pero podremos configurar el servicio para utilizar XML.
  • Si nuestro cliente es cualquier otro (aplicación de consola por ejemplo), tanto la llamada como la respuesta utilizará SOAP.

Por último, quiero hablar del famoso warning de tempuri.org. Si navegamos a tempuri.org podemos comprobar que existe y es un dominio de Microsoft que nos advierte de que cada servicio web necesita un único espacio de nombres para que las aplicaciones clientes puedan distinguir nuestros servicios en la web. Puedes ampliar más información en http://stackoverflow.com/questions/180985/what-is-tempuri-org pero está claro que la solución pasa por cambiar tempuri.org por algo como http://miaplicación.midominio.org.

Un saludo!