Peleándome con el UpdatePanel

6. March 2010 11:57 by Oscar.SS in Desarrollo Web  //  Tags:   //   Comments (11)

Cuando empezamos a trabajar con los controles ScriptManager y UpdatePanel normalmente nos quedamos encantados de lo rápido, sencillo y bonito que es todo. Pero cuando estamos en el trabajo,en aplicaciones reales, cuando se nos pide implementar cierta funcionalidad, nos damos cuenta que todo este automatismo tan bonito es poco elástico. Y que para conseguir la funcionalidad requerida por el cliente o por tu jefe, no nos queda más remedio que...¡pelearnos con el UpdatePanel!.

Vamos a ver un ejemplo de esta "pelea". Y para entrar en harina nada mejor que ir paso a paso chocando con todos lo problemas. Os recomiendo que copies el código en vuestro Visual Studio y ejecutéis todos los pasos que aquí se explican para daros de morros con todos lo problemas que surjan.

Supongamos que tenemos que implementar la siguiente funcionalidad.

  1. Mostrar un informe con los 4 primeros clientes de la tabla "Customers" de la conocida base de datos Northwind.
  2. Solo mostraremos los datos de los campos siguientes: CompanyName, ContactName, Address y Phone.
  3. En cada fila o registro del informe, debe haber un botón que muestre la fila selecionada escribiendo en el objeto HttpResponse. Es decir, que cada botón de cada fila debe hacer un PostBack para mostrar la fila seleccionada. Atentos porque esta es la verdadera dificultad.
  4. Otro botón, externo al informe, debe permitir mostrar los 10 primeros clientes y esta operación debe hacerse con AJAX.
Ahora nosotros como desarrolladores nos ponemos a pensar..¡que peligro!.
 
Y pensando, y pensando, llegamos a la conclusión de vamos a utilizar un control DataList para mostrar el informe. Y que este control lo meteremos dentro de un UpdatePanel registrando el botón externo al informe como disparador asíncrono para mostrar los 10 primeros resultados por AJAX. Y también registraremos el botón de cada fila del informe como disparador síncrono para que muestre la fila seleccionada con un PostBack.
 
Ya lo tenemos todo claro, y estamos seguros de que esto es muy fácil. ¡Veamos que pasa.!
 
            <asp:Button ID="Button1" runat="server" Text="Mostrar 10 resultados" onclick="Button1_Click" />
            <
asp:DataList ID="DataList1" runat="server" DataSourceID="SqlDataSource1" 
                onitemcommand
="DataList1_ItemCommand">
                
<ItemTemplate>
                    
<br />
                    <
br />
                    CompanyName:
                    <
asp:Label ID="CompanyNameLabel" runat="server" 
                        Text
='<%# Eval("CompanyName") %>' />
                    <
br />
                    ContactName:
                    <
asp:Label ID="ContactNameLabel" runat="server" 
                        Text
='<%# Eval("ContactName") %>' />
                    <
br />
                    Address:
                    <
asp:Label ID="AddressLabel" runat="server" Text='<%# Eval("Address") %>' />
                    <
br />
                    Phone:
                    <
asp:Label ID="PhoneLabel" runat="server" Text='<%# Eval("Phone") %>' />
                    <
br />
                    <
asp:Button ID="Button2" runat="server" Text="Mostrar Fila" />
                </
ItemTemplate>
            
</asp:DataList>
            
<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ConnectionString
="<%$ ConnectionStrings:NorthwindConnectionString %>" 
                SelectCommand
="SELECT TOP 4 [CompanyName], [ContactName], [Address], [Phone] FROM [Customers]">
            
</asp:SqlDataSource>
 
Añadimos este código a nuestro formulario. En primer lugar definimos el botón que mostrará los 10 primeros registros de la tabla "Customers". Después definimos el DataList que al cargarse la página gracias al SqlDataSource mostrará los 4 primeros resultados. Como vemos, en cada item o fila hemos definido el botón que mostrará la fila seleccionada. Solo nos falta escribir el código de cada botón.
 
    protected void DataList1_ItemCommand(object source, DataListCommandEventArgs e)
    {
        Response.Write(
"Fila seleccionada = " + (e.Item.ItemIndex + 1));
    
}

    
protected void Button1_Click(object sender, EventArgs e)
    {
        SqlDataSource1.SelectCommand 
"SELECT TOP 10 [CompanyName], [ContactName], [Address], [Phone] FROM [Customers]";
    
}
 
Podemos ejecutar la aplicación para comprobar que todo funciona perfectamente. Solo hay un problema, que cuando añadimos los 10 primeros resultados no lo hacemos por AJAX como se nos ha especificado. Así que manos a la obra.
 
Incluimos un control ScriptManager y un UpdatePanel que contenga el control DataList que muestra el informe. Y definimos los disparadores de control UpdatePanel...
 
                <Triggers>
                    
<asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
                    <
asp:PostBackTrigger ControlID="Button2" />
                </
Triggers>
  
Como vemos hemos registrado el control Button1 para que por AJAX muestre los 10 primeros registros. Y también hemos registrado el Button2 para muestre la fila seleccionada pero sin que se ejecute con AJAX. El código de nuestro formulario quedaría de la siguiente forma:
 
    <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
        
<div>
            
<asp:Button ID="Button1" runat="server" Text="Mostrar 10 resultados" onclick="Button1_Click" />
            <
asp:UpdatePanel ID="UpdatePanel1" runat="server">
                
<ContentTemplate>
                    
<asp:DataList ID="DataList1" runat="server" DataSourceID="SqlDataSource1" 
                        onitemcommand
="DataList1_ItemCommand">
                        
<ItemTemplate>
                            
<br />
                            <
br />
                            CompanyName:
                            <
asp:Label ID="CompanyNameLabel" runat="server" 
                                Text
='<%# Eval("CompanyName") %>' />
                            <
br />
                            ContactName:
                            <
asp:Label ID="ContactNameLabel" runat="server" 
                                Text
='<%# Eval("ContactName") %>' />
                            <
br />
                            Address:
                            <
asp:Label ID="AddressLabel" runat="server" Text='<%# Eval("Address") %>' />
                            <
br />
                            Phone:
                            <
asp:Label ID="PhoneLabel" runat="server" Text='<%# Eval("Phone") %>' />
                            <
br />
                            <
asp:Button ID="Button2" runat="server" Text="Mostrar Fila" />
                        </
ItemTemplate>
                    
</asp:DataList>
                
</ContentTemplate>
                
<Triggers>
                    
<asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
                    <
asp:PostBackTrigger ControlID="Button2" />
                </
Triggers>
            
</asp:UpdatePanel>
            
<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
                ConnectionString
="<%$ ConnectionStrings:NorthwindConnectionString %>" 
                SelectCommand
="SELECT TOP 4 [CompanyName], [ContactName], [Address], [Phone] FROM [Customers]">
            
</asp:SqlDataSource>
    
</div>
 
 
Ejecutemos la aplicación para ver que sucede.
 
 
 
 
Recibimos el error: No se pudo encontrar un control con el id. 'Button2' para el desencadenador de UpdatePanel 'UpdatePanel1'. 
 
 
¿Pero porque?. En realidad es lógico, el botón de cada fila se va a generar dinámicamente. Se crearán tantos botones como registros o filas mostremos en el control DataList. Y naturalmente cada botón tendrá un Id de cliente diferente, por lo tanto el UpdatePanel no tiene ni idea de a que disparador nos referimos cuando le decimos que registre el Button2, porque no existe un solo botón.
 
 
Eliminamos de nuestro código el registro de este botón maldito. Es decir, quitamos lo siguiente y ejecutamos la aplicación.
 
            <asp:PostBackTrigger ControlID="Button2" /> 
 
 
Parece que todo funciona correctamente. Pulsamos el botón para mostrar los 10 primeros registros y todo va como la seda. Pero que ocurre si pulsamos un botón de una de las filas.
 
 
 
 
¿Pero que es esto?. Al pulsar el botón estamos intentado escribir en el objeto HttpResponse de nuestra página. Y como estamos dentro del esquema de trabajo del UpdatePanel nuestra página ya no es la encargada de devolver el HTML al cliente. Ahora toman el mando de la respuesta los controles ScriptManager y UpdatePanel. 
 
 
¿Y como narices solucionamos esto?. Ejecutemos otra vez la aplicación pero no hagamos nada. Simplemente veamos el código fuente que se genera la primera vez que se carga la página.
 
    <script type="text/javascript">
    
//<![CDATA[
    
Sys.WebForms.PageRequestManager._initialize('ScriptManager1'document.getElementById('form1'));
    
Sys.WebForms.PageRequestManager.getInstance()._updateControls(['tUpdatePanel1'], ['Button1'], [], 90);
    
//]]>
    
</script>
 
Al principio del código fuente nos encontramos con este script autogenerado. ¿Y que hace este script?. 
 
En la primera linea instancia la clase cliente PageRequestManager y registra nuestro ScriptManager en nuestro formulario. Pero fijaros en la segunda linea. Fijaros que el botón que hemos definido para que muestre los 10 primeros resultados esta entre corchetes. Y fijaros también en que hay otros corchetes que no contienen nada.
 
