Blog dedicado a la programación .NET y la informática en general

Archivo para agosto, 2010

Generación de controles web personalizados ASP.NET (WebCustomControl)

Este probablemente sea mi primer mensaje del blog con cierto contenido ya extenso.

La generación de controles personalizados en .NET no es que sea algo precisamente trivial por eso he creado esta entrada en mi blog.

Aquí pretendo dar las indicaciones necesarias para crear un sencillo control web personalizado desde cero y llegar a un nivel que creo aceptable y que abrirá las puertas para llegar a hacer controles realmente profesionales.

Nos iniciaremos primero leyendo lo que MSDN nos indica sobre la clase WebControl, incluso ya puestos, si controlamos algo de inglés podemos ver el siguiente video webcast de Microsoft.

La clase WebControl es la principal clase de la que se debe heredar cuando queremos hacer un control web personalizado.

Trabajaré sobre un control que he tenido que generar para mi trabajo. Se trata de un control que filtra los resultados obtenidos a ser mostrados en un control DataGrid. El lector debe tener en cuenta que el proyecto en el que estaba trabajando comenzó con el Framework 1.1 y que por tanto se utilizan algunos conceptos ya obsoletos en Framework 4.0, pero lo importante es igual de válido para cualquiera de ellos.

Lo primero que haremos desde Visual Studio será agregar un nuevo elemento a nuestro proyecto (entiendo que el lector tiene ciertos conocimientos de programación en .NET y obviaré ciertos detalles) que será un “Control Web personalizado”. La clase resultante nos da una idea de por dónde debemos ir, pero en absoluto deja completo un control web.

La intención del control que he utilizado en este ejemplo es asociarle un control DataGrid (aunque podría haberse hecho perfectamente con un GridView) y que de forma automática lea las columnas que tiene asociadas para agregarlas a un control DropDownList de donde el usuario pueda elegir la columna por la que quiere filtrar la información a mostrar en el control DataGrid. Luego el valor a filtrar se agregará a un control TextBox y podrá indicar si quiere que la búsqueda sea susceptible a mayúsculas y minúsculas. Se realizará el filtrado una vez que haga click en un control Button. Todas las búsquedas sobre las columnas se hará como si fueran texto y mediante la sentencia LIKE ‘*valor*’ que está descrita en la propiedad Expression de la clase DataColumn. Además el control que se genera será el encargado de paginar y ordenar el DataGrid según lo indique el usuario. El origen de datos va a ser un fichero XML con los datos de Personas y que estará definido en un DataSet tipado. Como medio intermedio se agrega una clase preparada para ser consumida por un objeto ObjectDataSource.

Comenzamos la programación definiendo parámetros por defecto

[DefaultProperty("Titulo")]
[DefaultEvent("GetMeDataSource")]
[ToolboxData("<{0}:FiltroDataGrid runat=server></{0}:FiltroDataGrid>")]

public
class FiltroDataGrid : WebControl, INamingContainer

La propiedad por defecto es “Título” aunque no le he encontrado mucha utilidad, y para cuando se haga doble click en el control en tiempo de diseño se le ha añadido el evento por defecto “GetMeDataSource”, que de hecho va a ser el único evento que lance el control. Cabe destacar la herencia de la interfaz “INamingContainer” la cual se encargará que nuestros identificadores especificados en el control no se pisen con los identificadores que contenga la página de por sí. Yo de todas formas me he encargado que los identificadores sean únicos en el código asociándole el identificador del control.

Continuamos especificando las propiedades públicas que va contener el control. Lo único a destacar en este código es que dichas propiedades se guardan en el “ViewState” de la página que contenga el control.

#region Propieades Públicas
/// <summary>
/// Título del filtro
/// </summary>
[Bindable(true)]
[Category("Objetos")]
[DefaultValue("Poner título")]
[Localizable(true)]
public string Titulo
{
    get
    {
        string s = (string)ViewState[this.ID + ".Titulo"];
        return (s ?? string.Empty);
    }

    set
    {
        ViewState[this.ID + ".Titulo"] = value;
    }
}

/// <summary>
/// Identificador del DataGrid asociado para el filtrado
/// </summary>
[Bindable(true)]
[Category("Objetos")]
public string GridName
{
    get
    {
        return ViewState[this.ID + ".GridName"] as string;
    }

    set
    {
        ViewState[this.ID + ".GridName"] = value;
    }
}
#endregion

Ahora toca definir los controles que van a entrar en juego en la funcionalidad del control. En este punto debemos tener en cuenta que los controles deben permanecer como variables globales dentro de la clase, sino no formarán parte del ViewState y será imposible recuperar en la parte del servidor la información que haya sido introducida por el usuario desde la parte cliente.

#region Controles Persistentes
private DropDownList ddlColumna;
private TextBox txtValor;
private Button btnFiltrar;
private CheckBox chkCaseSensitive;
#endregion

Continuamos dibujando los controles en la parte cliente. Cuando se crea la clase WebCustomControl por defecto sólo nos genera el método sobrescrito “RederContents”, que está muy bien para definir código html pero que luego en tiempo de diseño no muestra los controles que introduzcamos en “CreateChildControls”. El código para el renderizado es el siguiente:

