My Opera is closing 3rd of March
photo of el Isra

el blog del isra

ASP.NET: Crear controles personalizados (Custom WebControls)

, , , , ,

Una de las características principales de .NET para Web (ASP.NET) es la gran cantidad de controles que contiene (WebControls), visuales o no visuales. No nos vemos limitados por lo que HTML nos ofrece, sino que recurrimos a esos controles para aprovechar sus características y funciones. Pero no podemos confiar en que siempre cubrirán nuestros requisitos.

Alguna vez leí en algún foro, una respuesta a quien pedía una librería para javascript: "lo mejor siempre es que tú hagas tu propio framework." Eso es verdad. Todo mecánico carga con la bolsa de herramientas que sabe que utilizará. Bob Ross tenía siempre lista su paleta de colores, las brochas y las espátulas con las que dibujaría sus árboles felices. Batman siempre carga su Baticinturón con sus Batiaccesorios de uso cotidiano. Pues en programación es lo mismo, no hay nada mejor que tener tu propio conjunto de herramientas, funciones y componentes, que cubran los requisitos de las aplicaciones que creas.

Después de dos semanas de "escribir de a pedacitos" p les tengo esta guía para crear un control Web personalizado en .NET. El ejemplo es una lista en acordeón, utilizando el framework para javascript jQuery. Será un ejemplo sencillo, pero tocaré los puntos principales para crear un WebControl, así como algunas características de .NET que es importante conocer.

El post es algo largo, pero hice lo necesario para explicar lo que se hace, y despejar las dudas que puedan surgir. Los comentarios están abiertos para cualquier pregunta.


Código fuente del resultado de esta guía

Requisitos

¿Cómo se generan los controles Web en .NET?

Cada página que creamos en .NET (con extensión aspx) es procesada por el servidor, por medio de Internet Information Services (IIS) y utilizando .NET para crear un archivo con puro código HTML y enviarla al usuario. Es decir, quien visite la página no va a ver nuestros componentes tal como los escribimos.

Suponiendo que creamos estos componentes:

<asp:Label ID="label1" runat="server" Text="Escriba un texto:" AssociatedControlID="textBox1" />
<asp:TextBox ID="textBox1" runat="server" Width="100px" />
<asp:Button ID="button1" runat="server" CssClass="boton" Text="Presione para continuar" OnClick="button1_Click()" OnClientClick="jsFunctionClick()" />

Cuando consultemos la página desde un navegador, si revisamos el código fuente encontraremos algo así:


<input type="text" id="textBox1" style="width:100px" />
<input type="submit" id="button1" class="boton" value="Presione para continuar" onclick="jsFunctionClick();__doPostBack('button1','Click');" />

Lo que el servidor hizo, fue procesar cada elemento de .NET de acuerdo a lo que determine la clase de la que se deriva (Label, TextBox, Button), y mostró el código HTML equivalente.

public class Label {
  protected void Render(HtmlWriter writer) {
    // Código para generar el control y escribirlo en la página.
  }
}

Dentro del namespace (espacio de nombres) System.Web.UI existe la clase Control, que es la base para generar controles en las páginas Web.

En el mismo namespace se encuentra otro espacio llamado WebControls, el cual contiene una clase WebControl, que se deriva de Control y tiene más propiedades básicas, como CssClass (para definir una clase en CSS), o TagName, que determina el tipo de elemento HTML que se generará (esta propiedad será muy importante en nuestro ejemplo).

Junto con WebControl están todas las clases definidas para los distintos controles que se utilizan en ASP, derivados todos ellos de WebControl.

Nuestro control puede derivarse tanto de Control, como de WebControl, o incluso de un control ya definido, como un Label, o un GridView. Todos ellos pueden ser utilizados mediante herencia y extendidos a nuestro gusto.

Mi primer control personalizado: Acordeón

Ya tenemos una idea de cómo se procesan los controles en .NET, ahora podemos comenzar con nuestro control. Como dije, crearemos un acordeón, es decir, una lista de definiciones en las que se mostrará sólo una a la vez, expandiéndose al seleccionarlo con el mouse y contrayendo al resto (ejemplo)

Antes de definir el control en .NET, debemos definir cuál será el resultado en HTML. Debemos decidir en qué elemento HTML se "convertirá" nuestro control. Tenemos muchas opciones, pero la más acertada, en mi opinión, es la de <dt>, que es precisamente, una lista de definiciones.


<dl>
  <dt>Título de primera definición</dt>
  <dd>Descripción de la primera definición.</dd>
  <dt>Título de la segunda definición</dt>
  <dd>Descripción de la segunda definición</dd>
</dl>
Título de primera definición
Descripción de la primera definición.
Título de la segunda definición
Descripción de la segunda definición

Tenemos tres tipos de elementos: dl, dt y dd. Para nuestro control, crearemos una clase Acordeon, que creará el dl, y una clase Elemento que generará las etiquetas dt y dd. ¿Por qué no hacer una clase para cada etiqueta? Porque en realidad la etiqueta dt sólo guarda un título. Podríamos poner más elementos en esa etiqueta, pero lo correcto es sólo dar un nombre a lo que dd está definiendo. Así que crearemos una clase Elemento que contendrá todo lo de dd y en la que podremos indicar también el título que irá en dt. El código en JavaScript lo veremos después. Por ahora nos concentraremos en la creación del HTML

Menu File &gt; New &gt; Project

Hora de comenzar el trabajo duro. Utilizaremos Visual Studio, ya después veremos la compilación en consola de comandos. Nos vamos al menú File > New > Project, y en la lista de plantillas seleccionamos Class Library. Pueden escoger el lenguaje que gusten. Yo utilizaré C#. Damos un nombre al proyecto y presionamos OK.

Crear nuevo proyecto

Automáticamente se creará un archivo default Class1.cs/Class1.vb. Lo borramos.

Nos vamos a las propiedades del proyecto (en el proyecto, clic derecho y luego en Properties), donde podemos definir el nombre del ensamblado (assembly) y el espacio de nombres. Estas propiedades las utilizaremos después, al momento de implementar nuestro control en un sitio Web. También podemos dar una descripción para el producto y la información de copyright. Otra forma de hacer esto es irnos al archivo /Properties/AssemblyInfo.cs y cambiar las propiedades manualmente.

Debemos descargar los archivos de jQuery y jQuery UI indicados en los requisitos y agregarlos al proyecto. Los pondremos en una nueva carpeta llamada Recursos.

Estructura de archivos que se utilizarán.

Es posible que en las referencias no aparezca automáticamente la de System.Web. Debemos agregarla, dando clic derecho al proyecto y seleccionando "Add Reference".

Ahora agregamos un nuevo archivo de clase al proyecto, con el nombre Acordeon.cs. Podemos añadirlo a un namespace, si queremos, aunque también podemos definirlo dentro de la clase que vamos a crear. Si en las propiedades del proyecto establecimos IsraSoft como namespace, y la clase pertenecerá al de MisControles, debemos establecer como namespace del control "IsraSoft.MisControles".

// librerías que se deben importar
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace IsraSoft.MisControles {
  [ToolboxData("<{0}:Acordeon runat=server></{0}:Acordeon>"),
   ParseChildren(typeof(Elemento),DefaultProperty="Elementos",ChildrenAsProperties=true),
   PersistChildren(false)]
  public class Acordeon : WebControl{

  }
}

Los valores encerrados entre corchetes son atributos que definen a la clase. También se pueden establecer ciertos atributos para las variables o métodos contenidos en la clase. En VisualBasic se utilizan los símbolos < y >, y se debe escribir todo como una sola línea, incluyendo la definición de la clase.

<ToolboxData("<{0}:Acordeon runat=server></{0}:Acordeon>"), _
 ParseChildren(typeof(Elemento),DefaultProperty="Elementos",ChildrenAsProperties=true), _
 PersistChildren(false)> _
Public Class Acordeon
  Inherits WebControl

End Class

ToolboxData indicará la forma predeterminada en que se crearán los controles, si los cargamos desde la barra de herramientas de Visual Studio. Yo estoy poniendo solamente el atributo runat, pero podríamos poner más propiedades, si queremos. Claro, deberán ser propiedades que tenga el control, no debemos poner cualquier cosa que se nos ocurra.

El valor "{0}" será reemplazado automáticamente por el prefijo que determinemos en nuestra aplicación. En los controles de .NET el prefijo es "asp", como en <asp:Label>, pero nosotros podemos definir uno propio para las bibliotecas de clases que importamos.

Los atributos ParseChildren y PersistChildren trabajan en conjunto, indicando si el contenido anidado de nuestro control (todo lo que se ponga entre la etiqueta de apertura y de cierre de Acordeon) será tratado y procesado como controles de servidor o como propiedades del control padre (Acordeon). En la definición de PersistChildren da una explicación más detallada de la interacción entre estos atributos.

Los parámetros que utilizamos en Acordeon son: Tipo del contenido anidado (Elemento), Propiedad predeterminada (Elementos es una variable que veremos más adelante) y ChildrenAsProperties, que indica que estos Elementos serán manejados como propiedades del control, es decir, no como controles individuales.

Al escribir Acordeon : WebControl, estamos indicando que la clase Acordeon heredará de la clase WebControl sus atributos y métodos, los cuales podemos moificar, como lo veremos más adelante.

En el mismo archivo crearemos una nueva clase, a la que llamaremos Elemento

// librerías que se deben importar
...

namespace IsraSoft.MisControles {
  [ToolboxData("<{0}:Acordeon runat=server></{0}:Acordeon>"),
   ParseChildren(typeof(Elemento),DefaultProperty="Elementos",ChildrenAsProperties=true),
   PersistChildren(false)]
  public class Acordeon : WebControl{

  }
  [ToolboxItem(false)]
  [ParseChildren(false),PersistChildren(true)]
  [ToolboxData("<{0}:Elemento runat=server></{0}:Elemento>")]
  public class Elemento : WebControl {

  }
}

Primero, como pueden ver, los atributos los puse en corchetes separados. ¿Por qué? Por la simple y sencilla razón de demostrarles que eso también se puede hacer p No importa realmente si lo hacen de esa forma, es más cuestión estética.

Aquí vemos una propiedad nueva, ToolboxItem, la cual indica el tipo de elemento dentro de la barra de herramientas. Al establecerlo en false, la clase quedará oculta para dicha barra, independientemente si es Control o no.

ParseChildren es establecido en Falso y PersistChildren en Verdadero. Esto es para que la etiqueta Elemento pueda contener otras etiquetas, de HTML o de otros controles de .NET.

El atributo ToolboxData sigue siendo definido para que quede establecida la forma en que se creará la etiqueta.

Todavía no podemos compilar el proyecto, pues hay algo que le indicamos a la clase Acordeon que no hemos definido: la propiedad Elementos.

Elementos será una variable de tipo Lista que contendrá todos los objetos Elemento que se definan en el sitio Web. Y la definimos de esta forma dentor de la clase:

  public class Acordeon : WebControl{

    private List<Elemento> _elementos;
    public List<Elemento> Elementos {
      get { return _elementos; }
      set { _elementos = value; }
    }
  }

Aquí definimos dos variables: _elementos y Elementos. La primera es una variable privada, que sólo podrá ser manipulada dentro de la clase Acordeon, y la otra es pública, y será la que manejemos desde fuera de la clase, o sea, cuando creemos nuestro control en una página Web.

Elementos parece ser un método, con esas llaves que le puse, pero no lo es. Una variable que tiene las funciones get y set anidadas es lo que se llama propiedad. Si se fijan, lo que Elementos hace en realidad es siempre recurrir a _elementos, que es la verdadera contenedora de los controles que crearemos para nuestro acordeón. _elementos es la mera mera, pero Elementos es la cara bonita que se verá en el código de la página Web.