Pues bien, esta linea lo que hace es registrar en el UpdatePanel cuales serán los controles disparadores que provocarán la actualización parcial del contenido del UpdatePanel. El primer corchete es para los controles que provocarán una llamada AJAX, y el segundo, como es lógico, serán los controles que provocarán un PostBack en toda regla.
 
He hablado de controles en plural porque entre estos corchetes podemos definir la colección de todos los disparadores del UpdatePanel. 
 
    Sys.WebForms.PageRequestManager.getInstance()._updateControls(['tUpdatePanel1'], ['btAjax1','btAjax2',...],
 ['btPostBack1','btPostBack2',...], 90); 
 
 
Ahora que sabemos que este script es el encargado de registrar los disparadores para el UpdatePanel es muy fácil imaginar que también podremos hacerlo nosotros desde el código cliente. ¡Veamos como!.
 
 
Creamos nuestro propio script que registre solamente los botones que se generan dinámicamente, es decir, cada botón de cada fila. Y lo vamos hacer bajo demanda del usuario. Este sería el script que habría que escribir justo debajo de la definición de nuestro ScriptManager.
  
    <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
    <script type
="text/javascript" language="javascript">
        
function RegistrarTriggers(control) {
            
Sys.WebForms.PageRequestManager.getInstance()._updateControls(['tUpdatePanel1'], [], [control.id], 90);
        
}
    </script>
Ahora solo nos queda colocar la llamada a esta función en el botón que se generará para cada fila del informe.
 
<asp:Button ID="Button1" OnClientClick="RegistrarTriggers(this)" runat="server" Text="Mostrar Fila" />
 
¡Y ya esta todo!. Comprobarlo ejecutando la aplicación y veréis como cada botón de las filas del informe muestra la fila seleccionada escribiendo en el objeto HttpResponse provocando un PostBack.
 
Quizás alguien se ha preguntado porque este ejemplo tan raro. Es una simplificación de una aplicación real, en la que se pretendía mostrar un informe más complejo en el que cada fila tuviera un botón que exportara a Excel el contenido de la fila pulsada. Este Excel, se enviaba desde el servidor en la cabecera del objeto HttpResponse. Por este motivo, los botones de cada fila deberían generar un PostBack y permitir escribir en el objeto HttpResponse de la página.
 
 
Agradecimientos: Este artículo no podría haberse escrito sin la colaboración de un buen compañero. ¡Gracias Carlos!. 
 
 

Comments (11) -

Sergio García
Sergio García
9/9/2010 4:57:55 PM #

se agradece por la claridad.

Excelente

muchas gracias.

saludos
Sergio

Oscar.SS
Oscar.SS
9/9/2010 8:18:21 PM #

Hola Sergio.

Me alegro que te haya gustado Smile

Un saludo y gracias por tu comentario.

christopher torres belez de villa
christopher torres belez de villa
10/26/2011 6:44:44 PM #

muy bueno este error pasa seguido, gracias.

Oscar.SS
Oscar.SS
10/26/2011 7:13:44 PM #

Hola Christopher, me alegro que te guste. Gracias por el comentario!!

Erick
Erick
2/21/2012 4:51:30 PM #

Muy buen articulo, gracias por compartirlo

Oscar.SS
Oscar.SS
2/21/2012 5:14:27 PM #

Gracias a ti por tu comentario Erick

Elizabeth
Elizabeth
1/7/2013 7:09:38 PM #

Excelente!

Muchas gracias por compartir tu conocimiento!

Saludos

Oscar.SS
Oscar.SS
1/7/2013 7:52:21 PM #

Gracias a ti Elizabeth por dejar un comentario.

Me alegro de que te sirviera Wink

Byron V.
Byron V.
5/29/2013 12:56:10 AM #

Llevo algunos días tratando de solucionar un problema que ya me tiene cansado. Espero que me puedas ayudar estimado Oscar. Tengo 2 updatePanel anidados. dentro del UpdatePanel(hijo) tengo un dropdowlist que genera un evento SelectedIndexChanged, al compilarlo funciona perfectamente ya que esta activado el AutoPostBack = "true"; lo que me trae loco es que al publicarlo en el servidor no lanza ese evento. Espero que me puedas dar una guía

Saludos
Byron V.

Javier C.N.
Javier C.N.
7/8/2013 6:00:50 AM #

Excelente solución. Después de mucho tiempo que la escribió, y Yo recién que me preocupo de buscar más para optimizar mis UPDATEPANEL... pero esta solución si  que me dio buenos resultados.

Mil gracias y Saludos,
Javier

Oscar.SS
Oscar.SS
7/8/2013 8:43:44 AM #

Hola Javier:

Me alegro que te sirva el artículo. Muchas gracias por publicar.

Un saludo.

Recent Comments

Comment RSS

Month List