Retornar un DataSet desde métodos de página (PageMethods) con ASP.Net Ajax

Hace unos días Rosario C. realizaba, a través de un comentario en el post "Llamar a métodos estáticos con ASP.Net Ajax", una consulta sobre un problema con el se había topado al intentar retornar DataSets desde un método de página (PageMethod) de ASP.Net Ajax, un tema tan interesante que vale la pena escribir un post en exclusiva.

Recordemos que los métodos estáticos de página son una interesante capacidad que nos ofrece este framework para poder invocar desde cliente (javascript) funciones de servidor (codebehind) de una forma realmente sencilla. Además, gracias a los mecanismos de seriación incluidos, y como ya vimos en el post "Usando ASP.NET AJAX para el intercambio de entidades de datos", es perfectamente posible devolver desde servidor entidades o estructuras de datos complejas, y obtenerlas y procesarlas directamente desde cliente utilizando javascript, y viceversa.

Es es ahí donde reside el problema con los DataSets: precisamente este tipo de datos no está soportado directamente por el seriador JSON incorporado, que es el utilizado por defecto, de ahí que se genere una excepción como la que sigue:

System.InvalidOperationException
A circular reference was detected while serializing an object of type 'System.Globalization.CultureInfo'.
en System.Web.Script.Serialization.JavaScriptSerializer.SerializeValueInternal(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat serializationFormat)\r\n en System.Web.Script.Serialization.JavaScriptSerializer.SerializeValue(Object o, StringBuilder sb, Int32 depth, Hashtable objectsInUse, SerializationFormat [...]

Pero antes de nada, un inciso. Dado que la excepción se produce en el momento de la seriación, justo antes de enviar los datos de vuelta al cliente, debe ser capturada en cliente vía la función callback especificada como último parámetro en la llamada al método del servidor:

  // Llamada a nuestro PageMethod
PageMethods.GetData(param, OnOK, OnError);
[...]


function OnError(msg)
{
// msg es de la clase WebServiceError.
alert("Error: " + msg.get_message());
}

 Aclarado esto, continuamos con el tema. La buena noticia es que el soporte para DataSets estará incluido en futuras versiones de la plataforma. De hecho, es perfectamente posible utilizar las librerías disponibles en previews disponibles en la actualidad. Existen multitud de ejemplos en la red sobre cómo es posible realizar esto, aunque será necesario instalar alguna CTP y referenciar librerías en el Web.config. Podéis echar un vistazo a lo descrito en este foro.

Esta solución sin embargo no es muy recomendable en estos momentos, puesto que estaríamos introduciendo en un proyecto librerías y componentes que, en primer lugar, están ideados sólo para probar y tomar contacto con las nuevas tecnologías, y, en segundo lugar, las características presentadas en ellos pueden modificarse o incluso eliminarse de las futuras versiones.

Existen otras formas de hacerlo, como la descrita en siderite, que consiste en crear un conversor justo a la medida de nuestras necesidades extendiendo la clase JavaScriptConverter, aunque aún me parecía una salida demasiado compleja al problema.

Sin embargo, la solución que he visto más simple es utilizar XML para la seriación de estas entidades, lo que se puede lograr de una forma muy sencilla añadiendo al método a utilizar un atributo modificando el formato de respuesta utilizado por defecto, de JSON a XML.

He creado un proyecto de demostración para VS2005, esta vez en VB.NET aunque la traducción a C# es directa, con una página llamada DataSetPageMethods.aspx en el que se establece un PageMethod en el lado del servidor, con la siguiente signatura:


<WebMethod()> _
<Script.Services.ScriptMethod(ResponseFormat:=ResponseFormat.Xml)> _
Public Shared Function ObtenerClientes(ByVal ciudad As String) As DataSet


Este método estático obtendrá de la tradicional base de datos NorthWind (en italiano, eso sí, no tenía otra a mano O:-)) un DataSet con los clientes asociados a la ciudad cuyo nombre se recibe como parámetro.

Como podréis observar, el método está precedido por la declaración de dos atributos. El primero de ellos, WebMethod(), lo define como un PageMethod y lo hará visible desde cliente de forma directa. El segundo de ellos redefine su mecanismo de seriación por defecto, pasándolo a XML. Si queréis ver el error al que hacía referencia al principio, podéis modificar el valor del ResponseFormat a JSON, su valor por defecto.

Desde cliente, al cargar la página rellenamos un desplegable con las distintas ciudades. Cuando el usuario selecciona un elemento y pulsa el botón "¡Pulsa!", se realizará una llamada asíncrona al PageMethod, según el código:

function llamar()
{
PageMethods.ObtenerClientes($get("<%= DropDownList1.ClientID %>").value , OnOK, OnError);
}