Cuando queremos obtener el valor de una propiedad, por ejemplo List<Elemento> lista = Elementos, el sistema entrará directamente a get, el cual devuelve el valor de _elementos. Si queremos asignar un valor a la propiedad, como Elementos = new List<Elemento>(), se entrará a set, y value representará el valor que queremos asignar a la propiedad.

Aquí parecerá no tener sentido el utilizar una variable oculta y una propiedad pública, pero es muy importante tenerlo en cuenta, ya que dentro de una propiedad puedes definir toda una función. Puedes hacer validaciones, condicionar el valor de la variable oculta de acuerdo a lo que tenemos en la propiedad, o incluso manipular el valor que recibes y guardar distintos valores en distintas variables privadas.

En Visual Basic, el código sería éste:

Public Class Acordeon
  Inherits WebControl
  Private _elementos As List<Elemento>
  Public Property Elementos() As List<Elemento>
    Get
      Return _elementos
    End Get
    Set
      _elementos = Value
    End Set
  End Property
End Class

Podemos agregar una propiedad de cualquier tipo a una clase. Cuando son los tipos de variables como int, string, bool, etc., todos tienen un valor predeterminado (0, "", false). El problema es cuando nuestra propiedad es un objeto de una clase más compleja, como lo es List<> en este caso. De entrada, _elementos tendrá un valor null, lo cual puede provocarnos errores.

Lo que haremos será instanciar _elementos en el constructor de la clase. Así nos aseguraremos que en todo momento tendrá un valor.

  public class Acordeon : WebControl{

    public Acordeon() {
      _elementos = new List<Elemento>();
    }
    private List<Elemento> _elementos;
    public List<Elemento> Elementos {
      get { return _elementos; }
      set { _elementos = value; }
    }
  }

Recuerden que en VB.NET el constructor se declara como Public Sub New()

.

Si compiláramos el proyecto en este punto, podemos agregarlo como referencia a un sitio Web e introducir controles de clase Acordeon. Si hacemos esto, podremos crearlos tal como los controles que .NET ya incluye.

<ctrl:Acordeon ID="acordeon1" runat="server">
    <ctrl:Elemento runat="server">
        Primer elemento de nuestra lista.
    </ctrl:Elemento>
    <ctrl:Elemento runat="server">
        Segundo elemento de nuestra lista.
    </ctrl:Elemento>
</ctrl:Acordeon>

El asunto es que si lo compilamos, no obtendremos ningún resultado. Seguramente sólo veremos esto en la página:

    <span id="acordeon1"></span>

Ah, ca...nijo! ¿Qué le pasó a nuestro control? ¿Dónde están esos dos elementos que escribimos?

Visual Studio tiene la gran ventaja de que podamos ver qué elementos se pueden insertar dentro de un control. En el caso de Acordeon, podemos ver que sólo se permite insertar objetos de tipo Elemento por dentro, y no podemos poner <p>, <div>, <asp:Label> ni ningún control que no sea un Elemento. Esto es porque los atributos ParseChildren y PersistChildren de Acordeon están definidos de tal forma que lo contenido dentro de un objeto de esa clase sea tratado como Propiedades, además de que especificamos que la propiedad a utilizar sería Elementos, por lo que el contenido de nuestro acordeón será únicamente una lista de Elementos.

Ahora bien, como dije, son propiedades, no controles. No se le puede exigir todo el trabajo a .NET. Si se le dice que son propiedades, son eso, y nada más. El hacerlos controles depende de nosotros. Así que ésa será nuestra siguiente tarea: decirle a la clase que los elementos serán tratados como controles.

WebControl tiene toda una serie de métodos que se ejecutan al generar (renderizar) el control en la página, es decir, al convertir el control en un elemento HTML. Todos esos métodos pueden ser sobreescritos, indicando el término override en dicho método dentro de nuestra clase.

Uno de esos métodos es CreateChildControls, que procesa la colección de controles anidados dentro de un control padre (en otras palabras, lo que queremos hacer con los Elementos en el Acordeón). Por medio de código, haremos que esos Elementos, considerados propiedades hasta ese momento, sean guardados dentro de la colección de controles del Acordeón.

public class Acordeon : WebControl{

  private List<Elemento> _elementos;
  public List<Elemento> Elementos {
    get { return _elementos; }
    set { _elementos = value; }
  }
  protected override void CreateChildControls() {
    foreach (Elemento e in Elementos) {
      this.Controls.Add(e);
    }
    base.CreateChildControls();
  }
}

Primero que nada, base.OnPreRender(e) es la llamada a la clase padre (WebControl) para que realice las operaciones que hace esta función. No debemos borrar esta línea, pues podríamos evitar que se realicen operaciones que no conocemos, y que le quiten funcionalidad a nuestro control. Lo que hacemos en este método es añadir cada miembro de Elementos a la colección Controls, que pertenece a WebControl. Con esto, el servidor ya no los tomará como propiedades al momento de generar el control, sino como controles anidados, y al ser derivados de WebControl serán tratados como tal.

Ya salimos de un problema. Ahora nuestros Elementos serán mostrados en la página.

El otro detalle es el tipo de elemento HTML que se está creando:

<span id="acordeon1">
  <span>Primer elemento de nuestra lista.</span>
  <span>Segundo elemento de nuestra lista.</span>
</span>

La etiqueta <span> es la más simple de HTML. Se usa para encerrar una porción de texto dentro de una línea, o un párrafo, o una lista, etc. Puede ser insertada dentro de cualquier elemento de texto. Por lo tanto, .NET la usa como predeterminada para los controles Web. Claro que eso no nos sirve a nosotros, así que la cambiaremos por las que necesitamos.

Al principio vimos que nuestro control sería una lista (dl) de elementos (dd) precedidos por su encabezado (dt). Comenzaremos con la clase Acordeon.

public class Acordeon : WebControl{

