Descripción de los servicios web de ASP.NET AJAX

por Scott Cate

Los servicios web son una parte integral de .NET Framework que proporciona una solución multiplataforma para intercambiar datos entre sistemas distribuidos. Aunque los servicios web se usan normalmente para permitir que diferentes sistemas operativos, modelos de objetos y lenguajes de programación envíen y reciban datos, también se pueden usar para insertar datos dinámicamente en una página de AJAX de ASP.NET o enviar datos de una página a un sistema back-end. Todo esto se puede hacer sin recurrir a las operaciones de postback.

Llamada a servicios web con ASP.NET AJAX

Dan Wahlin

Los servicios web son una parte integral de .NET Framework que proporciona una solución multiplataforma para intercambiar datos entre sistemas distribuidos. Aunque los servicios web se usan normalmente para permitir que diferentes sistemas operativos, modelos de objetos y lenguajes de programación envíen y reciban datos, también se pueden usar para insertar datos dinámicamente en una página de AJAX de ASP.NET o enviar datos de una página a un sistema back-end. Todo esto se puede hacer sin recurrir a las operaciones de postback.

Aunque el ASP.NET control UpdatePanel de AJAX proporciona una manera sencilla de habilitar cualquier página de ASP.NET, puede haber ocasiones en las que necesite acceder dinámicamente a los datos en el servidor sin usar UpdatePanel. En este artículo, verá cómo hacerlo mediante la creación y el consumo de servicios web en ASP.NET páginas de AJAX.

Este artículo se centrará en la funcionalidad disponible en el núcleo ASP.NET extensiones de AJAX, así como en un control habilitado de servicio web en el kit de herramientas de AJAX de ASP.NET denominado AutoCompleteExtender. Entre los temas tratados se incluyen la definición de servicios web habilitados para AJAX, la creación de servidores proxy de cliente y la llamada a servicios web con JavaScript. También verá cómo se pueden realizar llamadas de servicio web directamente a ASP.NET métodos de página.

Configuración de servicios web

Cuando se crea un nuevo proyecto de sitio web con Visual Studio 2008, el archivo web.config tiene una serie de nuevas adiciones que pueden ser desconocidas para los usuarios de versiones anteriores de Visual Studio. Algunas de estas modificaciones asignan el prefijo "asp" a controles ASP.NET AJAX para que se puedan usar en páginas, mientras que otros definen HttpHandlers y HttpModules necesarios. En la lista 1 se muestran las modificaciones realizadas en el elemento <httpHandlers> de web.config que afecta a las llamadas a servicio web. El HttpHandler predeterminado que se usa para procesar llamadas .asmx se quita y se reemplaza por una clase ScriptHandlerFactory ubicada en el ensamblado de System.Web.Extensions.dll. System.Web.Extensions.dll contiene toda la funcionalidad básica que usa ASP.NET AJAX.

Lista 1. configuración del controlador de servicios web de ASP.NET AJAX

<httpHandlers>
     <remove verb="*" path="*.asmx"/>
     <add verb="*" path="*.asmx" validate="false"
          type="System.Web.Script.Services.ScriptHandlerFactory,
          System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,
          PublicKeyToken=31bf3856ad364e35"/>
</httpHandlers>

Este reemplazo de HttpHandler se realiza para permitir que se realicen llamadas de notación de objetos JavaScript (JSON) desde ASP.NET páginas de AJAX a servicios web de .NET mediante un proxy de servicio web de JavaScript. ASP.NET AJAX envía mensajes JSON a servicios web en lugar de las llamadas estándar de Protocolo de acceso a objetos simples (SOAP) normalmente asociadas a servicios web. Esto da como resultado mensajes de solicitud y respuesta más pequeños en general. También permite un procesamiento del lado cliente más eficaz de los datos, ya que la biblioteca de JavaScript de AJAX de ASP.NET está optimizada para trabajar con objetos JSON. La lista 2 y la lista 3 muestran ejemplos de mensajes de solicitud y respuesta del servicio web serializados en formato JSON. El mensaje de solicitud que se muestra en la lista 2 pasa un parámetro de país con un valor de "Bélgica" mientras que el mensaje de respuesta de la lista 3 pasa una matriz de objetos Customer y sus propiedades asociadas.

Lista 2. Mensaje de solicitud del servicio web serializado en JSON

{"country":"Belgium"}

> [! NOTA] El nombre de la operación se define como parte de la dirección URL del servicio web; además, los mensajes de solicitud no siempre se envían a través de JSON. Los servicios web pueden usar el atributo ScriptMethod con el parámetro UseHttpGet establecido en true, lo que hace que los parámetros se pasen a través de los parámetros de cadena de consulta.

Lista 3. Mensaje de respuesta del servicio web serializado en JSON

