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!
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.

8 Comentarios:

Eduardo dijo...

Saludos José
Agradezco mucho este codigo. Te comento que el ejemplo que enviaste trabaja perfectamente, pero cuando yo trabajo con un datos de dialogArguments de una ventana modal. No se porque no los puedo recuperar.

Solo pongo una Metodo :
[WebMethod()]
public static ProcesaDatos Campos(ProcesaDatos cps)
{
//string Datos = Convert.ToString(cps);
cps.ConfCampos = cps.ConfCampos;
return cps;
}

y una clase :
public class ProcesaDatos
{
public string ConfCampos;

}
Podrias auxiliarme; te mando mis datos : eclopez@elektra.com.mx

GRACIAS

josé M. Aguilar dijo...

Hola, Eduardo.

Te contesto por email.

Anónimo dijo...

Hola José, muchas gracias por tu labor, es un trabajo Excelente y muy interesante.

Me gustaría saber si yo podría enviar una colección de objetos ("Mensaje"), en un Arraylist, esto para procesar en el cliente y en el servidor mucha información, sin despreciar el envío de Dataset del que hablas en otro artículo.

Muchas gracias por este articulo, Felicitaciones

Anónimo dijo...

Hola Jose, nuevamente, he leido tu articulo Retornar un DataSet desde métodos de página
(PageMethods)
con ASP donde encontre la respuesta a la pregunta que te hice arriba. Gracias

josé M. Aguilar dijo...

Hola, Pedro.

Me alegro de que te haya sido útil.

Un saludo y gracias por comentar.

Pedro Chacón dijo...

Hola Jose, nuevamente.

He realizado los ejercicios paso a paso y efectivamente funcionan, pero resulta que el objeto con el que deseo trabajar llevando y trayendo datos se encuentra en un paquete , con el siguiente namespace (WebServi.AdminFach.Conjunto), WebServi es un proyecto en el cual recopilo unos servicios publicados atraves de los webservices, “Conjunto” es una clase que llega a al proyecto por medio de la referencia a uno de los webservices, la cual se vuelve local y hace parte del ensamblado “WebServi”, la clase la estoy registrando de la siguiente forma:

[WebMethod(), XmlInclude(GetType(WebServi.AdminFach.Conjunto))] _
Public Shared Function actualizarEquipos(ByVal conjuntos As WebServi.AdminFach.Conjunto()) As Boolean
Return WebServi.WebServi.adminWeb.modificarConjuntoContrato(conjuntos)
End Function

WebServi contiene el acceso a la capa de negocio, y lo que intento es registrar unos equipos que estarían contenidos dentro del arreglo de Conjunto()
Cuando el ejercicio lo hago con la declaración de la clase en el mismo proyecto como tú lo haces, funciona a la perfección, pero cuando la instancio desde un paquete como arriba lo describo, el objeto no se serializa (“eso creo”) y no lo puedo instanciar a “Conjunto”en el cliente desde javascript, pues me arroja el siguiente error “Conjunto no está definido”
Me podrías ayudar con este problemilla, pues he llegado averiguar la fuente del problema pero nada que lo he podido solucionar, muchas gracias por tu atención, y tu ayuda.

josé M. Aguilar dijo...

Hola, Pedro.

Estoy de viaje unos días y no podré estudiar el tema hasta el próximo fin de semana.

¿Sería posible que prepararas un 'esqueleto' de la solución (para VS2005) que pudiera usar para reproducir en mi equipo el escenario? En este caso, puedes enviármelo a josemague en gmail.

Hasta entonces sólo se me ocurren varios puntos de comprobación:
- ¿es serializable la clase Conjunto?
- en caso contrario, como solución alternativa, ¿sería factible crearte clases locales al servicio web que te aislaran del problema (tu 'Conjunto'), e instanciar y cargar estas últimas ya desde dentro del webmethod?
- ¿ocurre lo mismo si en el ejemplo de este post sacas la clase 'Mensaje' del proyecto y la metes en un ensamblado aparte?

Bueno, lo dicho, salvo que me digas lo contrario, el próximo fin de semana me pongo a ello.

Ah, y gracias por participar!

josé M. Aguilar dijo...

Pedro, he encontrado un momentillo para ver tu tema.

Si he entendido bien el problema, todo se soluciona incluyendo en cliente (javascript) el namespace de tu clase Conjunto. Es decir, instanciarla con x = new WebServi.AdminFach.Conjunto();

He probado con el ejemplo de esta página y funciona bien incluso definiendo la clase Mensaje en otro ensamblado y un namespace diferente.

Espero que te valga de ayuda.

Un saludo.