  private List<Elemento> _elementos;
  public List<Elemento> Elementos {
    get { return _elementos; }
    set { _elementos = value; }
  }
  protected override HtmlTextWriterTag TagKey {
    get { return HtmlTextWriterTag.Dl; }
  }
  protected override void CreateChildControls() {
    foreach (Elemento e in Elementos) {
      this.Controls.Add(e)
    }
    base.CreateChildControls(e);
  }
}

Otro elemento sobreescrito de la clase WebControl: TagKey. Cuando la función que renderiza el control tiene que escribir el tipo de elemento HTML que se generará, recurre a esta propiedad, por lo que nosotros reemplazamos lo que tiene de forma predeterminada (que lleva a la etiqueta span), por la de DL. Haremos lo mismo con Elemento, dándole la etiqueta DD.

  public class Elemento : WebControl {
    protected override HtmlTextWriterTag TagKey {
    get { return HtmlTextWriterTag.Dd; }
    }
  }

Ahora sí, nuestro acordeón generará automáticamente los elementos que necesitamos.

<dl id="acordeon1">
  <dd>Primer elemento de nuestra lista.</dd>
  <dd>Segundo elemento de nuestra lista.</dd>
</dl>

¿Y el título, apá? Ya tenemos nuestra lista y los elementos de descripción de cada miembro, pero el título no lo hemos generado. No creamos un control más para esos elementos, ¿por qué? Porque consideraremos que el título siempre será texto. Lo que haremos será darle a Elemento una propiedad en la que se defina la etiqueta DT que corresponderá a tal elemento.

  public class Elemento : WebControl {
    protected override HtmlTextWriterTag TagKey {
    get { return HtmlTextWriterTag.Dd; }
    }
    private string _titulo;
    [Category("Appearance")]
    public string Titulo {
      get { return _titulo; }
      set { _titulo = value; }
    }
  }

Cuando usamos el diseñador de VisualStudio, al seleccionar un WebControl podemos ver sus propiedades en diferentes Categorías. La propiedad Category define en cuál de esas se mostrará la propiedad.

Ya definida la variable que tomará el nombre del título, podemos utilizarla en nuestras páginas Web.

<ctrl:Acordeon ID="acordeon1" runat="server">
    <ctrl:Elemento runat="server" Titulo="Elemento 1">
        Primer elemento de nuestra lista.
    </ctrl:Elemento>
    <ctrl:Elemento runat="server" Titulo="Elemento 2">
        Segundo elemento de nuestra lista.
    </ctrl:Elemento>
</ctrl:Acordeon>

Obviamente el definir la propiedad Titulo así nomás no sirve de nada. Tenemos que generar la etiqueta DT por código, y debe hacerse antes de que se cree el elemento DD.

WebControl tiene toda una serie de métodos para renderizar (generar) el control por etapas: uno para la etiqueta de inicio, otro para la de cierre, otro para el contenido, otro para los atributos, etc. El de Render será el que utilicemos.

  public class Elemento : WebControl {
    protected override HtmlTextWriterTag TagKey {
     get { return HtmlTextWriterTag.Dd; }
    }
    private string _titulo;
    [Category("Appearance")]
    public string Titulo {
      get { return _titulo; }
      set { titulo = value; }
    }
    protected override void Render(HtmlTextWriter writer) {
      string dt = "<dt>{0}</dt>";
      writer.Write(string.Format(dt, Titulo));
      base.Render(writer);
    }
  }

Recordemos que la llamada al mismo método en base es lo que realiza las funciones predeterminadas para este método. Render crea el control DD, por lo que debemos agregar la etiqueta DT antes de que esto ocurra.

El objeto writer es el que se utiliza para "escribir" el código HTML dentro de la página. Va pasando de un método a otro, escribiendo lo que cada uno le indique. Aquí, le estamos indicando que escriba la etiqueta DT con el título del elemento, antes de crear la etiqueta DD.

Ahora sí, ya tenemos nuestra lista.

<dl id="acordeon1">
  <dt>Elemento 1</dt>
  <dd>Primer elemento de nuestra lista.</dd>
  <dt>Elemento 2</dt>
  <dd>Segundo elemento de nuestra lista</dd>
</dl>

Felicidades! Hemos creado nuestro primer control HTML que genera una lista de elementos!!! :cheer: Claro que no es el objetivo de un "Acordeón" en tener una simple lista. Es hora de entrar en algo más avanzado de .NET

Inserción de archivos en un WebControl

Hasta ahora tenemos un WebControl que crea un elemento HTML, sin muchas complicaciones. Lo que sigue es darle la función de acordeón incluída en el archivo de jQueryUI que indiqué en los requisitos.

Como se muestra en la documentación de jQuery UI, para crear un acordeón se debe hacer esto:

