Crear controles personalizados de ASP.NET Server con plantilla dataBound

 

Scott Mitchell
4GuysFromRolla.com

Marzo de 2004

Se aplica a:
   Microsoft® ASP.NET

Resumen: Examina el uso de plantillas en controles web de entrada de datos y crea un control de servidor personalizado que muestra los elementos de contenido sindicados mediante la especificación RSS. También se examinan los temas de preocupación al desarrollar controles de entrada de datos con plantilla, incluida la creación del contenido del control basado en datos externos, la generación de eventos durante la construcción del control y los eventos de propagación que se producen en las plantillas. (27 páginas impresas)

Descargue el código fuente de este artículo.

Contenido

Introducción
RssFeed: un control de servidor personalizado para mostrar fuentes de distribución RSS
Crear un control DataBound sin plantilla
Agregar plantillas al control DataBound
Conclusión
Acerca del autor

Introducción

Aunque Microsoft® ASP.NET los controles de servidor son capaces de mantener su estado entre postbacks y de generar eventos del lado servidor en respuesta a eventos del lado cliente, el objetivo principal y la tarea más importante para los controles de servidor es la representación. La representación es el proceso de generar el marcado HTML correspondiente y es algo que hacen todos los controles de servidor. De hecho, el código HTML devuelto al explorador desde una página web de ASP.NET es simplemente la suma del marcado de los controles de la página.

El CÓDIGO HTML emitido por un control de servidor suele basarse en los valores de sus propiedades. De hecho, el CÓDIGO HTML emitido por la mayoría de los controles web se basa solo en sus valores de propiedad. Por ejemplo, el control Web TextBox siempre emitirá un <elemento HTML de entrada> . Los atributos de este elemento pueden diferir en función de los valores de propiedad, pero no hay ninguna manera de que un desarrollador de páginas pueda cambiar radicalmente el HTML emitido de TextBox.

Sin embargo, hay controles que permiten un grado de control mucho más preciso sobre el HTML emitido. Los más comunes son los controles web de datos ( DataGrid, DataList y Repeater), que usan plantillas para permitir que el desarrollador de páginas personalice su HTML representado. Las plantillas pueden contener una combinación de sintaxis HTML, controles web y sintaxis de enlace de datos.

Al crear sus propios controles de servidor personalizados, es posible que desee proporcionar a los desarrolladores de páginas una mayor cantidad de flexibilidad para determinar la salida representada del control. En un artículo anterior, Building Templated Custom ASP.NET Server Controls, he examinado los conceptos básicos de los controles con plantilla y he visto un ejemplo de creación de un control con plantilla que no es de entrada de datos. En este artículo, examinaremos la creación de controles con plantilla de entrada de datos, la creación de un control de servidor personalizado para mostrar el contenido sindicado a través de RSS.

Nota Los controles web de datos son ejemplos de controles con plantilla de entrada de datos, ya que su contenido se construye a partir de un origen de datos y usan plantillas para personalizar y representar su marcado. Los controles con plantilla y los controles de entrada de datos son ortogonales; es decir, puede tener un control sin plantilla, un control no de entrada de datos o un control sin plantilla. Por ejemplo, DropDownList, RadioButtonList y CheckBoxList son ejemplos de controles de entrada de datos y no con plantilla. En Building Templated Custom ASP.NET Server Controls, he creado un control con plantilla no de entrada de datos que muestra las estadísticas del sitio web.

Nota RSS es una especificación para la sindicación de contenido mediante un formato XML. Para obtener más información sobre qué es RSS y dónde se usa, consulte un artículo anterior sobre la mía, Creación de un agregador de noticias RSS con ASP.NET.

RssFeed: un control de servidor personalizado para mostrar fuentes de distribución RSS

Para comprender en profundidad el proceso (y los problemas comunes) de crear un control con plantilla de entrada de datos, se recorrerá paso a paso la creación de un control de servidor personalizado que llamaré a RssFeed. RssFeed es un control con plantilla de entrada de datos que muestra una fuente de distribución RSS en una tabla HTML. Es muy fácil de usar; en su forma más sencilla, solo tiene que quitar el control en una página web de ASP.NET y, a continuación, en la clase de código subyacente, establecer su propiedad DataSource en la dirección URL de la fuente RSS y, a continuación, llamar al método DataBind().

private void Page_Load(object sender, System.EventArgs e)
{
   if (!Page.IsPostBack)
   {      
      RssFeed1.DataSource = "http://dotavery.com/blog/Rss.aspx";
      RssFeed1.DataBind();
   }
}

En la figura 1 se muestra la salida de RssFeed cuando se usa en su forma más sencilla. La apariencia de RssFeed se puede mejorar a través de sus numerosas propiedades estilísticas. Al igual que los controles web de datos, RssFeed tiene estilos como ItemStyle, AlternatingItemStyle, HeaderStyle, etc. En la figura 2 se muestra una vista más estéticamente agradable de RssFeed.

Aa479322.databoundtemplatedcontrols_fig01(en-us,MSDN.10).gifAa479322.databoundtemplatedcontrols_fig01

Figura 1. Salida simple de RssFeed

Aa479322.databoundtemplatedcontrols_fig02(en-us,MSDN.10).gifAa479322.databoundtemplatedcontrols_fig02

Ilustración 2. RssFeed con estilo