[{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Maison
     Dewey","CustomerID":"MAISD","ContactName":"Catherine
     Dewey"},{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Suprêmes
     délices","CustomerID":"SUPRD","ContactName":"Pascale
     Cartrain"}]

En la sección siguiente, verá cómo crear servicios web capaces de controlar los mensajes de solicitud JSON y responder con tipos simples y complejos.

Creación de servicios web habilitados para AJAX

ASP.NET AJAX Framework proporciona varias maneras diferentes de llamar a servicios web. Puede usar el control AutoCompleteExtender (disponible en el kit de herramientas de AJAX de ASP.NET) o JavaScript. Sin embargo, antes de llamar a un servicio, tiene que habilitarlo compatible con AJAX para que el código de script de cliente pueda llamarlo.

Tanto si no está familiarizado con servicios web ASP.NET, le resultará sencillo crear servicios y habilitarlos compatibles con AJAX. .NET Framework ha admitido la creación de servicios web ASP.NET desde su versión inicial en 2002 y las extensiones ASP.NET AJAX proporcionan funcionalidad de AJAX adicional que se basa en el conjunto predeterminado de características de .NET Framework. Visual Studio .NET 2008 Beta 2 tiene compatibilidad integrada para crear archivos de servicio web .asmx y deriva automáticamente código asociado junto a clases de la clase System.Web.Services.WebService. A medida que agregue métodos a la clase, debe aplicar el atributo WebMethod para que los consumidores del servicio web los llamen.

La lista 4 muestra un ejemplo de aplicación del atributo WebMethod a un método denominado GetCustomersByCountry().

Lista 4. Uso del atributo WebMethod en un servicio web

[WebMethod]
public Customer[] GetCustomersByCountry(string country)
{
     return Biz.BAL.GetCustomersByCountry(country);
}

El método GetCustomersByCountry() acepta un parámetro de país y devuelve una matriz de objetos Customer. El valor de país pasado al método se reenvía a una clase de capa empresarial que, a su vez, llama a una clase de capa de datos para recuperar los datos desde la base de datos, rellenar las propiedades del objeto Customer con datos y devolver la matriz.

Uso del atributo ScriptService

Al agregar el atributo WebMethod permite que los clientes llamen al método GetCustomersByCountry() que envían mensajes SOAP estándar al servicio web, no permite realizar llamadas JSON desde aplicaciones ASP.NET AJAX de fábrica. Para permitir que se realicen llamadas JSON, debe aplicar el atributo ScriptService de la extensión ASP.NET AJAX a la clase de servicio web. Esto permite a un servicio web enviar mensajes de respuesta con formato JSON y permite que el script del lado cliente llame a un servicio mediante el envío de mensajes JSON.

La lista 5 muestra un ejemplo de cómo aplicar el atributo ScriptService a una clase de servicio web denominada CustomersService.

Lista 5. Uso del atributo ScriptService para habilitar un servicio web compatible con AJAX

[System.Web.Script.Services.ScriptService]
[WebService(Namespace = "http://xmlforasp.net")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class CustomersService : System.Web.Services.WebService
{
     [WebMethod]
     public Customer[] GetCustomersByCountry(string country)
     {
          return Biz.BAL.GetCustomersByCountry(country);
     }
}

El atributo ScriptService actúa como un marcador que indica que se puede llamar desde el código de script de AJAX. En realidad, no controla ninguna de las tareas de serialización o deserialización de JSON que se producen en segundo plano. ScriptHandlerFactory (configurado en web.config) y otras clases relacionadas realizan la mayor parte del procesamiento JSON.

Uso del atributo ScriptMethod

El atributo ScriptService es el único ASP.NET atributo AJAX que debe definirse en un servicio web .NET para que lo usen páginas ASP.NET AJAX. Sin embargo, otro atributo denominado ScriptMethod también se puede aplicar directamente a métodos web en un servicio. ScriptMethod define tres propiedades, como UseHttpGet, ResponseFormat y XmlSerializeString. Cambiar los valores de estas propiedades puede ser útil en los casos en los que el tipo de solicitud aceptado por un método web debe cambiarse a GET, cuando un método web necesita devolver datos XML sin procesar en forma de un objeto XmlDocument o XmlElement o cuando los datos devueltos desde un servicio siempre deben serializarse como XML en lugar de JSON.

La propiedad UseHttpGet se puede usar cuando un método web debe aceptar solicitudes GET en lugar de solicitudes POST. Las solicitudes se envían mediante una dirección URL con parámetros de entrada del método web convertidos en parámetros QueryString. La propiedad UseHttpGet tiene como valor predeterminado false y solo debe establecerse en true cuando se sabe que las operaciones son seguras y cuando no se pasan datos confidenciales a un servicio web. En la lista 6 se muestra un ejemplo del uso del atributo ScriptMethod con la propiedad UseHttpGet.

Lista 6. Uso del atributo ScriptMethod con la propiedad UseHttpGet.

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
public string HttpGetEcho(string input)
{
     return input;
}

A continuación se muestra un ejemplo de los encabezados enviados cuando se muestra el método web HttpGetEcho que se muestra en la lista 6:

GET /CustomerViewer/DemoService.asmx/HttpGetEcho?input=%22Input Value%22 HTTP/1.1

Además de permitir que los métodos web acepten solicitudes HTTP GET, el atributo ScriptMethod también se puede usar cuando es necesario devolver respuestas XML desde un servicio en lugar de JSON. Por ejemplo, un servicio web puede recuperar una fuente RSS de un sitio remoto y devolverla como un objeto XmlDocument o XmlElement. Después, el procesamiento de los datos XML se puede producir en el cliente.

La lista 7 muestra un ejemplo del uso de la propiedad ResponseFormat para especificar que los datos XML deben devolverse desde un método web.

Lista 7. Usar el atributo ScriptMethod con la propiedad ResponseFormat.

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
public XmlElement GetRssFeed(string url)
{
     XmlDocument doc = new XmlDocument();
     doc.Load(url);
     return doc.DocumentElement;
}

La propiedad ResponseFormat también se puede usar junto con la propiedad XmlSerializeString. La propiedad XmlSerializeString tiene un valor predeterminado de false, lo que significa que todos los tipos devueltos excepto las cadenas devueltas de un método web se serializan como XML cuando la propiedad ResponseFormat está establecida en ResponseFormat.Xml. Cuando XmlSerializeString se establece en true, todos los tipos devueltos desde un método web se serializan como XML, incluidos los tipos de cadena. Si la propiedad ResponseFormat tiene un valor de ResponseFormat.Json se omite la propiedad XmlSerializeString.

La lista 8 muestra un ejemplo del uso de la propiedad XmlSerializeString para forzar la serialización de cadenas como XML.

Lista 8. Uso del atributo ScriptMethod con la propiedad XmlSerializeString

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml,XmlSerializeString=true)]
public string GetXmlString(string input)
{
     return input;
}

El valor devuelto de llamar al método web GetXmlString que se muestra en la lista 8 se muestra a continuación:

<?xml version="1.0"?>
     <string>Test</string>

Aunque el formato JSON predeterminado minimiza el tamaño general de los mensajes de solicitud y respuesta y es más consumido por clientes de ASP.NET AJAX de forma cruzada, las propiedades ResponseFormat y XmlSerializeString se pueden usar cuando las aplicaciones cliente, como Internet Explorer 5 o versiones posteriores, esperan que se devuelvan datos XML desde un método web.

Trabajar con tipos complejos

La lista 5 mostró un ejemplo de devolución de un tipo complejo denominado Customer desde un servicio web. La clase Customer define varios tipos simples diferentes internamente como propiedades como FirstName y LastName. Los tipos complejos que se usan como parámetro de entrada o tipo de valor devuelto en un método web compatible con AJAX se serializan automáticamente en JSON antes de enviarse al lado cliente. Sin embargo, los tipos complejos anidados (los definidos internamente dentro de otro tipo) no están disponibles para el cliente como objetos independientes de forma predeterminada.

En los casos en los que un tipo complejo anidado usado por un servicio web también debe usarse en una página de cliente, el atributo ASP.NET AJAX GenerateScriptType se puede agregar al servicio web. Por ejemplo, la clase CustomerDetails que se muestra en La lista 9 contiene propiedades Address y Gender que representan tipos complejos anidados.

Lista 9. La clase CustomerDetails que se muestra aquí contiene dos tipos complejos anidados.

public class CustomerDetails : Customer
{
     public CustomerDetails()
     {
     }
     Address _Address;
     Gender _Gender = Gender.Unknown;
     public Address Address
     {
          get { return _Address; }
          set { _Address = value; }
     }
     public Gender Gender
     {
          get { return _Gender; }
          set { _Gender = value; }
     }
}

Los objetos Address y Gender definidos dentro de la clase CustomerDetails que se muestra en la lista 9 no estarán disponibles automáticamente para su uso en el lado cliente a través de JavaScript, ya que son tipos anidados (Address es una clase y Gender es una enumeración). En situaciones en las que un tipo anidado usado en un servicio web debe estar disponible en el lado cliente, se puede usar el atributo GenerateScriptType mencionado anteriormente (vea la Lista 10). Este atributo se puede agregar varias veces en casos en los que se devuelven distintos tipos complejos anidados desde un servicio. Se puede aplicar directamente a la clase de servicio web o a métodos web específicos anteriores.

Lista 10. Usar el atributo GenerateScriptService para definir tipos anidados que deben estar disponibles para el cliente.

[System.Web.Script.Services.ScriptService]
[System.Web.Script.Services.GenerateScriptType(typeof(Address))]
[System.Web.Script.Services.GenerateScriptType(typeof(Gender))]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class NestedComplexTypeService : System.Web.Services.WebService
{
     //Web Methods
}

Al aplicar el GenerateScriptType atributo al servicio web, los tipos Address y Gender estarán disponibles automáticamente para usarse por el código ASP.NET AJAX JavaScript por parte del cliente. En la lista 11 se muestra un ejemplo de JavaScript que se genera automáticamente y se envía al cliente agregando el atributo GenerateScriptType en un servicio web. Verá cómo usar tipos complejos anidados más adelante en el artículo.

Lista 11. Tipos complejos anidados disponibles para una página de ASP.NET AJAX.

if (typeof(Model.Address) === 'undefined')
{
     Model.Address=gtc("Model.Address");
     Model.Address.registerClass('Model.Address');
}
Model.Gender = function() { throw Error.invalidOperation(); }
Model.Gender.prototype = {Unknown: 0,Male: 1,Female: 2}
Model.Gender.registerEnum('Model.Gender', true);

Ahora que ha visto cómo crear servicios web y hacer que sean accesibles para páginas de ASP.NET AJAX, echemos un vistazo a cómo crear y usar servidores proxy de JavaScript para que los datos se puedan recuperar o enviar a servicios web.

Creación de servidores proxy de JavaScript

Llamar a un servicio web estándar (.NET u otra plataforma) suele implicar la creación de un objeto proxy que le protege de las complejidades del envío de mensajes de solicitud y respuesta SOAP. Con las llamadas a servicio web de ASP.NET AJAX, los servidores proxy de JavaScript se pueden crear y usar para llamar fácilmente a los servicios sin preocuparse por serializar y deserializar mensajes JSON. Los servidores proxy de JavaScript se pueden generar automáticamente mediante el control ASP.NET AJAX ScriptManager.

La creación de un proxy de JavaScript que pueda llamar a servicios web se realiza mediante la propiedad de los servicios de ScriptManager. Esta propiedad permite definir uno o varios servicios a los que una página ASP.NET AJAX puede llamar de forma asincrónica para enviar o recibir datos sin necesidad de operaciones de postback. Se define un servicio mediante el control ASP.NET AJAX ServiceReference y se asigna la dirección URL del servicio web a la propiedad Path del control. La lista 12 muestra un ejemplo de hacer referencia a un servicio denominado CustomersService.asmx.

<asp:ScriptManager ID="ScriptManager1" runat="server">
     <Services>
          <asp:ServiceReference Path="~/CustomersService.asmx" />
     </Services>
</asp:ScriptManager>

Lista 12. Definir un servicio web usado en una página de ASP.NET AJAX.

Agregar una referencia a CustomersService.asmx a través del control ScriptManager hace que el proxy de JavaScript se genere dinámicamente y haga referencia a ellos mediante la página. El proxy se inserta mediante la etiqueta de <script> y se carga dinámicamente llamando al archivo CustomersService.asmx y anexando /js al final. En el ejemplo siguiente se muestra cómo se inserta el proxy de JavaScript en la página cuando la depuración está deshabilitada en web.config:

<script src="CustomersService.asmx/js" type="text/javascript"></script>

> [! NOTA] Si desea ver el código proxy de JavaScript real que se genera, puede escribir la dirección URL en el servicio web de .NET deseado en el cuadro de dirección de Internet Explorer y anexar /js al final.

Si la depuración está habilitada en web.config, se insertará una versión de depuración del proxy de JavaScript en la página como se muestra a continuación:

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

El proxy de JavaScript creado por ScriptManager también se puede incrustar directamente en la página en lugar de hacer referencia mediante el atributo src de la etiqueta de <script>. Esto se puede hacer estableciendo la propiedad InlineScript del control ServiceReference en true (el valor predeterminado es false). Esto puede ser útil cuando un proxy no se comparte entre varias páginas y cuando desea reducir el número de las llamadas de red realizadas al servidor. Cuando InlineScript se establece en true, el explorador no almacenará en caché el script de proxy, por lo que se recomienda el valor predeterminado de false en los casos en los que varias páginas usen el proxy en una aplicación ASP.NET AJAX. A continuación se muestra un ejemplo de uso de la propiedad InlineScript:

<asp:ServiceReference InlineScript="true" Path="~/CustomersService.asmx"/>

Uso de servidores proxy de JavaScript

Una vez que se hace referencia a un servicio web por una página de ASP.NET AJAX mediante el control ScriptManager, se puede realizar una llamada al servicio web y los datos devueltos se pueden controlar mediante funciones de devolución de llamada. Se llama a un servicio web haciendo referencia a su espacio de nombres (si existe uno), el nombre de la clase y el nombre del método web. Los parámetros pasados al servicio web se pueden definir junto con una función de devolución de llamada que controla los datos devueltos.

En la Lista 13 se muestra un ejemplo de uso de un proxy de JavaScript para llamar a un método web denominado GetCustomersByCountry(). Se llama a la función GetCustomersByCountry() cuando un usuario final hace clic en un botón de la página.

Lista 13. Llamar a un servicio web con un proxy de JavaScript.

function GetCustomerByCountry()
{
     var country = $get("txtCountry").value;
     InterfaceTraining.CustomersService.GetCustomersByCountry(country, OnWSRequestComplete);
}
function OnWSRequestComplete(results)
{
     if (results != null)
     {
          CreateCustomersTable(results);
          GetMap(results);
     }
}

Esta llamada hace referencia al espacio de nombres InterfaceTraining, la clase CustomersService y el método web GetCustomersByCountry definidos en el servicio. Pasa un valor de país obtenido de un cuadro de texto, así como una función de devolución de llamada denominada OnWSRequestComplete que se debe invocar cuando se devuelve la llamada asincrónica del servicio web. OnWSRequestComplete controla la matriz de objetos Customer devueltos desde el servicio y los convierte en una tabla que se muestra en la página. La salida generada a partir de la llamada se muestra en la figura 1.

Binding data obtained by making an asynchronous AJAX call to a Web Service.

Figura 1: Enlace de datos obtenidos mediante la realización de una llamada asincrónica de AJAX a un servicio web. (Haga clic para ver la imagen a tamaño completo.)

Los servidores proxy de JavaScript también pueden realizar llamadas unidireccionales a servicios web en casos en los que se debe llamar a un método web, pero el proxy no debe esperar una respuesta. Por ejemplo, puede que quiera llamar a un servicio web para iniciar un proceso como un flujo de trabajo, pero no esperar un valor devuelto del servicio. En los casos en los que es necesario realizar una llamada unidireccional a un servicio, la función de devolución de llamada que se muestra en la Lista 13 simplemente se puede omitir. Dado que no se define ninguna función de devolución de llamada, el objeto proxy no esperará a que el servicio web devuelva datos.

Control de errores

Las devoluciones de llamada asincrónicas a servicios web pueden encontrar diferentes tipos de errores, como la red que está inactiva, el servicio web no está disponible o se devuelve una excepción. Afortunadamente, los objetos proxy de JavaScript generados por ScriptManager permiten definir varias devoluciones de llamada para controlar errores y errores, además de la devolución de llamada correcta mostrada anteriormente. Una función de devolución de llamada de error se puede definir inmediatamente después de la función de devolución de llamada estándar en la llamada al método web, como se muestra en la Lista 14.

Lista 14. Definir una función de devolución de llamada de error y mostrar errores.

function GetCustomersByCountry() 
{
     var country = $get("txtCountry").value;
     InterfaceTraining.CustomersService.GetCustomersByCountry(country, 
          OnWSRequestComplete, OnWSRequestFailed);
}
function OnWSRequestFailed(error)
{
     alert("Stack Trace: " + error.get_stackTrace() + "/r/n" +
          "Error: " + error.get_message() + "/r/n" +
          "Status Code: " + error.get_statusCode() + "/r/n" +
          "Exception Type: " + error.get_exceptionType() + "/r/n" +
          "Timed Out: " + error.get_timedOut());
}

Los errores que se producen cuando se llama al servicio web desencadenarán la función de devolución de llamada OnWSRequestFailed() a la que se llamará, que acepta un objeto que representa el error como parámetro. El objeto de error expone varias funciones diferentes para determinar la causa del error, así como si se agota o no el tiempo de espera de la llamada. La lista 14 muestra un ejemplo de uso de las distintas funciones de error y la figura 2 muestra un ejemplo de la salida generada por las funciones.

Output generated by calling ASP.NET AJAX error functions.

figura 2: salida generada llamando a funciones de error de ASP.NET AJAX. (Haga clic para ver la imagen a tamaño completo.)

Control de datos XML devueltos desde un servicio web

Anteriormente vio cómo un método web podría devolver datos XML sin procesar mediante el atributo ScriptMethod junto con su propiedad ResponseFormat. Cuando ResponseFormat se establece en ResponseFormat.Xml, los datos devueltos desde el servicio web se serializan como XML en lugar de JSON. Esto puede ser útil cuando los datos XML deben pasarse directamente al cliente para su procesamiento mediante JavaScript o XSLT. En el momento actual, Internet Explorer 5 o superior proporciona el mejor modelo de objetos del lado cliente para analizar y filtrar datos XML debido a su compatibilidad integrada con MSXML.

Recuperar datos XML de un servicio web no es diferente de recuperar otros tipos de datos. Empiece invocando el proxy de JavaScript para llamar a la función adecuada y definir una función de devolución de llamada. Una vez que la llamada devuelva, puede procesar los datos en la función de devolución de llamada.

La lista 15 muestra un ejemplo de llamada a un método web denominado GetRssFeed() que devuelve un objeto XmlElement. GetRssFeed() acepta un único parámetro que representa la dirección URL de la fuente RSS que se va a recuperar.

Lista 15. Trabajar con datos XML devueltos desde un servicio web.

function GetRss()
{
     InterfaceTraining.DemoService.GetRssFeed(
          "https://blogs.interfacett.com/dan-wahlins-blog/rss.xml",
          OnWSRequestComplete);
}
function OnWSRequestComplete(result)
{
     if (document.all) //Filter for IE DOM since other browsers are limited
     {
          var items = result.selectNodes("//item");
          for (var i=0;i<items.length;i++)
          {
               var title = items[i].selectSingleNode("title").text;
               var href = items[i].selectSingleNode("link").text;
               $get("divOutput").innerHTML +=
               "<a href='" + href + "'>" + title + "</a><br/>";
          }
     }
     else
     {
          $get("divOutput").innerHTML = "RSS only available in IE5+";
     }
}

En este ejemplo se pasa una dirección URL a una fuente RSS y se procesan los datos XML devueltos en la función OnWSRequestComplete(). OnWSRequestComplete() comprueba primero si el explorador es Internet Explorer para saber si el analizador MSXML está disponible o no. Si es así, se usa una instrucción XPath para buscar todas las etiquetas <elemento> dentro de la fuente RSS. Cada elemento se recorre en iteración y las etiquetas de <título> y <vínculo> asociadas se encuentran y se procesan para mostrar los datos de cada elemento. En la figura 3 se muestra un ejemplo de la salida generada a partir de la realización de una llamada AJAX de ASP.NET a través de un proxy de JavaScript al método web GetRssFeed().

Control de tipos complejos

Los tipos complejos aceptados o devueltos por un servicio web se exponen automáticamente a través de un proxy de JavaScript. Sin embargo, los tipos complejos anidados no son accesibles directamente en el lado cliente a menos que el atributo GenerateScriptType se aplique al servicio como se explicó anteriormente. ¿Por qué desea usar un tipo complejo anidado en el lado cliente?

Para responder a esta pregunta, supongamos que una página de ASP.NET AJAX muestra los datos de los clientes y permite a los usuarios finales actualizar la dirección de un cliente. Si el servicio web especifica que el tipo de dirección (un tipo complejo definido dentro de una clase CustomerDetails) se puede enviar al cliente, el proceso de actualización se puede dividir en funciones independientes para mejorar el uso del código.

Output creating from calling a Web Service that returns RSS data.

Figura 3: salida que crea a partir de la llamada a un servicio web que devuelve datos RSS. (Haga clic para ver la imagen a tamaño completo.)

La Lista 16 muestra un ejemplo de código del lado cliente que invoca un objeto Address definido en un espacio de nombres Model, lo rellena con datos actualizados y lo asigna a una propiedad Address del objeto CustomerDetails. A continuación, el objeto CustomerDetails se pasa al servicio web para su procesamiento.

Lista 16. Uso de tipos complejos anidados

function UpdateAddress()
{
     var cust = new Model.CustomerDetails();
     cust.CustomerID = $get("hidCustomerID").value;
     cust.Address = CreateAddress();
     InterfaceTraining.DemoService.UpdateAddress(cust,OnWSUpdateComplete);
}
function CreateAddress()
{
     var addr = new Model.Address();
     addr.Street = $get("txtStreet").value;
     addr.City = $get("txtCity").value;
     addr.State = $get("txtState").value;
     return addr;
}
function OnWSUpdateComplete(result)
{
     alert("Update " + ((result)?"succeeded":"failed")+ "!");
}

Crear y usar métodos de página

Los servicios web proporcionan una excelente manera de exponer los servicios reutilizables a una variedad de clientes, incluidas páginas de ASP.NET AJAX. Sin embargo, puede haber casos en los que una página necesite recuperar datos que nunca se usarán ni compartirán en otras páginas. En este caso, la creación de un archivo .asmx para permitir que la página acceda a los datos puede parecer excesiva, ya que el servicio solo lo usa una sola página.

ASP.NET AJAX proporciona otro mecanismo para realizar llamadas de tipo servicio web sin crear archivos .asmx independientes. Esto se hace mediante una técnica denominada "métodos de página". Los métodos page son métodos estáticos (compartidos en VB.NET) incrustados directamente en una página o un archivo de código que tienen aplicado el atributo WebMethod. Al aplicar el atributo WebMethod, se puede llamar mediante un objeto de JavaScript especial denominado PageMethods que se crea dinámicamente en tiempo de ejecución. El objeto PageMethods actúa como proxy que le protege del proceso de serialización/deserialización JSON. Tenga en cuenta que para usar el objeto PageMethods debe establecer la propiedad EnablePageMethods de ScriptManager en true.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
</asp:ScriptManager>

En la Lista 17 se muestra un ejemplo de definición de dos métodos de página en un ASP.NET clase de código al lado. Estos métodos recuperan datos de una clase de capa empresarial ubicada en la carpeta App_Code del sitio web.

Lista 17. Definir métodos de página.

[WebMethod]
public static Customer[] GetCustomersByCountry(string country)
{
     return Biz.BAL.GetCustomersByCountry(country);
}
[WebMethod]
public static Customer[] GetCustomersByID(string id)
{
     return Biz.BAL.GetCustomersByID(id);
}

Cuando ScriptManager detecta la presencia de métodos web en la página, genera una referencia dinámica al objeto PageMethods mencionado anteriormente. La llamada a un método web se realiza haciendo referencia a la clase PageMethods seguida del nombre del método y los datos de parámetro necesarios que se deben pasar. En la lista 18 se muestran ejemplos de llamar a los dos métodos de página mostrados anteriormente.

Lista 18. Llamar a métodos de página con el objeto JavaScript PageMethods.

function GetCustomerByCountry() 
{
     var country = $get("txtCountry").value;
     PageMethods.GetCustomersByCountry(country, OnWSRequestComplete);
}
function GetCustomerByID() 
{
     var custID = $get("txtCustomerID").value;
     PageMethods.GetCustomersByID(custID, OnWSRequestComplete);
}
function OnWSRequestComplete(results) 
{
     var searchResults = $get("searchResults");
     searchResults.control.set_data(results);
     if (results != null) GetMap(results[0].Country,results);
}

El uso del objeto PageMethods es muy similar al uso de un objeto proxy de JavaScript. Primero se especifican todos los datos de parámetro que se deben pasar al método page y, a continuación, se define la función de devolución de llamada a la que se debe llamar cuando se devuelve la llamada asincrónica. También se puede especificar una devolución de llamada de error (consulte la lista 14 para obtener un ejemplo de control de errores).

AutoCompleteExtender y el kit de herramientas de ASP.NET AJAX

El kit de herramientas de ASP.NET AJAX (disponible en https://www.devexpress.com/Products/AJAX-Control-Toolkit) ofrece varios controles que se pueden usar para acceder a los servicios web. En concreto, el kit de herramientas contiene un control útil denominado AutoCompleteExtender que se puede usar para llamar a servicios web y mostrar datos en páginas sin escribir ningún código JavaScript en absoluto.

El control AutoCompleteExtender se puede usar para ampliar la funcionalidad existente de un cuadro de texto y ayudar a los usuarios a localizar más fácilmente los datos que buscan. A medida que escriben en un cuadro de texto, el control se puede usar para consultar un servicio web y muestra los resultados debajo del cuadro de texto dinámicamente. En la Figura 4 se muestra un ejemplo del uso del control AutoCompleteExtender para mostrar los identificadores de cliente de una aplicación de soporte técnico. A medida que el usuario escribe caracteres diferentes en el cuadro de texto, los distintos elementos se mostrarán debajo en función de su entrada. A continuación, los usuarios pueden seleccionar el identificador de cliente deseado.

El uso de AutoCompleteExtender en una página de ASP.NET AJAX requiere que el ensamblado AjaxControlToolkit.dll se agregue a la carpeta de rango del sitio web. Una vez agregado el ensamblado del kit de herramientas, querrá hacer referencia a él en web.config para que los controles que contiene estén disponibles para todas las páginas de una aplicación. Para ello, agregue la siguiente etiqueta dentro de la etiqueta de <controles> de web.config:

<add namespace="AjaxControlToolkit" assembly="AjaxControlToolkit" tagPrefix="ajaxToolkit"/>

En los casos en los que solo necesite usar el control en una página específica, puede hacer referencia a él agregando la directiva Reference a la parte superior de una página, como se muestra a continuación, en lugar de actualizar web.config:

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" 
     TagPrefix="ajaxToolkit" %>

Using the AutoCompleteExtender control.

Figura 4: Uso del control AutoCompleteExtender. (Haga clic para ver la imagen a tamaño completo.)

Una vez configurado el sitio web para usar el kit de herramientas de ASP.NET AJAX, se puede agregar un control de AutoCompleteExtender a la página de forma muy similar a la que agregaría un control de servidor ASP.NET normal. En la Lista 19 se muestra un ejemplo del uso del control para llamar a un servicio web.

Lista 19. Con el control AutoCompleteExtender del kit de herramientas de ASP.NET AJAX.

<ajaxToolkit:AutoCompleteExtender ID="extTxtCustomerID" runat="server"
     MinimumPrefixLength="1" ServiceMethod="GetCustomerIDs"
     ServicePath="~/CustomersService.asmx"
     TargetControlID="txtCustomerID" />

AutoCompleteExtender tiene varias propiedades diferentes, como el identificador estándar y las propiedades runat que se encuentran en los controles de servidor. Además de esto, permite definir cuántos caracteres escribe un usuario final antes de consultar el servicio web para los datos. La propiedad MinimumPrefixLength que se muestra en la Lista 19 hace que se llame al servicio cada vez que se escribe un carácter en el cuadro de texto. Querrá tener cuidado al establecer este valor, ya que cada vez que el usuario escribe un carácter, se llamará al servicio web para buscar valores que coincidan con los caracteres del cuadro de texto. El servicio web al que se llamará, así como el método web de destino, se definen mediante las propiedades ServicePath y ServiceMethod, respectivamente. Por último, la propiedad TargetControlID identifica el cuadro de texto al que enlazar el control AutoCompleteExtender.

El servicio web al que se llama debe tener aplicado el atributo ScriptService como se ha descrito anteriormente y el método web de destino debe aceptar dos parámetros denominados prefixText y count. El parámetro prefixText representa los caracteres tipados por el usuario final y el parámetro count representa el número de elementos que se van a devolver (el valor predeterminado es 10). La Lista 20 muestra un ejemplo del método web GetCustomerIDs llamado por el control AutoCompleteExtender mostrado anteriormente en la Lista 19. El método web llama a un método de capa de negocio que, a su vez, llama a un método de capa de datos que controla el filtrado de los datos y devuelve los resultados coincidentes. El código del método de capa de datos se muestra en la Lista 21.

Lista 20. Filtrado de datos enviados desde el control AutoCompleteExtender.

[WebMethod]
public string[] GetCustomerIDs(string prefixText, int count) 
{
     return Biz.BAL.GetCustomerIDs(prefixText, count);
}

Listado 21. Filtrado de los resultados en función de la entrada del usuario final.

public static string[] GetCustomerIDs(string prefixText, int count)
{
     //Customer IDs cached in _CustomerIDs field to improve performance
     if (_CustomerIDs == null)
     {
          List<string> ids = new List<string>();
          //SQL text used for simplicity...recommend using sprocs
          string sql = "SELECT CustomerID FROM Customers";
          DbConnection conn = GetDBConnection();
          conn.Open();
          DbCommand cmd = conn.CreateCommand();
          cmd.CommandText = sql;
          DbDataReader reader = cmd.ExecuteReader();
          while (reader.Read())
          {
               ids.Add(reader["CustomerID"].ToString());
          }
          reader.Close();
          conn.Close();
          _CustomerIDs = ids.ToArray();
     }
     int index = Array.BinarySearch(_CustomerIDs, prefixText, new CaseInsensitiveComparer());
     //~ is bitwise complement (reverse each bit)
     if (index < 0) index = ~index;
     int matchingCount;
     for (matchingCount = 0; matchingCount < count && index + matchingCount < _CustomerIDs.Length; matchingCount++)
     {
          if (!_CustomerIDs[index + matchingCount].StartsWith(prefixText, StringComparison.CurrentCultureIgnoreCase))
          {
               break;
          }
     }
     String[] returnValue = new string[matchingCount];
     if (matchingCount > 0)
     {
          Array.Copy(_CustomerIDs, index, returnValue, 0, matchingCount);
     }
     return returnValue;
}

Conclusión

ASP.NET AJAX proporciona una excelente compatibilidad para llamar a servicios web sin escribir mucho código JavaScript personalizado para controlar los mensajes de solicitud y respuesta. En este artículo ha visto cómo habilitar los servicios web de .NET para AJAX para permitirles procesar mensajes JSON y cómo definir servidores proxy de JavaScript mediante el control ScriptManager. También ha visto cómo se pueden usar servidores proxy de JavaScript para llamar a servicios web, controlar tipos simples y complejos y tratar los errores. Por último, ha visto cómo se pueden usar los métodos de página para simplificar el proceso de creación y realización de llamadas de servicio web y cómo el control AutoCompleteExtender puede proporcionar ayuda a los usuarios finales a medida que escriben. Aunque UpdatePanel disponible en ASP.NET AJAX ciertamente será el control de elección para muchos programadores de AJAX debido a su simplicidad, saber cómo llamar a servicios web a través de servidores proxy de JavaScript puede ser útil en muchas aplicaciones.

Biografía

Dan Wahlin (Profesional más valioso de Microsoft para ASP.NET y servicios web XML) es instructor de desarrollo .NET y consultor de arquitectura en entrenamiento técnico de interfaz (http://www.interfacett.com). Dan fundó el sitio web XML para desarrolladores ASP.NET (www.XMLforASP.NET), forma parte de la oficina de ponentes de INETA y participa como ponente en varias conferencias. Dan es coautor de Professional Windows DNA (Wrox), ASP.NET: Tips, Tutorials and Code (Sams), ASP.NET 1.1 Insider Solutions, Professional ASP.NET 2.0 AJAX (Wrox), ASP.NET 2.0 MVP Hacks y es autor de XML for ASP.NET Developers (Sams). Cuando no está escribiendo código, artículos o libros, Dan disfruta componiendo y grabando música y jugando al golf y al baloncesto con su mujer y sus hijos.

Scott Cate ha trabajado con las tecnologías web de Microsoft desde 1997 y es el presidente de myKB.com (www.myKB.com) donde se especializa en escribir aplicaciones ASP.NET basadas en soluciones de software de Knowledge Base. Puede ponerse en contacto con Scott por correo electrónico en scott.cate@myKB.com o en su blog, ScottCate.com