<script type="text/javascript" src="jquery-1.3.2.js"></script>
<script type="text/javascript" src="jquery-ui-1.7.2.custom.min.js"></script>
<script type="text/javascript">
  $(function(){
    $('acordeon1').accordion();
  }
</script>
<dl id="acordeon1">
  <dt>Elemento 1</dt>
  <dd>Primer elemento de nuestra lista.</dd>
  <dt>Elemento 2</dt>
  <dd>Segundo elemento de nuestra lista</dd>
</dl>

Lo que se hace aquí es agregar a la página Web la referencia a los dos archivos de JavaScript y una función que convierte en acordeón a nuestra lista.

Todo esto se ve sencillo si hacemos la página a mano, pero aquí estamos utilizando un WebControl de .NET, y lo más conveniente es tener todas esas funciones dentro del mismo. En realidad no vamos a hacer algo muy complicado, no más que lo que hemos hecho hasta ahora.

Los archivos JS que utilizaremos no son clases de .NET, ni se puede acceder a ellos directamente. Serán utilizados como recursos.

En el explorador de soluciones de Visual Studio, seleccionamos el archivo de jquery-1.3.2.js, le damos clic derecho y seleccionamos Properties, para ver el panel de Propiedades. Ahí veremos una propiedad "Build Action", a la cual le daremos el valor de "Embeded Resource". Luego cambiamos la propiedad "Copy to output Directory" a "Do not copy". Con esto, le indicamos al compilador que el archivo será insertado dentro del control (es decir, en el archivo dll generado de la compilación) y se accederá a él como recurso, no como archivo, y que al mismo tiempo, no debe crearse una copia en la compilación. Hacemos lo mismo con el archivo de jQuery UI.

Ahora nos vamos al archivo Properties/AssemblyInfo.cs, para agregar las referencias a estos recursos. Al principio del archivo, escribimos una referencia a System.Web.UI. Al final, escribimos esto:

[assembly: WebResourceAttribute("IsraSoft.Recursos.jquery-1.3.2.js", "text/javascript")]
[assembly: WebResourceAttribute("IsraSoft.Recursos.jquery-ui-1.7.2.custom.min.js", "text/javascript")]

Si se fijan, pusimos a cada archivo "IsraSoft.Recursos". Esto es porque ambos están dentro del proyecto que tiene un espacio de nombres IsraSoft y están dentro de la carpeta Recursos, como lo vimos en la creación del proyecto. Es importante verificar que la referencia al archivo siempre esté correctamente escrita.

Ahora nos vamos de vuelta a nuestro control, donde haremos las funciones que generen esas etiquetas de script que puse de ejemplo arriba.

Ya modificamos la función Render en Elemento. Ahora trabajaremos sobre otro método en la clase Acordeon, que también se ve involucrada en el proceso de generación del control: OnPreRender.

public class Acordeon : WebControl{

  private List<Elemento> _elementos;
  public List<Elemento> Elementos {
    get { return _elementos; }
    set { _elementos = value; }
  }
  protected override HtmlTextWriterTag TagKey {
    get { return HtmlTextWriterTag.Dl; }
  }
  protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);
    Page.ClientScript.RegisterClientScriptResource(typeof(Acordeon),
      "IsraSoft.Recursos.jquery-1.3.2.js");
    Page.ClientScript.RegisterClientScriptResource(typeof(Acordeon),
      "IsraSoft.Recursos.jquery-ui-1.7.2.custom.min.js");
    string script =
      "<script type=\"text/javascript\">" +
      "<!--\n" +
      "jQuery(function(){" +
        "jQuery(\"#" + this.ClientID + "\").accordion();" +
      "});\n" +
      "-->" +
      "</script>";
    Page.ClientScript.RegisterClientScriptBlock(typeof(Acordeon),
      "Acordeon" + this.ID, script, false);
  }
  protected override void CreateChildControls() {
    foreach (Elemento e in Elementos) {
      this.Controls.Add(e)
    }
    base.CreateChildControls(e);
  } }

PreRender es una función que se ejecuta antes de generar las etiquetas HTML que forman un WebControl. Ya que vamos a agregar código de JavaScript para nuestro botón, hay que introducirlo fuera del elemento HTML. En este caso, lo haremos como en el ejemplo, primero la etiqueta <script> y luego la <dl&rt;.

Como antes, mantenemos la llamada a base para que se realicen las operaciones heredadas en este método. Después utilizamos un nuevo elemento, muy útil en el desarrollo Web en .NET: ClientScript.

ClientScript, objeto del tipo ClientScriptManager, nos permite realizar distintas operaciones para insertar código de JavaScript en una página ASPx.

En nuestro control, tenemos que agregar la referencia a los dos archivos de JavaScript (jQuery y jQuery UI). El método que usamos en este ejemplo es RegisterClientScriptResource, que hace precisamente lo que queremos: insertar una referencia a los archivos JS desde los Recursos de nuestra librería.

Como recordarán, le dimos a estos archivos la propiedad de aparecer como Embeded Resources. Lo que hará .NET dentro de nuestra librería es generar un paquete WebResource.axd, donde se almacenará toda referencia a un recurso, ya sea una imagen, un script, una hoja de estilos... Cualquier objeto agregado como recurso.

RegisterClientScriptResource lleva dos parámetros: un tipo para el script y el recurso que se enlazará. El primero (que sinceramente no sé de qué depende) debe llevar el tipo de clase del que lo estamos llamando, en este caso, Acordeon. En el segundo, daremos el mismo nombre que pusimos en la referencia dentro del archivo AssemblyInfo.cs.

Después de agregar esas referencias, sigue crear un bloque de script que convierta nuestro elemento en un acordeón, lo cual se hace con el método RegisterClientScriptBlock del ScriptManager. El contenido de la función de javascript lo puse en una variable para que puedan verlo por separado.

Esta función tiene cuatro parámetros. El primero es el tipo, igual que en el método anterior.

El segundo parámetro es un identificador para este bloque de código. Si tenemos un script genérico, que puede ser utilizado en distintos controles, a todos le damos el mismo identificador. Antes de registrar el bloque, podemos revisar si ya está en la página, usando Page.ClientScript.IsClientScriptBlockRegistered("identificador") para verificarlo. En nuestro control, el bloque será único para esta clase (pues la operación incluye el id del control), así que no necesitamos condicionarlo.

En el tercer parámetro es el contenido en javascript, el mismo que puse en la variable string.

El último parámetro nos pregunta si queremos que se inserten automáticamente las etiquetas <script></script>. Yo pongo este parámetro en falso (y por eso escribí en el script éstas etiquetas), porque al hacerlo automáticamente, se crean algunas instrucciones de esas únicas para Internet Explorer, y eso es algo que no queremos como los buenos desarrolladores Web que somos cool

En cuanto al script, es la misma función que convierte nuestro control en un acordeón, como lo vimos arriba en el ejemplo de HTML. this.ClientID devuelve el id del elemento HTML, no el del WebControl. Si insertamos un acordeón de nombre acordeon1 en nuestra página, posiblemente ClientID sea igual que ID, pero si la página, por ejemplo, tiene un MasterPage y el acordeón está dentro de un ContentPlaceHolder, ClientID devolverá algo como "ctl00_ContentPlaceHolder1_acordeon1". Por eso no utilizamos ID, porque su valor no necesariamente es igual en el control HTML.