#region Métodos de renderizado
protected override void CreateChildControls()
{
    // Listado de columnas
    ddlColumna = new DropDownList();
    ddlColumna.ID = this.ID + "Ddl";
    if (this.Grid != null)
    {
        this.Grid.PageIndexChanged += Grid_PageIndexChanged;
        this.Grid.SortCommand += Grid_SortCommand;
        ddlColumna.Items.Add(new ListItem("- Ninguno -", string.Empty));
        foreach (BoundColumn gridColumn in this.Grid.Columns)
        {
            ddlColumna.Items.Add(new ListItem(gridColumn.HeaderText, gridColumn.DataField));
        }
    }
    this.Controls.Add(ddlColumna);
    // Cuadro de texto para el valor de filtrado
    txtValor = new TextBox();
    txtValor.ID = this.ID + "Txt";
    this.Controls.Add(txtValor);
    // Chek para distinguir entre mayúsculas y minúsculas
    chkCaseSensitive = new CheckBox();
    chkCaseSensitive.Text = "Distinguir may./min.";
    this.Controls.Add(chkCaseSensitive);
    // Botón para actualizar los datos filtrados
    btnFiltrar = new Button();
    btnFiltrar.Text = "Filtrar";
    btnFiltrar.CausesValidation = false;
    btnFiltrar.Click += btnFiltrar_Click;
    this.Controls.Add(btnFiltrar);
    // Llama al método base
    base.CreateChildControls();
}

protected override void RenderContents(HtmlTextWriter output)
{
    // Ejemplo de cómo adaptar el estilo del control al Grid
    string style = "style=\"background:#" + this.Grid.HeaderStyle.BackColor.R.ToString("X") + this.Grid.HeaderStyle.BackColor.G.ToString("X") + this.Grid.HeaderStyle.BackColor.B.ToString("X") + "\"";
    if (this.Grid.Width.Value > 0)
    {
        style = "style=\"background:#" + this.Grid.HeaderStyle.BackColor.R.ToString("X") + this.Grid.HeaderStyle.BackColor.G.ToString("X") + this.Grid.HeaderStyle.BackColor.B.ToString("X") + "; width:" + this.Grid.Width + "\"";
    }
    // Presenta visualmente el contenido del control
    string salida = string.Format("\r\n<div id=\"" + this.ID + "Div\"{1}>{0}<br />\r\n", Titulo, string.IsNullOrEmpty(style) ? string.Empty : style);
    output.Write(salida);
    base.RenderContents(output);
    salida = "\r\n</div>";
    output.Write(salida);
}

protected override void Render(HtmlTextWriter writer)
{
    // De esta forma nos aseguramos que se muestra en tiempo de ejecución
    EnsureChildControls();
    base.Render(writer);
}
#endregion

Como nota al código explicaré que Grid es un objeto que nos devuelve el DataGrid asociado al control mediante la propiedad “GridName”. Lo que hace básicamente es buscar en la página que contiene al control un control con el identificador indicado en la propiedad “GridName”. Ojo porque si la página que lo contiene es una “MasterPage” no será suficiente son buscarlo con el método “FindControl”.

El truco para que se muestre el control tal cual en tiempo de diseño es sobrescribir el método “Render” y usar “EnsureChildControls”, esto se asegura que antes de renderizar el control se han generado todos los controles secundarios del mismo. De otra forma sólo se verá el código html que hayamos descrito en “RenderContents”.

El resto del código es para gestionar los eventos de los controles, paginar, filtrar y ordenar el DataGrid y lanzar un evento para que se vuelvan a pasar los datos que mostrará el “DataGrid” en su propiedad “DataSource”.

Creo que hasta aquí lo explicado es suficiente para su entendimiento pero si alguien se queda con ganas de más o tiene dudas al respecto, gustosamente le atenderé.

A partir de aquí se puede hacer que el control se vea en la barra de herramientas de Visual Studio con un icono que nosotros le indiquemos, o también se le puede asociar una plantilla de diseño para modificar ciertas propiedades, pero esto ya es algo más avanzado y creo que la información que hay en la red es suficiente como para yo incluirla en este primer contacto.

Espero que os haya servido de ayuda.

Anuncio publicitario

Asociar un StrongName a un ensamblado ya creado

En cierta ocasión he necesitado asociar un StrongName a un proyecto para luego publicarlo en la GAC. El caso es que en ese momento no pude porque tenía referenciada una librería que no tenía asociada otro StrongName. ¿Qué puede hacerse cuando no se tiene el código fuente?. Una posible solución, aunque no infalible es desemsamblar el fichero y luego volverlo a ensamblar asociándole un nombre fuerte. ¿No se puede firmar de otra forma el ensamblado?, pues yo no he encontrado otra forma. Y OJO, porque firmar digitalmente un ensamblado que no es nuestro puede ser ilegal a menos que lo autorice el propietario.
 
Los contras son que probablemente lo podáis desemsamblar con normalidad pero luego no lo podáis reensamblar, si alguno lo prueba ya me contará.
 
Los comandos son los siguientes:
  1. Primero desemsamblar: ildasm libreria.dll  /out:libreria.il
  2. Se puede comprobar que se genera un fichero .il y puede haber generado un fichero de recursos .res
  3. Ahora toca reensamblar el fichero asociándole un nombre fuerte para firmarlo digitalmente: ilasm libreria.il  /res:libreria.res  /dll  /key:nombreFuerte.snk  /out:libreria.dll
  4. Y por último se puede comprobar que efectivamente tiene el nombre fuerte asociado con el siguiente comando: sn -vf libreria.dll

Dejo un vínculo a información más detallada sobre la herramienta ilasm.

Espero que sea de utilidad.

A %d blogueros les gusta esto: