Acceso a los controles de un UserControl

Posted on abril 29, 2010

9


ASP.NET provee de elementos muy interesantes para construir un sitio web. Sin embargo también hay algunas dificultades que tendremos que sortear tarde o temprano.

Una de estas dificultades consiste en que los controles que ponemos dentro de un control de usuario, no pueden ser accesados tan fácilmente como los que están colocados a nivel de página.

Por ejemplo, si tenemos un control de usuario sencillo como el siguiente:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="WSMyLittleStore.WebUserControl1" %>

<asp:Label ID="Label1" runat="server" Text="Nombre"></asp:Label>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<br />
<asp:Label ID="Label2" runat="server" Text="Apellido"></asp:Label>
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>

Y un formulario igualmente sencillo como:

<form id="form1" runat="server">
 <div>
 <uc1:WebUserControl1 ID="WebUserControl11" runat="server" />
 <br />
 <asp:Label ID="Label1" runat="server" Text="Edad"></asp:Label>
 <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
 </div>
 </form>

No podremos hacer lo siguiente en el code-behind de la página:

protected void Page_Load(object sender, EventArgs e)
 {
 TextBox1.Text = "TextBox en el form"; //Correcto!
 WebUserControl1.TextBox1.Text = "TextBox en el UserControl"; //Error!
 WebUserControl1.TextBox2.Text = "TextBox en el UserControl"; //Error!
 }

Esto se debe a que los controles dentro del UserControl están declarados con el atributo protected.

He utilizado tres maneras de sortear este problema, cada una con sus ventajas e inconvenientes:

1) Crear una propiedad pública dentro del UserControl

En el code-behind del UserControl haríamos lo siguiente:

public partial class WebUserControl1 : System.Web.UI.UserControl
 {
 public TextBox Texto1
 {
 get { return TextBox1; }
 set { TextBox1 = Texto1; }
 }

 public TextBox Texto2
 {
 get { return TextBox2; }
 set { TextBox1 = Texto2; }
 }

 protected void Page_Load(object sender, EventArgs e)
 {
 }
 }

Ahora en el code-behind del form podemos utilizar Texto1 y Texto2 para referirnos a las hasta ahora inaccesibles cajas de texto del UserControl:

protected void Page_Load(object sender, EventArgs e)
 {
 TextBox1.Text = "TextBox en el form"; //Correcto!
 WebUserControl1.Texto1.Text = "TextBox en el UserControl"; //Correcto!!
 WebUserControl1.Texto2.Text = "TextBox en el UserControl"; //Correcto!!
 }

Esta es la opción más recomendada por la mayoría. Para mi es totalmente impráctica cuando el UserControl cuenta con muchos controles que deseamos exponer. Imagínense 40 declaraciones de gets y sets. Simplemente, me parece una pérdida de tiempo y una carga negativa para la mantenibilidad de la aplicación. Sin embargo puede ser útil para utilizarla con unos pocos controles (hasta 3, diría yo).

2) Utilizar FindControl en el code-behind de la página

En este caso nos olvidamos de los gets y sets y trabajamos directamente con el code-behind del form:

protected void Page_Load(object sender, EventArgs e)
 {
 TextBox Texto1 = (TextBox)WebUserControl1.FindControl("TextBox1");
 TextBox Texto2 = (TextBox)WebUserControl1.FindControl("TextBox2");
 TextBox1.Text = "TextBox en el form"; //Correcto!
 Texto1.Text = "TextBox en el UserControl"; //Correcto!
 Texto2.Text = "TextBox en el UserControl"; //Correcto!
 }

Sé que esta alternativa va a resultar tentadora para más de uno. Pero hay que tener en cuenta 2 inconvenientes:

Primero, que estamos introduciendo “código duro” en nuestro form, obligándonos a mantener a mano la coincidencia de los nombres de los controles con el string que utilizamos en cada método FindControl.

Segundo y más importante aún, que cualquier error en los nombres no será detectado por el compilador sino que causará errores de ejecución o de lógica en nuestro formulario.

3) Cambiar los atributos a public

En este caso nos fijaremos que nuestro UserControl se compone de 3 ficheros:

  • UserControl.ascx
  • UserControl.ascx.cs
  • UserControl.ascx.designer.cs

Nosotros abriremos el archivo UserControl.ascx.designer.cs y veremos que ahí se encuentran declarados nuestros controles con el atributo protected. Veremos también que sobre cada declaración hay un mensaje que dice:

 /// Auto-generated field.
 /// To modify move field declaration from designer file to code-behind file.

Y bueno, pues podemos hacer caso de eso y cortar la declaración del control para luego pegarla en el code-behind. Una vez en el code-behind, cambiaremos la palabra protected por public y ¡voila!, ya tendremos acceso al control desde el form.

El code-behind del UserControl debería quedar como sigue:

public partial class WebUserControl1 : System.Web.UI.UserControl
 {
 public global::System.Web.UI.WebControls.TextBox TextBox1;
 public global::System.Web.UI.WebControls.TextBox TextBox2;

 protected void Page_Load(object sender, EventArgs e)
 {

 }
 }

Ahora en el code-behind de nuestro form podemos escribir:

protected void Page_Load(object sender, EventArgs e)
 {
 TextBox1.Text = "TextBox en el form"; //Correcto!
 WebUserControl1.TextBox1.Text = "TextBox en el UserControl"; //Correcto!
 WebUserControl1.TextBox2.Text = "TextBox en el UserControl"; //Correcto!
 }

El principal inconveniente de esta solución es que atenta contra una de las leyes de la POO: que los atributos de una clase no deben ser accesados directamente, sino a través de métodos o propiedades. Pero es que las otras soluciones tampoco parecen sacadas de un libro de “buenas prácticas” y me parece que Microsoft nos ha dejado un poco huérfanos con este problema.

Para finalizar, creo que ninguna de las opciones es óptima. Cada una servirá para situaciones particulares y ya depende de cada uno considerar sus ventajas y desventajas. Si alguien conoce otra forma, ¡por favor compartirla con los que leen este blog!

Por lo pronto, yo pienso que Microsoft debería ofrecer una mejor alternativa en las futuras revisiones del UserControl. Por decir algo: que la accesibilidad de cada control pueda ser establecida por medio de una propiedad en tiempo de diseño.

¿Qué opinan ustedes?

¡Hasta pronto!

Posted in: ASP.NET, CSharp, dotNET