Al crearse nuestro control, en HTML se insertará la función $('#acordeon1').accordion(), para convertir nuestra lista, tal como lo deseamos, en un acordeón.

Ahora sí! Ya no sólo tenemos una lista de definiciones, sino que también tiene el efecto que queríamos! Pero para quienes no sepan cómo usarlo en nuestro sitio Web, se los explicaré.

Aplicación de nuestro acordeón a un sitio Web.

Ya terminado nuestro control, nos vamos al menú Build > Build Solution, o en el explorador de soluciones, le damos clic derecho al proyecto y seleccionamos Build. Si todo sale bien, se compilará el proyecto y generará una dll con el nombre de ensamblado (Assembly name) establecido en AssemblyInfo.cs (o en las propiedades del proyecto). Esta dll se guardará en la carpeta del proyecto, en la carpeta Bin\Debug. La dejaremos ahí por ahora.

En el sitio Web (si no lo tenemos, hay que crearlo... obviamente), le damos clic derecho en la raíz del sitio o en la carpeta Bin y seleccionamos "Add Reference...". Algunas personas se van con la finta de que hay una opción "Add Web Reference", pero eso es otra cosa, no se confundan.

En el cuadro que aparece, nos vamos a la pestaña Browse y, como si fuésemos a abrir un archivo, buscamos la dll en la carpeta del proyecto Acordeon. Presionamos OK y nuestra referencia se habrá creado.