RssFeed no requiere que se usen plantillas. Las figuras 1 y 2 muestran la salida de RssFeed cuando no se especifica una plantilla. RssFeed tiene dos plantillas opcionales: ItemTemplate y HeaderTemplate. Estas plantillas se pueden usar para personalizar opcionalmente la apariencia del encabezado y los elementos RSS. Cada elemento RSS tiene una serie de propiedades: Title, Link, Description, PubDate y otros. Los valores de estas propiedades se pueden emitir en una plantilla mediante la siguiente sintaxis de enlace de datos: <%# Container.DataItem.PropertyName %>. Por ejemplo, el siguiente control RssFeed usa una plantilla para personalizar la presentación de las propiedades Title y PubDate :

<skm:RssFeed ...>
  <ItemTemplate>
    <strong><%# Container.DataItem.Title %></strong>
    <br>
    <i><%# Container.DataItem.PubDate.ToShortDateString() %></i>
  </ItemTemplate>
</skm:Rss>

La salida generada por esta versión con plantilla se puede ver en la figura 3.

Aa479322.databoundtemplatedcontrols_fig03(en-us,MSDN.10).gif

Figura 3. RssFeed después de aplicar una plantilla

El control RssFeed también proporciona tres eventos que los desarrolladores de páginas pueden usar para pulsar mediante programación en el control. Los nombres y la semántica de estos eventos son idénticos a tres eventos comunes en los controles web de datos:

  • ItemCreated. Se desencadena a medida que se crea cada elemento RSS.
  • ItemDataBound. Se activa una vez por cada elemento RSS después de que el elemento haya sido databound.
  • ItemCommand. Se desencadena si se generó un evento Command en la plantilla ItemTemplate de RssFeed. Esto podría ocurrir si un desarrollador de páginas agregaba un control Web Button, LinkButton o ImageButton a la plantilla que tiene establecido CommandName o CommandArgument . Cuando se hace clic en este botón, se devuelve la página web y se activa el evento ItemCommand de RssFeed.

En este artículo, verá los pasos que debe seguir como desarrollador de controles para crear un control de entrada de datos con plantilla. Hay una gran cantidad de temas carnosos para cubrir! Comencemos con un vistazo a la creación de un control de entrada de datos sin plantilla y, a continuación, vamos a agregar compatibilidad con plantillas al control de entrada de datos.

Nota El código fuente completo del control RssFeed se puede descargar mediante el vínculo al principio de este artículo. También hay un área de trabajo de GotDotNet con la versión más reciente del código, disponible en http://workspaces.gotdotnet.com/RssFeed. También hay una amplia documentación en línea para desarrolladores de páginas que usan RssFeed y un artículo, A Custom ASP.NET Server Control for Displaying RSS Feeds,that discusses using RssFeed in an ASP.NET Web page.

Crear un control DataBound sin plantilla

Hay numerosos controles web integrados, no con plantilla, ASP.NET web de entrada de datos, como RadioButtonList, DropDownList y CheckBoxList. Los controles de entrada de datos no con plantilla son útiles cuando desea que la salida del control se base en algún origen de datos, pero no es necesario proporcionar al usuario ningún grado de personalización del HTML representado. De hecho, RssFeed se creó inicialmente como un control de entrada de datos no con plantilla. Su HTML representado se corrigió como una tabla de varias columnas (como se vio en las figuras 1 y 2).

Los controles de entrada de datos son controles que proporcionan una propiedad DataSource que se puede asignar a algún conjunto de datos, junto con un método DataBind() que, cuando se invoca, enlaza dataSource al control. Este proceso de enlace se realiza normalmente mediante la enumeración de los datos, agregando algún tipo de "elemento" para cada registro de datos. Por ejemplo, la propiedad DataSource del control web DropDownList se puede establecer en los resultados de una consulta de base de datos. Llamar al método DataBind() del control recorre en iteración los resultados de DataSource, creando un listItem para cada registro. Cuando se representa el control, cada ListItem se representa como un elemento de opción> HTML< en un <elemento select>.

Por lo tanto, un aspecto común de los controles de entrada de datos es que son una composición de controles de "elemento". DataGrid, por ejemplo, se compone de una colección de DataGridItems. DataList, de DataListItems. RadioButtonList, CheckBoxList y DropDownList se componen de un conjunto de ListItems. El control RssFeed , como veremos, se compone de RssFeedItems.

En Building Templated Custom ASP.NET Server Controls ,he analizado las diferencias entre los controles representados y los controles compuestos. Los controles representados son aquellos cuyo marcado HTML se genera generando manualmente el marcado HTML adecuado. Los controles compuestos son controles que contienen un conjunto de controles secundarios y estos controles secundarios se delegan la responsabilidad de generar el marcado HTML. Dado que los controles de entrada de datos se componen de una serie de "elementos", donde cada elemento se agrega a la jerarquía de controles del control de entrada de datos, los controles de entrada de datos son controles compuestos.

Es importante tener un conocimiento firme de los controles compuestos y los métodos implicados en el trabajo con controles compuestos. Si aún no lo ha hecho, dedique un momento a leer Building Templated Custom ASP.NET Server Controls(Compilar controles personalizados personalizados de ASP.NET Server), especialmente la sección "Controles representados y controles compuestos".

La creación de un control de entrada de datos implica los tres pasos siguientes:

  1. Cree una propiedad DataSource .
  2. Invalide el método DataBind() y cree la jerarquía de controles.
  3. Invalide CreateChildControls() para compilar la jerarquía de controles.

Echemos un vistazo a cada una de estas tareas individualmente.

Creación de la propiedad DataSource

Al crear la propiedad DataSource , es importante decidir qué, precisamente, constituye los datos que se enlazarán al control. Para los controles web de datos, cualquier origen de datos que implemente IEnumerable o IListSource se puede enlazar al control. Los objetos que implementan IEnumerable incluyen matrices, los objetos del espacio de nombres System.Collections y DataReaders, entre otros. DataSet implementa IListSource. Los controles web de datos, a continuación, aceptan un dataSource de tipo objeto, pero en el descriptor de acceso set de la propiedad, se comprueba el objeto asignado para asegurarse de que es null, de tipo IEnumerable o de tipo IListSource.

La especificación RSS escribe un formato XML para codificar datos sindicados. Por lo tanto, los datos que procesa RssFeed serán un archivo XML. Normalmente, RSS se usa para sindicar contenido en línea, desde sitios web de noticias o blogs. Por lo tanto, el desarrollador de páginas debe poder especificar una dirección URL remota como origen de los datos. Dado que es posible que los datos no sean remotos, pero en realidad un archivo local, la propiedad DataSource también debe aceptar objetos XmlReader , objetos TextReader o objetos XmlDocument . Para controlar esto, nuestra propiedad DataSource será de tipo object, pero cuando se le asigne, comprobará que el valor asignado es del tipo adecuado.

object dataSource;

public virtual object DataSource
{
   get
   {
      return dataSource;
   }
   set
   {
      // make sure we're working with a string, XmlReader, or TextReader
      if (value == null || 
        value is string || 
        value is XmlReader || 
        value is TextReader || 
        value is XmlDocument)
         dataSource = value;
      else
         throw new ArgumentException("DataSource must be assigned a 
            string, XmlReader, or TextReader.");
   }
}

Observe que si el desarrollador de páginas intenta asignar un objeto al DataSource que no es uno de los tipos admitidos, se produce una excepción ArgumentException .

Invalidar el método DataBind()

Después de que un desarrollador de páginas asigne algunos datos al origen de datos, llamará al método DataBind() del control para enlazar los datos al control. DataBind() debe llamar primero al método OnDataBinding() para generar el evento DataBinding. Este es un paso importante, ya que el evento DataBinding hará que se evalúen las expresiones de enlace de datos que el desarrollador de páginas ha agregado a las plantillas del control.

A continuación, la jerarquía de controles debe borrarse y, a continuación, volver a generarse. La razón por la que debe borrarse es que el método CreateChildControls() puede que ya se haya ejecutado en este punto, que ya habría creado la jerarquía de controles. Después de volver a generar la jerarquía de controles, el último paso consiste en establecer la propiedad ChildControlsCreated del control en True, de modo que las llamadas futuras a EnsureChildControls() no vuelvan a generar la jerarquía.

public override void DataBind()
{
   base.OnDataBinding(EventArgs.Empty);

   // Create the control hierarchy.  First, clear out the child controls
   Controls.Clear();
   ClearChildViewState();
   TrackViewState();

   // Create the control hierarchy
   CreateControlHierarchy(true);

   // Mark the hierarchy as having been created
   ChildControlsCreated = true;
}

El método DataBind() crea la jerarquía de controles llamando al método CreateControlHierarchy(). Este método, que examinaremos en la sección siguiente, creará la jerarquía de controles.

Creación de la jerarquía de controles

La clase Control contiene un método CreateChildControls() cuya responsabilidad es crear la jerarquía de controles. Este método se puede invocar en varios lugares durante el ciclo de vida del control a través de una llamada al método EnsureChildControls(). EnsureChildControls() simplemente comprueba si la propiedad ChildControlsCreated es False. Si es así, se invoca el método CreateChildControls(). Se garantiza que se llama al método CreateChildControls(), al más reciente, durante la fase de representación previa del control.

Se encuentra en el método CreateChildControls() y, a continuación, debe construir la jerarquía de controles. En lugar de tener toda esta lógica dentro de este método, vamos a crear un método personalizado (CreateControlHiearchy()) que realice esta tarea. Por lo tanto, nuestro primer intento en el método CreateChildControls() tendría el siguiente aspecto:

protected override void CreateChildControls()
{
   // Clear out the control hiearchy
   Controls.Clear();

   // Build up the control hierachy
   CreateControlHierarchy();
}

Cada vez que se visita la página web de ASP.NET, el control RssFeed debe construir su jerarquía de controles. Como vimos anteriormente, el Page_Load controlador de eventos de la página web de ASP.NET llama al método DataBind() del control RssFeed en la primera carga. Pero imagine, por un momento, si esto no era el caso. El método CreateChildControls() de RssFeed seguiría ejecutándolo y el método CreateControlHiearchy() seguiría compilando la jerarquía de controles, aunque no lo quisiera necesariamente.

Por lo tanto, ¿significa que no necesita CreateChildControls() para llamar a CreateControlHierarchy() en absoluto? ¿Puede dejar que el método DataBind()controle la llamada a CreateControlHiearchy()? Imagine si este fue el enfoque que tomó. Cuando se visitó por primera vez una página web y se llamó a DataBind(), la jerarquía de controles se crearía correctamente. ¿Pero qué pasaría en el postback? Recuerde que el controlador de eventos Page_Load se escribió de forma que el método DataBind() se llamó solo en la primera visita a la página.

Nota Normalmente, solo se llama al método DataBind() de los controles de entrada de datos en la primera carga de página o cuando se produce algún evento que requiere que los datos se vuelvan a enlazar al control. Un ejemplo de llamada a DataBind() de nuevo en respuesta a algún evento sería cuando se usa una dataGrid ordenable. Si el usuario opta por ordenar los datos de otra manera, el evento SortCommand de DataGrid se activa y, en el controlador de eventos, el desarrollador de páginas vuelve a ordenar los datos y, a continuación, lo vuelve a enlazar a DataGrid.

CreateChildControls() y, a continuación, solo debe llamar a CreateControlHierarchy() cuando se publique la página. En este escenario, CreateControlHierarchy() tendrá que reconstruir los elementos del viewState del control. Sin embargo, si se llama a CreateControlHierarchy() desde el método DataBind(), debe construir sus elementos a partir del origen de datos. Para determinar si CreateControlHierarchy() debe construir la jerarquía a partir de DataSource, CreateControlHierarchy() aceptará un valor booleano: True para crear la jerarquía a partir del origen de datos, False para crearla a partir de ViewState.

A continuación se muestra el método CreateChildControls() final. Tenga en cuenta que solo se llama CreateControlHierarchy() en postback. (La variable ViewState RssItemCount se establece después de crear la jerarquía de controles y se conserva entre postbacks. Por lo tanto, en la primera página que visita antes de que se haya creado la jerarquía de controles, la variable ViewState RssItemCount será null y, por tanto, no se llamaráa CreateControlHierarchy().)

protected override void CreateChildControls()
{
   // Clear out the control hiearchy
   Controls.Clear();

   // see if we need to build up the hierarchy
   if (ViewState["RssItemCount"] != null)
      CreateControlHierarchy(false);
}

Nota Recuerde de la sección anterior "Invalidar el método DataBind(), que el método DataBind() llama a CreateControlHierarchy(), pasando un valor de true, ya que cuando se invoca este método desde DataBind(), el contenido del control debe compilarse a partir del origen de datos.

Creación del método CreateControlHierarchy()

El último paso, y lo más importante, en la creación de la jerarquía de controles es escribir el método CreateControlHierarchy(), que realiza todo el trabajo real de creación de la jerarquía de controles.

Antes de examinar el código algo largo, vamos a analizar primero lo que hace este método en inglés. El método comienza por determinar si el origen de datos se debe usar para construir la jerarquía de control o si la jerarquía debe crearse a partir de ViewState. Si se va a usar dataSource, se realiza una llamada a GetDataSource(). GetDataSource() devuelve un objeto ArrayList de objetos RssItem .

RssItem es una representación abstracta de un elemento RSS. Por ejemplo, un sitio de noticias podría usar RSS para sindicar sus últimas noticias. Cada artículo se considera un elemento RSS. Según la especificación RSS, los elementos RSS tienen propiedades como Title, Link, Description, Author, Category, PubDate (la fecha en que se publicó el elemento), etc. Recuerde que dataSource es datos XML. Básicamente, GetDataSource() recorre en iteración los datos XML y devuelve arrayList de instancias rssItem . Los detalles de GetDataSource() no son importantes; Simplemente tenga en cuenta que analiza el XML en un objeto ArrayList. Después de enlazar los datos desde dataSource, use una variable ViewState, RssItemCount, para contener el número de elementos enlazados al control.

Si la estructura del control se va a reconstruir a partir de ViewState, se crea un origen de datos ficticio, es decir, una matriz de tipo objeto con el número rssItemCount de elemento.

Recuerde de nuestros debates anteriores de este artículo que los controles de entrada de datos se componen de "elementos", que suelen ser controles web derivados de controles web que proporcionan el comportamiento de representación deseado. Por ejemplo, DataGrid se representa como una tabla> HTML< con cada elemento de DataGrid representado como una fila de la tabla. No es sorprendente que la clase DataGridItem se derive de TableRow. El control RssFeed se representa de forma similar al control DataGrid, como una tabla> HTML<. El control RssFeed se compone de controles RssFeedItem , que, al igual que el control DataGridItem , es un control web derivado de la clase TableRow .

El control RssFeed contiene un objeto ArrayList privado denominado rssItemsArrayList cuyo propósito es contener referencias a los elementos del control RssFeed . Este objeto ArrayList privado se mantiene porque el control RssFeed tiene una propiedad Items , que proporciona acceso mediante programación a estas instancias rssFeedItem . En el método CreateControlHierarchy(), el arrayList rssItemsArrayList se rellena con las instancias RssFeedItem agregadas al control RssFeed.

En el código siguiente se muestran las partes alemanas del método CreateControlHierarchy(). Se han omitido algunos bits para mayor brevedad.

protected virtual void CreateControlHierarchy(bool useDataSource)
{
   IEnumerable rssData = null;

   // Clear out and/or create the rssItemsArrayList
   if (rssItemsArrayList == null)
      rssItemsArrayList = new ArrayList();
   else
      rssItemsArrayList.Clear();
   

   // Get the rssData
   bool isValidXml = true;
   if (useDataSource)
   {
      // get the proper dataSource 
      //(based on if the DataSource is a URL, 
      // file path, XmlReader, etc.)
      rssData = GetDataSource();
   }
   else
   {
      // Create a dummy DataSource
      rssData = new object[(int) ViewState["RssItemCount"]];
      rssItemsArrayList.Capacity = (int) ViewState["RssItemCount"];
   }

   if (rssData != null)
   {
      // create a Table
      Table outerTable = new Table();
      Controls.Add(outerTable);

      // Add a header
      TableRow headerRow = new TableRow();
      TableCell headerCell = new TableCell();
      headerCell.Text = this.HeaderText;
         
      // Add the cell and row to the row/table
      headerRow.Cells.Add(headerCell);               
      outerTable.Rows.Add(headerRow);

      int itemCount = 0;
      foreach(RssItem item in rssData)
      {
         // Determine if this item is an Item or AlternatingItem
         RssFeedItemType itemType = RssFeedItemType.Item;
         if (itemCount % 2 == 1)
            itemType = RssFeedItemType.AlternatingItem;

         // Create the RssFeedItem
         RssFeedItem feedItem = CreateRssFeedItem(outerTable.Rows, 
           itemType, item, useDataSource);
         this.rssItemsArrayList.Add(feedItem);

         itemCount++;
      }

      // Instantiate the RssItems collection
      this.rssItemsCollection = new RssFeedItemCollection(rssItemsArrayList);

      // set the RssItemCount ViewState variable if needed
      if (useDataSource)
         ViewState["RssItemCount"] = itemCount;
   }
}

Tenga en cuenta que la jerarquía de controles RssFeed contiene un control Table , que luego contiene una sola fila de encabezado seguida de una fila para cada elemento de rssData ArrayList. Cada fila se crea en un bucle foreach . En cada iteración del bucle, se crea un nuevo RssFeedItem llamando al método CreateRssFeedItem(). Dediquemos un momento a examinar este método.

El propósito de CreateRssFeedItem() es crear una nueva instancia rssFeedItem y agregarla a la colección Rows de table. A continuación, si se crea la estructura del control a partir del origen de datos, la propiedad DataItem de RssFeedItem debe asignarse a la instancia rssItem actual y las columnas de la fila deben crearse y rellenarse con los datos del rssItem actual.

protected virtual RssFeedItem CreateRssFeedItem(TableRowCollection rows, 
  RssFeedItemType itemType, RssItem item, bool useDataSource)
{
   RssFeedItem feedItem = new RssFeedItem(itemType);
   RssFeedItemEventArgs e = new RssFeedItemEventArgs(feedItem);

   TableCell titleCell = new TableCell();
   TableCell pubDateCell = new TableCell();

   HyperLink lnkItem = new HyperLink();
   lnkItem.Target = this.Target;
   titleCell.Controls.Add(lnkItem);

   feedItem.Cells.Add(titleCell);
   if (ShowPubDate)
      feedItem.Cells.Add(pubDateCell);

   OnItemCreated(e);   // raise the ItemCreated event

   rows.Add(feedItem);

   if (useDataSource)
   {
      feedItem.DataItem = item;
   
      if (item.Link == String.Empty)
         titleCell.Text = item.Title;
      else
      {
         lnkItem.NavigateUrl = item.Link;
         lnkItem.Text = item.Title;
         titleCell.Controls.Add(lnkItem);
      }

      if (ShowPubDate)
         pubDateCell.Text = item.PubDate.ToString(this.DateFormatString);

      OnItemDataBound(e);      // raise the ItemDataBound event
   }

   return feedItem;
}

Con la conclusión de estos tres pasos, la creación de una propiedad DataSource , la invalidación del método DataBind() y la creación de la jerarquía de controles a través de CreateChildControls(), tiene un control web de entrada de datos en funcionamiento.

Estilos en controles DataBound

Los controles Web DataGrid y DataList permiten a los desarrolladores de páginas adaptar fácilmente la apariencia de la salida mediante el uso de estilos. Estos incluyen estilos de nivel superior que se aplican a todo el control Web, así como estilos más específicos, como ItemStyle, AlternatingItemStyle, HeaderStyle, FooterStyle, etc. Para RssFeed, hay tres propiedades de estilo de destino: HeaderStyle, ItemStyle y AlternatingItemStyle. Dado que RssFeed se representa como una tabla HTML, con su encabezado y elementos como filas en la tabla, estos tres estilos son de tipo TableItemStyle.

private TableItemStyle headerStyle = null;
private TableItemStyle itemStyle = null;
private TableItemStyle alternatingItemStyle = null;

public virtual TableItemStyle HeaderStyle
{
   get
   {
      if (headerStyle == null)
         headerStyle = new TableItemStyle();

      return headerStyle;
   }
}

public virtual TableItemStyle ItemStyle
{
   get
   {
      if (itemStyle == null)
         itemStyle = new TableItemStyle();

      return itemStyle;
   }
}

public virtual TableItemStyle AlternatingItemStyle
{
   get
   {
      if (alternatingItemStyle == null)
         alternatingItemStyle = new TableItemStyle();

      return alternatingItemStyle;
   }
}

Nota Tenga en cuenta que los estilos son propiedades complejas y, por lo tanto, requieren cuidado para asegurarse de que se almacenan correctamente en viewState del control RssFeed . (Este código se ha omitido en el ejemplo anterior por motivos de brevedad y, dado que solo consta de parte del código necesario para mantener correctamente el estado de estilo sobre postbacks).

Intuitivamente, es posible que piense que estos estilos se deben aplicar al encabezado y los elementos del control RssFeed a medida que se crean en los métodos CreateControlHierarchy() y CreateRssFeedItem(). Sin embargo, si lo hace, persistirá esta configuración de estilo en viewStates de los controles secundarios. Dado que el estilo también se conserva en viewState de RssFeed, tenerlo almacenado en los controles secundarios es desperdiciado, lo que conduce a viewStates innecesariamente hinchados (lo que afecta al tamaño y el tiempo de respuesta de la página web de ASP.NET).

Para evitar que los estilos se almacenen en viewStates de los controles secundarios, debe aplicar los estilos después de guardar ViewState. Esto significa que debe aplicar los estilos en la fase de representación. Por lo tanto, debe invalidar el método Render() y, desde allí, aplicar los estilos. Para simplificar el método Render(), vamos a crear otro método , PrepareControlHierarchyForRendering() para aplicar los estilos. A continuación, el método Render() llama a PrepareControlHierarchyForRendering() antes de representar su contenido.

protected override void Render(HtmlTextWriter writer)
{
   // Parepare the control hiearchy for rendering
   PrepareControlHierarchyForRendering();

   // We call RenderContents instead of Render() 
   // so that the encasing tag (<span>)
   // is not included; rather, just a <table> is emitted...
   RenderContents(writer);
}

En el método PrepareControlHierarchyForRendering(), debe hacer referencia mediante programación al control Table en la jerarquía de controles, agarrar el encabezado y aplicar su estilo y, a continuación, recorrer en iteración las filas restantes, aplicar el estilo adecuado (ya sea ItemStyle o AlternatingItemStyle).

protected virtual void PrepareControlHierarchyForRendering()
{
   // Make sure we have a control to work with
   if (Controls.Count != 1)
      return;

   // Apply the table style
   Table outerTable = (Table) Controls[0];
   outerTable.CopyBaseAttributes(this);
   outerTable.ApplyStyle(ControlStyle);

   // apply the header formatting
   outerTable.Rows[0].ApplyStyle(this.HeaderStyle);

   // Apply styling for all items in table, if styles are specified...
   if (this.itemStyle == null && this.alternatingItemStyle == null)
      return;

   // First, get alternatingItemStyle setup...         
   TableItemStyle mergedAltItemStyle = null;
   if (this.alternatingItemStyle != null)
   {
      mergedAltItemStyle = new TableItemStyle();
      mergedAltItemStyle.CopyFrom(this.itemStyle);
      mergedAltItemStyle.CopyFrom(this.alternatingItemStyle);
   }
   else
      mergedAltItemStyle = itemStyle;

   bool isAltItem = false;
   for (int i = 1; i < outerTable.Rows.Count; i++)
   {
      if (isAltItem)                           
         outerTable.Rows[i].MergeStyle(mergedAltItemStyle);
      else
         outerTable.Rows[i].MergeStyle(ItemStyle);

      isAltItem = !isAltItem;
   }
}

En el resto de este artículo se examina cómo agregar compatibilidad con plantillas, incluido cómo usar la propagación de eventos para responder a eventos que se producen dentro de una plantilla.

Agregar plantillas al control DataBound

En Building Templated Custom ASP.NET Server Controls (Compilar controles personalizados personalizados de ASP.NET), he examinado los pasos necesarios para agregar compatibilidad con plantillas a un control no de entrada de datos. Recuerde que esto implica los tres pasos siguientes:

  1. Crear una variable de miembro privado de tipo ITemplate.
  2. Creación de una propiedad pública de tipo ITemplate. Es a través de esta propiedad que el desarrollador de páginas especificará la sintaxis de marcado HTML, controles web y enlace de datos para la plantilla.
  3. Aplicación de la plantilla mediante el método InstatiateIn() de ITemplate.

Estos pasos son los mismos para agregar una plantilla a un control de entrada de datos.

Muchos controles de entrada de datos contienen más de una plantilla. DataList, por ejemplo, contiene un HeaderTemplate, FooterTemplate, ItemTemplate, AlternatingItemTemplate, etc. Para cada plantilla debe proporcionarse un control, se debe crear una variable de miembro privado independiente y se debe proporcionar una propiedad ITemplate pública independiente. Para RssFeed, vamos a usar solo dos plantillas: HeaderTemplate, que puede personalizar el marcado del encabezado; y ItemTemplate, que especifica el marcado personalizado para cada elemento (RssFeedItem) en el control RssFeed .

Para empezar, defina las variables de miembro privado y las propiedades ITemplate públicas para HeaderTemplate y ItemTemplate:

private ITemplate _itemTemplate;
private ITemplate _headerTemplate;

public ITemplate ItemTemplate
{
   get
   {
      return _itemTemplate;
   }
   set
   {
      _itemTemplate = value;
   }
}

public ITemplate HeaderTemplate
{
   get
   {
      return _headerTemplate;
   }
   set
   {
      _headerTemplate = value;
   }
}

A continuación, debe volver a los métodos CreateControlHierarchy() y CreateRssFeedItem() y, si es necesario, use los métodos InstiateIn() de las plantillas para representar el contenido del encabezado y del elemento. Digo, si es necesario, porque con RssFeed la plantilla es opcional. Sin especificar la plantilla, nos gustaría que RssFeed represente su contenido en la tabla estándar de varias columnas, como vimos en las figuras 1 y 2.

Para determinar si se ha especificado una plantilla, basta con comprobar si la variable de miembro privado aplicable es null o no. Si es null, no se ha proporcionado una plantilla. El siguiente fragmento de código procede del método CreateControlHierarchy() y crea el encabezado RssFeed a partir de HeaderTemplate si se proporciona HeaderTemplate.

protected virtual void CreateControlHierarchy(bool useDataSource)
{
   ...
   
   if (rssData != null)
   {
      // create a Table
      Table outerTable = new Table();
      Controls.Add(outerTable);

      // Add a header, if needed
      TableRow headerRow = new TableRow();
      TableCell headerCell = new TableCell();

      // see if we should use the template or the default
      if (_headerTemplate != null)
      {
         _headerTemplate.InstantiateIn(headerCell);
      }
      else
      {
         // add a default header
         ...
      }
         
      // Add the cell and row to the row/table
      headerRow.Cells.Add(headerCell);               
      outerTable.Rows.Add(headerRow);

      ...
   }
}

Tenga en cuenta que antes de crear el encabezado, compruebe si _headerTemplate es null. Si no es así, significa que el usuario ha especificado headerTemplate, por lo que crea una instancia de la plantilla en headerCell. Si no se ha proporcionado headerTemplate, se agrega el encabezado predeterminado. (En la sección "Crear un control dataBound no con plantilla", hemos examinado el código para crear este encabezado predeterminado).

En una vena similar, el método CreateRssFeedItem() comprueba si _itemTemplate es null o no y, en función de esa comparación, crea una instancia de la plantilla en la instancia rssFeedItem creada o compila mediante programación la interfaz RssFeedItem predeterminada.

protected virtual RssFeedItem CreateRssFeedItem(TableRowCollection rows, 
  RssFeedItemType itemType, RssItem item, bool useDataSource)
{
   RssFeedItem feedItem = new RssFeedItem(itemType);
   RssFeedItemEventArgs e = new RssFeedItemEventArgs(feedItem);

   // see if there is an ItemTemplate
   if (_itemTemplate != null)
   {
      TableCell dummyCell = new TableCell();

      // instantiate in the ItemTemplate
      _itemTemplate.InstantiateIn(dummyCell);

      feedItem.Cells.Add(dummyCell);

      OnItemCreated(e);   // raise the ItemCreated event

      rows.Add(feedItem);

      if (useDataSource)
      {
         feedItem.DataItem = item;
         feedItem.DataBind();

         OnItemDataBound(e);      // raise the ItemDataBound event
      }
   }
   else
   {
      // manually create the item
      ...
   }

   return feedItem;
}

Tenga en cuenta que, con la plantilla, si va a crear el control desde dataSource, se asigna el rssItem actual a la propiedad DataItem de RssFeedItem creada y, a continuación, se llama al método DataBind() de RssFeedItem. Esto hará que se resuelva cualquier sintaxis de enlace de datos de la plantilla. Y eso es todo lo que hay que agregar compatibilidad con plantillas.

En este momento, ha mejorado el control para que permita al desarrollador de páginas personalizar el HTML representado mediante plantillas. Pero, ¿qué ocurre si el desarrollador de páginas quiere agregar, por ejemplo, un control Web button dentro de la plantilla y, a continuación, responder mediante programación a su evento de clic? Como es probable que sepa, DataGrid, DataList y Repeater tienen un evento ItemCommand que se desencadena si se genera un evento Command desde dentro de los intestinos de sus plantillas. Echemos un vistazo a cómo extender RssFeed para admitir también el evento ItemCommand .

Detección y propagación de eventos

Hay numerosas acciones que pueden hacer que un control web genere un evento. Si un surfista web, por ejemplo, hace clic en un control Web button que provoca un postback, tras la devolución de ese evento Command de Button se activará. ¿Qué debe ocurrir si uno de los controles secundarios de un control compuesto genera un evento? El modelo de control de servidor ASP.NET usa la propagación de eventos para percolar el evento a través de la jerarquía de control hasta que algún control dicta que se detendrá la propagación.

La propagación de eventos se realiza a través de dos métodos: OnBubbleEvent() y RaiseBubbleEvent(). RaiseBubbleEvent(), como su nombre implica, propaga un evento al elemento primario del control. El método OnBubbleEvent() de un control compuesto se ejecutará si uno si sus controles secundarios propagan un evento. Para aclarar las cosas, veamos un ejemplo sencillo. Supongamos que tiene un control compuesto p, que tiene un control secundario c. Ahora, imagine que c desencadena un evento que se propaga hasta su elemento primario a través de una llamada a RaiseBubbleEvent(). Cuando el evento se propaga, se ejecutará el método OnBubbleEvent() de p.

El método OnBubbleEvent() devuelve un valor booleano que indica si se va a cancelar la propagación. Un valor de True, entonces, detiene la propagación; Un valor de False propaga automáticamente el evento hacia arriba. La implementación predeterminada de OnBubbleEvent() simplemente devuelve False. Sin embargo, puede invalidar este método para inspeccionar un evento de propagación y determinar si desea detenerlo y, quizás, generar un evento diferente.

Esta técnica se usa en DataGrid, DataList y Repeater para controlar el evento Command de Botones, LinkButtons e ImageButtons dentro de los controles. Dado que el evento Command del botón llama a RaiseBubbleEvent(), esto aplica el evento al elemento primario del botón. Las clases que componen los elementos de DataGrid, DataList y Repeater ( DataGridItem, DataListItem y RepeaterItem) invalidan onBubbleEvent() para detectar eventos de propagación. Si se ha propagado un evento Command , estas clases de elemento detienen la propagación y crean una instancia de EventArgs adecuada. (DataGridItemCommandEventArgs para DataGrid, DataListItemCommandEventArgs para DataList y RepeaterItemCommandEventArgs para repeater). A continuación, esto se propaga hasta DataGrid, DataList o Repeater. DataGrid, DataList y Repeater también invalidan OnBubbleEvent(); al obtener un evento en cola, se detiene la propagación y se genera el evento ItemCommand del control web de datos.

Con esta comprensión de cómo controla los eventos de burbujas web de datos, echemos un vistazo a los pasos necesarios para agregar un evento ItemCommand al control RssFeed . En primer lugar, debe invalidar el método OnBubbleEvent() de la clase RssFeedItem. Aquí debe escuchar eventos de comando de propagación. Al obtener este evento, empaquete los detalles en una instancia RssFeedItemCommandEventArgs y, a continuación, percolate el evento al control RssFeed a través de una llamada a RaiseBubbleEvent(). (RssFeedItemCommandEventArgs es una clase creada en el proyecto derivado de CommandEventArgs. Tiene las mismas propiedades que DataGridItemCommandEventArgs: Item, una referencia al RssFeedItem cuyo botón contenedor ha disparado el evento; CommandSource, una referencia al botón que generó el evento; y CommandName y CommandArgument, que tienen los valores de las propiedades CommandName y CommandArgument del botón).

protected override bool OnBubbleEvent(object source, EventArgs args)
{
   // only bother bubbling appropriate events
   if (args is CommandEventArgs)
   {
      RssFeedItemCommandEventArgs e = new RssFeedItemCommandEventArgs(this, source, (CommandEventArgs) args);
      base.RaiseBubbleEvent(this, e);

      return true;
   }
   else
      return false;
}

Como puede ver, OnBubbleEvent() comprueba si eventArgs entrante es de tipo CommandEventArgs. Si es así, crea una nueva instancia rssFeedItemCommandEventArgs y la propaga hasta el elemento primario de RssFeedItem. (Recuerde que el elemento primario de RssFeedItem es realmente un control Table . A continuación, el control Table propaga el evento a su elemento primario, el control RssFeed ). Si el evento se propaga, el método devuelve True, finalizando el proceso de propagación del evento Command . Si es un evento distinto de un evento Command , OnBubbleEvent() devuelve False, lo que permite que el bubbling continúe sin tratar.

Todo lo que queda es que se invalide el método OnBubbleEvent() del control RssFeed. Cuando se propaga una instancia rssFeedItemCommandEventArgs , es el momento de generar el evento ItemCommand y finalizar la propagación; de lo contrario, deje que la propagación continúe sin obstáculos.

protected override bool OnBubbleEvent(object source, EventArgs args)
{
   // only bother bubbling appropriate events
   if (args is RssFeedItemCommandEventArgs)
   {
      OnItemCommand((RssFeedItemCommandEventArgs) args);
      return true;
   }
   else
      return false;
}

Ahora que el control RssFeed genera un evento ItemCommand , puede agregar botones a ItemTemplate y responder mediante programación a su clic. A continuación se muestra un ejemplo sencillo que ilustra esto. Imagine que tiene configurado ItemTemplate para mostrar un botón Visitar para cada elemento. Cuando el usuario hace clic en este botón, el objetivo es hacer que se marquen en la dirección URL del elemento RSS (que se almacena en la propiedad Link ). La plantilla siguiente crearía un botón Visitar para cada elemento RssFeed , pasando la propiedad Link en commandName.

<skm:RssFeed ...>
  <ItemTemplate>
    <strong><%# Container.DataItem.Title %></strong>
    <br>
    <asp:Button runat="server" Text="Visit"
        CommandName='<%# Container.DataItem.Link %>'>
    </asp:Button>
  </ItemTemplate>
</skm:Rss>

A continuación, querrá conectar el evento ItemCommand de RssFeed a un controlador de eventos. El código de este controlador de eventos sería dolorosomente sencillo: solo una respuesta simple.Redirect() al valor de CommandName del botón en el que se ha hecho clic.

private void blog_ItemCommand(object sender, skmRss.RssFeedItemCommandEventArgs e)
{
   Response.Redirect(e.CommandName);
}

Conclusión

En este artículo hemos visto cómo, en primer lugar, crear un control de entrada de datos y, a continuación, cómo agregar compatibilidad con plantillas a este control de entrada de datos. En concreto, disectamos RssFeed, un control de servidor ASP.NET personalizado diseñado para mostrar información de una fuente de distribución RSS. Al crear un control con plantilla de entrada de datos, me resulta útil crear primero el control como un control de entrada de datos y, después, agregar compatibilidad con plantillas. Como se describe en la sección "Crear un control de entrada de datos no con plantilla", la creación de un control de entrada de datos implica tres pasos:

  1. Cree una propiedad DataSource .
  2. Invalide el método DataBind().
  3. Invalide el método CreateChildControls() y proporcione un método para crear la jerarquía de controles.

Agregar compatibilidad con plantillas a un control de entrada de datos no es especialmente difícil. Comience agregando una variable miembro privada y la propiedad pública correspondiente de tipo ITemplate para cada plantilla que el control debe admitir. A continuación, en el método CreateChildControls(), cree una instancia de la plantilla en el elemento de entrada de datos (RssFeedItem, para este control).

Los controles de entrada de datos son un medio útil para ayudar a ASP.NET desarrolladores de páginas a mostrar datos estructurados en un formato web presentable. Agregar compatibilidad con plantillas al control de entrada de datos concede a los desarrolladores de páginas un alto grado de libertad para crear el marcado HTML resultante del control.

Acerca del autor

Scott Mitchell, autor de cinco libros y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft durante los últimos cinco años. Scott trabaja como consultor independiente, entrenador y escritor. Puede acceder a él en mitchell@4guysfromrolla.com o a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.

© Microsoft Corporation. Todos los derechos reservados.