La función callback de notificación de finalización de la operación, OnOk, recibirá el DataSet del servidor y mostrará en la página dos resúmenes de los datos obtenidos que describiré a continuación. He querido hacerlo así para demostrar dos posibles alternativas a la hora de procesar desde cliente los datos recibidos, el DataSet, desde servidor.

En el primer ejemplo, se crea en cliente una lista sin orden con los nombres de las personas de contacto de los clientes (valga la redundancia ;-)), donde muestro cómo es posible utilizar el DOM para realizar recorridos sobre el conjunto de datos:

 var nds = data.documentElement.getElementsByTagName("NewDataSet");
var rows = nds[0].getElementsByTagName("Table");
var st = rows.length + " filas. Personas:  br />";
st += "<ul>";
for(var i = 0; i < rows.length; i++)
{
st += "<li>" + getText(rows[ i ].getElementsByTagName("Contatto")[ 0 ])+ "</li>";
}
st += "</ul>";
etiqueta.innerHTML = st;


En el segundo ejemplo se muestra otra posibilidad, la utilización de DOM para crear una función recursiva que recorra en profundidad la estructura XML mostrando la información contenida. La función javascript que realiza esta tarea se llama procesaXml.

Como se puede comprobar analizando el código, utilizar la información del DataSet en cliente es necesario, en cualquier caso, un conocimiento de la estructura interna de est tipo de datos, lo cual no es sencillo, por lo que recomendaría enviar y gestionar los DataSets en cliente sólo cuando no haya más remedio.

Una alternativa que además de resultar menos pesada en términos de ancho de banda y proceso requerido para su tratamiento es bastante más sencilla de implementar sería enviar a cliente la información incluida un un array, donde cada elemento sería del tipo de datos apropiado (por ejemplo, un array de Personas, Facturas, o lo que sea). La seriación será JSON, mucho más optimizada, además de resultar simplísimo su tratamiento en javascript.

El proyecto de demostración también incluye una página (ArrayPageMethods.aspx) donde está implementado un método que devuelve un array del tipo Datos, cuya representación se lleva también a cliente y hace muy sencillo su uso, como se puede observar en esta versión de la función de retorno OnOk:

function OnOK(datos)
{
var etiqueta = $get("lblMensajes");
var s = "<ul>";
for (var i = 0; i < datos.length; i++)
{
s += "<li>" + datos[ i ].Contacto;
s += ". Tel: " + datos[ i ].Telefono;
s += "</li>";
}
s += "</ul>";
etiqueta.innerHTML = s;
}

Por último, comentar que el proyecto de ejemplo funciona también con VS2008 (Web Developer Express), aunque hay que abrirlo como una web existente.

Publicado en: www.variablenotfound.com.



Enlace: Descargar proyecto VS2005.
Published 18/12/2007 20:22 por José M. Aguilar
Comparte este post:
http://geeks.ms/blogs/jmaguilar/archive/2007/12/18/retornar-un-dataset-desde-m-233-todos-de-p-225-gina-pagemethods-con-asp-net-ajax.aspx

Comentarios

# re: Retornar un DataSet desde métodos de página (PageMethods) con ASP.Net Ajax

buenas noches, realice todos los pasos, sin embargo al escribir <Script.Services.ScriptMethod(ResponseFormat:=ResponseFormat.Xml)> _, me da como error que responseformat.xml no esta definido, estoy trabajando con visual studio teams ystem 2008

Saturday, July 26, 2008 2:41 AM por julio

# re: Retornar un DataSet desde métodos de página (PageMethods) con ASP.Net Ajax

ya lo soluciones!!, necesitaba incluir esta linea al inicio de mi codigo: "Imports System.Web.Script.Services", ahora tengo otro problemita, me encuentro recibiendo un datareader de sql, luego cargo los datos a un datatable y devuelvo el datatable a javascript, pero me da el siguiente error:

"System.InvalidOperationException-- A circular reference was detected while serializing an object of type 'System.Reflection.Module'.", no logro solucionarlo colocando "<Script.Services.ScriptMethod(ResponseFormat:=ResponseFormat.Xml)> _" y tampoco colocandolo. ayudenme porfavor

Saturday, July 26, 2008 3:15 AM por julio

# re: Retornar un DataSet desde métodos de página (PageMethods) con ASP.Net Ajax

Hola, Julio.

¿Has probado a descargarte el proyecto de demo? Si te funciona bien, es posible que puedas pillar alguna idea que te ayude...

Saludos y gracias por comentar.

Wednesday, July 30, 2008 2:32 PM por José M. Aguilar

# re: Retornar un DataSet desde métodos de página (PageMethods) con ASP.Net Ajax

En ajaxPro se puede hacer esto:

var rpt=Pag01.Funcion_Ejecutar(par1,par2).value;

y rpt tiene lo que devuelve el servidor sin necesidas de crear una funcion de CallBack, en el MsAjax(PageMethod) tambien se puede hacer esto

Monday, August 11, 2008 9:02 PM por Ronald