En la actualización de la interfaz de un control de usuario en ASP.NET Web Forms con VB.NET (wucEstudiantes2.ascx), surgió la necesidad de modernizar los viejos controles asp:ImageButton que dependían de imágenes GIF fijas, reemplazándolos por controles asp:LinkButton integrados con Bootstrap Icons. Aunque la migración parecía ser un cambio menor de marcado, desencadenó errores de conversión de eventos, excepciones de formato en backend y un fallo en la apertura de modales AJAX.
1. Sustitución de marcado inicial
El control de usuario utilizaba un ImageButton para activar la búsqueda desplegable y seleccionar filas dentro de un GridView. El marcado original para seleccionar a un estudiante lucía así:
<asp:ImageButton runat="server" ID="imbSelect" ImageUrl="~/imagenes/seleccionar.gif" CommandArgument='<%# Eval("idnumber") %>' OnClick="imbSelect_Click" CausesValidation="False" CommandName='<%# Eval("student_name") %>' ValidationGroup='<%# Eval("career_idnumber") %>'></asp:ImageButton>Para aprovechar los estilos del marco de trabajo CSS y sustituir la imagen por un icono, reestructuré el control a un asp:LinkButton:
<asp:LinkButton runat="server" ID="lbtnSelect" CssClass="btn btn-outline-primary btn-sm" CommandArgument='<%# Eval("idnumber") %>' CommandName='<%# Eval("student_name") %>' ValidationGroup='<%# Eval("career_idnumber") %>' OnClick="lbtnSelect_Click" CausesValidation="False">
<i class="bi bi-check-circle"></i>
</asp:LinkButton>2. El primer escollo: InvalidCastException en el evento Click
Al hacer clic en el nuevo botón, la aplicación se detuvo inmediatamente lanzando un error en tiempo de ejecución durante la fase de PostBack.
La causa técnica reside en las diferentes firmas de evento que maneja la infraestructura de ASP.NET Web Forms:
- Un
ImageButtontransmite argumentos del tipoImageClickEventArgs(el cual contiene coordenadas de clic X/Y). - Un
LinkButtontransmite argumentos genéricos del tipoEventArgs.
El controlador de eventos original en el código subyacente (VB.NET) mantenía la firma antigua:
Protected Sub imbSelect_Click(sender As Object, e As System.Web.UI.ImageClickEventArgs)
hdfIdEstudiante.Value = DirectCast(sender, ImageButton).CommandArgument
hdfIdCarrera.Value = DirectCast(sender, ImageButton).ValidationGroup
txtNombre.Text = DirectCast(sender, ImageButton).CommandName
RaiseEvent EstudianteSeleccionado(IdEstudiante, IdCarrera)
End SubPara solucionar esta incompatibilidad, refactoricé el evento ajustando la firma a EventArgs genérico y utilizando TryCast para realizar una conversión segura del emisor (sender):
Protected Sub lbtnSelect_Click(sender As Object, e As EventArgs)
Dim btn As LinkButton = TryCast(sender, LinkButton)
If btn IsNot Nothing Then
hdfIdEstudiante.Value = btn.CommandArgument
hdfIdCarrera.Value = btn.ValidationGroup
txtNombre.Text = btn.CommandName
RaiseEvent EstudianteSeleccionado(IdEstudiante, IdCarrera)
End If
End Sub3. Segundo problema: FormatException al parsear propiedades
Resuelto el inconveniente del casting de evento, emergió un segundo error durante la lectura de las propiedades del control de usuario.
La propiedad IdEstudiante realizaba una conversión directa asumiendo que el campo oculto siempre contendría una cadena numéricamente válida:
Public Property IdEstudiante() As Long
Get
Return Long.Parse(hdfIdEstudiante.Value)
End Get
Set(ByVal value As Long)
hdfIdEstudiante.Value = value.ToString()
End Set
End PropertySi durante la reconstrucción del árbol de controles o antes de la asignación del clic la propiedad Get era invocada estando el HiddenField vacío (""), Long.Parse() interrumpía la ejecución. Se fortalecieron ambas propiedades sustituyendo Parse por TryParse:
Public Property IdEstudiante() As Long
Get
Dim valor As Long = -1
Long.TryParse(hdfIdEstudiante.Value, valor)
Return valor
End Get
Set(ByVal value As Long)
hdfIdEstudiante.Value = value.ToString()
If value = -1 Then
IdCarrera = -1
txtNombre.Text = ""
Else
LoadName()
End If
End Set
End Property4. El punto de quiebre: El TargetControlID del ModalPopupExtender
Tras solucionar los errores de backend, persistía un comportamiento anómalo: al presionar el nuevo botón de búsqueda principal (lbtnSearch), el modal emergente no se abría en pantalla.
Al inspeccionar la configuración del kit de herramientas AJAX (AjaxControlToolkit), se identificó la raíz del problema. El extensor del modal dependía explícitamente del identificador del control de imagen original:
<asp:ModalPopupExtender ID="mpePrincipal" runat="server" BackgroundCssClass="modalBackground"
DropShadow="False" PopupControlID="pnlPrincipal" PopupDragHandleControlID="pnlDragMsg"
Enabled="True" DynamicServicePath="" CancelControlID="btnCancelar" TargetControlID="imbBuscar">
</asp:ModalPopupExtender>Dado que el botón imbBuscar había sido sustituido en la vista por lbtnSearch, el JavaScript del cliente no podía enlazar el evento lanzador del modal. La solución definitiva consistió en sincronizar el atributo TargetControlID hacia el nuevo LinkButton:
<asp:ModalPopupExtender ID="mpePrincipal" runat="server" BackgroundCssClass="modalBackground"
DropShadow="False" PopupControlID="pnlPrincipal" PopupDragHandleControlID="pnlDragMsg"
Enabled="True" DynamicServicePath="" CancelControlID="btnCancelar" TargetControlID="lbtnSearch">
</asp:ModalPopupExtender>La historia detrás de la nota
En ocasiones, los fallos de ejecuciones complejas o excepciones desconcertantes en backend son desencadenados simplemente por un ID desalineado en un extender de interfaz. Verificar las referencias cruzadas entre marcado y extensiones AJAX ahorra horas de depuración.