Teniendo la referencia al proyecto, desde el código de servidor (C# o VB) podemos invocar a las clases que se encuentran en el proyecto.

  IsraSoft.MisControles.Acordeon ac = newIsraSoft.MisControles.Acordeon();

Pero bueno, la idea es agregarlos desde el editor de html. Podemos agregar la referencia en el archivo ASPx de esta forma:

    <%@ Page Language="C#" AutoEventWireup="true" Codebehind="Default.aspx.cs" Inherits="prueba._Default" %&rt;
    <%@ Register Assembly="Acordeon" Namespace="IsraSoft.MisControles" TagPrefix="ctrl" %&rt;

Assembly es el nombre del archivo sin el ".dll". Namespace es el que incluye nuestro control. TagPrefix es lo que irá en la etiqueta antes del nombre del control, así como para los demás WebControls está el prefijo "asp:".

    <body>
        <ctrl:Acordeon ID="ac1" runat="server">
            <ctrl:Elemento runat="server" Titulo="Elemento 1">
                <p>contenido del elemento 1</p>
            </ctrl:Elemento>
            <ctrl:Elemento runat="server" Titulo="Elemento 2">
                <h1>elemento 2</h1>
                <p>contenido del elemento 2</p>
            </ctrl:Elemento>
        </ctrl:Acordeon>
    </body>

Con esto, nuestro control se generará tal como lo esperamos (para los pesimistas, como debe generarse p).

Claro, han de pensar "qué flojera escribir esa línea en todas mis páginas". Pues para ustedes que se quieren ahorrar tiempo antes del Parkinson, podemos escribir esto sólo una vez, dentro de nuestro archivo de configuración.

Abrimos nuestro archivo web.config, o lo creamos, si aun no existe en el sitio Web. Hay una etiqueta <system.web>, dentro de la cual añadiremos esto:

    <pages>
      <controls>
        <add assembly="Acordeon" namespace="IsraSoft.MisControles" tagPrefix="ctrl" />
      </controls>
    </pages>

Es lo mismo que hacemos en la página, con la etiqueta @Register, sólo que esta vez aplicará para todas las páginas del proyecto. Sencillo, ¿no?


Bueno, ya terminamos. Creamos un sencillo acordeón para nuestras páginas Web. Pero esto sólo es una introducción a lo que es crear un control personalizado en ASP.NET. Hay más cosas que se pueden hacer para extender nuestra clase, por ejemplo, agregar todas las opciones que puede tener la función accordion de Jquery UI, y aplicarlas de acuerdo al valor dado en nuestro control. Podemos agregar propiedades de estilos también, sobre todo en Elemento, que crea dos etiquetas HTML.

Cada vez que recompilemos nuestra librería, en el sitio Web debemos ir a la carpeta Bin en el explorador de soluciones, dar clic derecho a la dll y seleccionar "Update Reference". Las dll en Bin no son exactamente referencias, sino copias de las dlls externas, por lo que debemos actualizarlas cada vez que hay un cambio en el origen.

Compilación en consola

Para los que nos hacemos los muy-muy, que somos la mera onda, y que decimos que no necesitamos Visual Studio y que hacemos nuestros programas en el bloc de notas... qué mentirosos somos... Pero si queremos apantallar, aquí les muestro cómo compilar nuestro proyecto desde consola de comandos.

Antes que otra cosa, sí. Se puede compilar código de C#/VB.NET desde el símbolo de sistema, cmd, el remedo de MS-DOS o como quieran llamarlo. .NET se instala en la carpeta Windows\Microsoft.NET\Framework\[version]\, donde encontraremos unos ejecutables csc.exe y vbc.exe, que son los compiladores.

En una carpeta almacenamos los archivos Acordeon.cs, AssemblyInfo.cs y los dos archivos de JavaScript. Luego nos vamos al símbolo de sistema y ejecutamos esta sentencia:

\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe /out:Acordeon.dll /target:library /res:jquery-1.3.2.js,IsraSoft.Recursos.jquery-1.3.2.js /res:jquery-ui-1.7.2.custom.min.js,IsraSoft.Recursos.jquery-ui-1.7.2.custom.min.js AssemblyInfo.cs Acordeon.cs
  • /out - Archivo destino de nuestro proyecto.
  • /target - Tipo de ensamblado. En este caso será una librería.
  • /res - Recursos. Se pone la ruta al archivo y separado con coma, el nombre de la referencia al recurso. En el caso de los archivos JS, la referencia es la misma que se usa en el AssemblyInfo y en la llamada desde el control Acordeon.
  • Al final se ponen los archivos a compilar. También se puede poner *.cs

De la misma forma que en Visual Studio, se creará una dll que agregamos a nuestro sitio Web, en la carpeta Bin. El resto es igual.

Para compilar código de Visual Basic (usando vbc.exe) no estoy seguro si es igual. Nunca lo he usado, así que se los quedaré debiendo.


Ahora sí, es todo sobre este tutorial. Sé que fue muy largo (mis dedos lo resienten), pero lo que quise hacer fue explicar lo necesario para dudas. No voy a pegar el código de un archivo sin explicarles cómo funciona, ni voy a hacer una guía como esas en las que me basé, que tratan de una sola cosa cada una, y que necesité unirlo todo yo solo sad

Me tardé mucho porque lo hice de poco en poco, en el poco tiempo que llegara a tener en la tarde, y el fin de semana no tuve tampoco mucho tiempo, por motivos personales. Al fin pude terminarlo.

Espero que esto les sirva como referencia para otros controles que tengan que hacer. Cualquier duda, pueden escribirme en los comentarios.

Sistema Solar en JavascriptCSS: body{display:none}

Comments

Anibalanibal784 Saturday, November 14, 2009 2:10:13 AM

No lo leí todo porque me maria a ésta altura de la nochie, mañana tal vez lo vea bien.

Algo que me resultó... curioso, ¿por qué mostrar cómo compilar por consola, si todo el trabajo fue hecho con la IDE?, no quiero decir que esté mal sino que hacer todo ese trabajo con el IDE para luego compilar por consola, no le veo lógica.

PD: pinta un muy buen trabajo.

el Israel-isra Tuesday, November 17, 2009 5:43:22 PM

Originally posted by anibal784:

¿por qué mostrar cómo compilar por consola, si todo el trabajo fue hecho con la IDE?

Fue en parte un chiste. Qué poca... gracia p

Peeero, como dije, en parte. Si por alguna razón programas en el bloc de notas (porque no tienes Visual Studio por no poder comprarlo legalmente, o simplemente si no te gusta usar un IDE, mostré la forma de compilarlo por consola.

Otra cosa: un amigo hizo alguna vez una prueba con una aplicación. Compilada en consola ocupa menos espacio en disco que compilada en el IDE, ya que éste último aplica muchas propiedades que tal vez tú no utilices o no consideres añadir (como propiedades del ensamblado, o el archivo pdb, usado para depuración, o varios xml que se generan con una compilación). No sé si con librerías sea lo mismo que con aplicaciones.

En todo caso, no está de más dar la explicación.

Saludos

Unregistered user Wednesday, November 18, 2009 3:23:13 PM

Karis writes: Hola Isra, felicidades esta muy entretenido tu blog, e ilustrativo este post, voy a hacer la prueba con este control y te cuento... Saludos, espero que te este llendo super bien!!, haber si te acuerdas de mi jeje... Karina Sierra.

el Israel-isra Wednesday, November 18, 2009 3:53:37 PM

Hola, ex patrona p
Ahí la llevo con el trabajo, y pues de lo que he aprendido trataré de ayudar.
Muchas gracias, y que a ti también te esté yendo bien en tu chamba yes
Y como decía un profesor en la carrera: si tienen dudas, aquí estaré para ampliarlas p

Saludos

Unregistered user Monday, February 22, 2010 4:21:48 PM

Cristian writes: Hola necesito alguien que sepa como conectar mi backup de base de datos de mi proveedor de hosting a mi SQL server 2005 manager studio, porque el problema que tengo que cuando intento restaurar el backup me dice que no son la misma base de datos, si alguien me puede ayudar se los agradeceria muho. mi correo c_cydejko@hotmail.com

el Israel-isra Monday, February 22, 2010 4:33:54 PM

RESTORE DATABASE [Base_de_Datos]
FROM DISK = 'C:\Databases\Database.bak' --Ruta física a la base de datos
WITH REPLACE

Unregistered user Sunday, April 18, 2010 3:15:48 PM

Valentin writes: Hola, buen dia, hice el tutorial pero al compilar la clase me marco un error: "Error 1 El nombre 'e' no existe en el contexto actual" dicho error hace referencia a la siguiente linea: base.CreateChildControls(e); en la siguiente funcion: protected override void CreateChildControls() { foreach (Elemento e in Elementos) { this.Controls.Add(e); } base.CreateChildControls(e); } se me olvido incluir algo? alguna referencia, o a que variable hace referencia la 'e'? ya que esta afuera de la 'e' del 'foreach' De antemano gracias :)

Anibalanibal784 Sunday, April 18, 2010 4:31:35 PM

Seguramente debe ser base.CreateChildControls(); sin el e como parámetro (estoy sacando conclusiones, pues no hice los pasos ni se bien qué hace cada método...).
La e, hace referencia a la variable del foreach, a menos que sea una variable de instancia (o clase/static, es por eso que a mi me gusta más hacer uso del apuntador this cuando hago referencia a atributos de un objeto).

http://msdn.microsoft.com/es-es/library/system.web.ui.control.createchildcontrols(VS.90).aspx

PD: Deberíamos esperar a que conteste el-isra que la tiene más clara.

el Israel-isra Sunday, April 18, 2010 5:23:57 PM

Originally posted by anibal784:

Deberíamos esperar a que conteste el-isra que la tiene más clara


Tampoco me alburées, wey

Efectivamente, es un error u.u Disculpen, me confundí otros métodos sobreescritos que sí utilizan parámetro, cuando escribí el post. Ya lo corregí
Lo correcto es base.CreateChildControls(), como dice Aníbal

Unregistered user Wednesday, April 21, 2010 8:12:41 PM

Anónimo writes: Hola, estoy intentando este tutorial en VB.NET, pero me marca error desde que inicio, al parecer no me acepta la clase WebControl - type 'WebControl' is not defined :( comparando tu proyecto con el mío yo no tengo la carpeta de referencias ni la de properties. Ya intente haciendo clic con botón derecho, add reference..., en la pestaña de .net selecciono system.web y doy clic en ok, pero la carpeta no aparece, la referencia tampoco, y el error continua apareciendo :( ayudaaaa!!!! DX

Unregistered user Wednesday, April 21, 2010 8:35:46 PM

Anónimo writes: Disculpen todos :( ya encontre mi error... aún si me pudieran decir porque no tengo las carpetas de referencias ni la de properties se los agradecería mucho. Saludos

el Israel-isra Wednesday, April 21, 2010 8:59:46 PM

Lo estás haciendo en VisualBasic.NET? Creo que en los de VB es donde no te aparece la lista de referencias, pero también las puedes ver en las propiedades del proyecto. Hay una sección en esa ventana sobre Referencias

Unregistered user Friday, April 23, 2010 1:24:45 AM

Matias writes: Hola, realmente no se si me gana el sueño o que pasa con mis dedos que parece no escriben algo bien. Tenes el codigo como para subirlo a algun lugar y ver que es lo que tiene distinto respecto al mio?? LLevo un par de horas intentando y no doy en la tecla. Saludos, Matias

el Israel-isra Friday, April 23, 2010 1:44:14 AM

Nunca se me ocurrió. Orita más al rato lo subo. Ve y duerme un rato p

el Israel-isra Friday, April 23, 2010 3:04:45 AM

Listo. El código fuente está en la parte superior del post. También corregí un error por falta de un símbolo ; aunque no creo que sea algo que no hubieran notado

Saludos

Unregistered user Friday, April 23, 2010 2:10:29 PM

Matias writes: Excelente, muchas gracias. Al final era una tonteria que estaba delante mio y no la veia. Una pregunta, en la parte visual de la página se muestra un error al cargar el control ( en el visual studio) que dice lo siguiente: "No se puede establecer 'ctrl:Acordeon' en la propiedad 'Elementos'". ¿te sucede eso y/o tenes idea del motivo? Gracias por la ayuda. Felicitaciones por el post. Saludos, Matias.

el Israel-isra Friday, April 23, 2010 4:08:37 PM

Originally posted by Matias:

en la parte visual de la página se muestra un error al cargar el control ( en el visual studio) que dice lo siguiente: "No se puede establecer 'ctrl:Acordeon' en la propiedad 'Elementos'". ¿te sucede eso y/o tenes idea del motivo?


Ya lo vi, pero no sabría decirte dónde está el problema. Acostumbro programar en la vista de código fuente, no en el diseñador.
Supongo que requiere su configuración para hacer que funcione correctamente en la vista de diseño, pero sinceramente, no sé cómo :S
Nota aparte, no te recomiendo usar la vista de diseño, ni en este programa ni en otros cmo Dreamweaver, sino manipular directamente el HTML. Estos generadores automáticos de código HTML nomás hacen un desastre en los archivos. No es tan difícil aprender estos lenguajes.

Saludos

Anibalanibal784 Friday, April 23, 2010 6:49:57 PM

Estos generadores automáticos de código HTML nomás hacen un desastre en los archivos. No es tan difícil aprender estos lenguajes.


Oia, si hasta te pareces a mi bigsmile, el editor de Visual Studio no es tan malo (repito NO es tan malo), el código que genera es... medianamente coherente y bastante ordenado. Igual siempre es bueno saber el código, más que nada para cuando el editor visual no te deja tomar cierto elemento, o no te hace caso, o te agrega cosas que tu no quieres: llámese, elementos p donde no van, elementos div donde no deberían ir, o cosas así.

Unregistered user Wednesday, August 11, 2010 2:29:35 PM

Alejandro Biondo writes: Perdón, pero no logro que me funcione bien en Visual Basic. Namespace IpsosControls.MisControles "), _ ParseChildren(TypeOf (Elemento) , True, "Elementos"), _ PersistChildren(False)> _ Me da un error en el typeof(Elemento) que dice que a continuación debo poner un IS, y no se me ocurre como corregirlo...

Unregistered user Tuesday, March 22, 2011 9:22:01 PM

Rey writes: muy buen post podrias decirme que debo de leer para saber mas al respecto o si tuvieras algun tutorial que me facilitaras se te agradece mi mail es rudy_silver@hotmail.com

Unregistered user Monday, May 9, 2011 11:16:59 PM

Lionel writes: Me funciona bien el acordion. Sólo tengo una duda. Como puedes acceder desde lado servidor aun valor seleccionado del acordeon, es decir en el server saber cual selecciono. algo como MiAcordeon.SelectedValue; o algo asi..para acceder a los valores del item seleccionado. Si tienes más info, podrías postearlo? Saludos.

Unregistered user Wednesday, August 17, 2011 8:31:52 PM

nelson Cardenas writes: Sencillamente el mejor tutorial de la web para crear controles personalizados, gracias desde colombia.

Unregistered user Monday, October 31, 2011 10:02:23 PM

Alvaro Cabrera writes: Amigo es muy buen artículo, Tengo una duda cómo invocar variables Public de UserControl desde la página host? Gracias....

Unregistered user Friday, November 25, 2011 8:58:38 PM

Noimporta writes: Excelente tutorial para hacer WebControls. Me respondiste algunas dudas que tenía respecto a hacer funcional el contenido de un control. Y como hacer la inyección de código script. Te sugeriría... Nah da lo mismo. Funcionó todo a la primera. Para los demás: señoritas, señores, no olviden compilar antes de usar. Gracias! PD: ¿Que el IDE no es tan malo? jajajaja! Perdón? si lo aprenden a usar bien verán que es de los mejores editores/generadores de código que se han hecho junto con Eclipse (favor evitar las críticas "religiosas" de que lenguaje es mejor si mundo Java o mundo .NET, me parece una discusión del todo absurda)

Write a comment

New comments have been disabled for this post.