Compartir a través de


Creación de controles personalizados de ASP.NET Server personalizados de 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 elementos de contenido sindicados mediante la especificación RSS. También examina 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
Creación de un control DataBound sin plantilla
Agregar plantillas al control DataBound
Conclusión
Acerca del autor

Introducción

Aunque los controles de servidor de Microsoft® ASP.NET 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 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 TextBox Web siempre emitirá un elemento HTML <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 CÓDIGO HTML emitido del TextBox.

Sin embargo, hay controles que permiten un grado de control mucho más fino sobre el HTML emitido. Los más comunes son los controles web de datos ( los DataGrid, DataListy Repeater, que usan plantillas de 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 quiera proporcionar a los desarrolladores de páginas una mayor 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 examinado un ejemplo de creación de un control con plantilla sin 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 con plantilla, no de entrada de datos o de entrada de datos, no con plantilla. Por ejemplo, elDropDownList , RadioButtonListy 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 sin 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 lo que 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 obtener un conocimiento profundo del proceso (y 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. La figura 2 muestra una vista más estéticamente agradable de RssFeed.

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

Figura 1. Salida sencilla de RssFeed

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

Figura 2. RssFeed con estilo

RssFeed no requiere que se usen plantillas. Las figuras 1 y 2 muestran RssFeedel resultado cuando no se especifica una plantilla. RssFeed tiene dos plantillas opcionales: ItemTemplate y HeaderTemplate. Estas plantillas se pueden usar para personalizar opcionalmente la apariencia de los elementos de encabezado y RSS. Cada elemento RSS tiene varias propiedades: Title, Link, Description, PubDate, etc. 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 activa a medida que se crea cada elemento RSS.
  • ItemDataBound. Se activa una vez para 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 puede ocurrir si un desarrollador de páginas agregó unbutton de , LinkButtono ImageButton control web a la plantilla que tiene su CommandName o CommandArgu ment establecido. Cuando se hace clic en este botón, se devuelve la página web y se desencadena el evento RssFeedItemCommand.

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 muchos temas de carne que 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 de 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 RssFeedy un artículo, Un control de servidor personalizado de ASP.NET para mostrar fuentes RSS, que describe el uso de RssFeed en una página web de ASP.NET.

Creación de un control DataBound sin plantilla

Hay numerosos controles web integrados, no con plantilla, ASP.NET web de entrada de datos, como el RadioButtonList, DropDownListy 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 sin 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 el 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 DropDownList control web DataSource se puede establecer en los resultados de una consulta de base de datos. Al llamar al método DataBind() del control, se recorre en iteración los resultados del DataSource, creando un listItem de para cada registro. Cuando se representa el control, cada listItem se representa como una opción de <HTML> elemento de un <seleccionar> elemento.

Por lo tanto, un aspecto común de los controles de entrada de datos es que son una composición de controles "item". EldataGrid de , por ejemplo, se compone de una colección de DataGridItems. DataList, de DataListItems. LosRadioButtonList , CheckBoxListy DropDownList se componen de un conjunto de s de ListItem. 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 mediante la generación manual del 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 trabajar con controles compuestos. Si aún no lo ha hecho, dedique un momento a leer creación de 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 el 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 de la System.Collections espacio de nombres y DataReaders, entre otros. El DataSet de implementa IListSource . Los controles Web de datos, después, 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 detalla 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 distribuir 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 objeto, pero cuando se asigne a , 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 a la dataSource de que no es uno de los tipos admitidos, se produce un argumentException.

Invalidación del método DataBind()

Después de que un desarrollador de páginas asigne algunos datos al DataSource, llamará al método DataBind() del control para enlazar los datos al control. El DataBind() debe llamar primero al método OnDataBinding() para generar el evento DataBinding. Este es un paso importante, ya que el evento DataBinding provocará 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 haber ejecutado ya 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 es 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 realiza 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 controlador de eventos Page_Load de la página web de ASP.NET llama al método RssFeed del control DataBind() en la primera carga. Pero imagine, por un momento, si esto no era el caso. El método CreateChildControls() de RssFeed seguirá 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() llamar a CreateControlHierarchy()? ¿Puede dejar que el método DataBind() controle la llamada a CreateControlHiearchy()? Imagine si este era 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é sucedería en postback? Recuerde que el controlador de eventos Page_Load se escribió de forma que solo se llamó al método DataBind() en la primera visita a la página.

Nota Normalmente, el método DataBind() de controles de entrada de datos solo se llama 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 un DataGrid ordenable. Si el usuario opta por ordenar los datos de una manera diferente, el DataGridde evento SortCommand 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 la DataGrid.

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

A continuación se muestra el método final CreateChildControls(). Tenga en cuenta que solo se llama createControlHierarchy() en postback. (La variable RssItemCount ViewState 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 crear la jerarquía de controles, la variable RssItemCount ViewState será nula y, por lo tanto, CreateControlHierarchy()no se llamará).

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 Recuperación de la sección anterior "Invalidación del 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 generarse a partir del DataSource.

Creación del método CreateControlHierarchy()

El último, y el paso más importante, en la creación de la jerarquía de controles es escribir el método CreateControlHierarchy(), que realiza todas las tareas reales 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 dataSource debe usarse para construir la jerarquía de controles o si la jerarquía debe compilarse desde ViewState. Si se va a usar el dataSource de , se realiza una llamada a GetDataSource(). GetDataSource() devuelve un arrayList de objetos RssItem.

Un 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 el dataSource de es datos XML. Básicamente, GetDataSource() recorre en iteración los datos XML y devuelve un arrayList de instancias de 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 de 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 RssItemCount número de elemento.

Recuerde de nuestras discusiones anteriores de este artículo que los controles de entrada de datos están formados por "elementos", que normalmente son controles web derivados de controles web que proporcionan el comportamiento de representación deseado. Por ejemplo, DataGrid se representa como una tabla de <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 de DataGrid, como una tabla <HTML>. El control rssFeed de se compone de controles RssFeedItem, que, al igual que el control DataGridItem , es un control web derivado de la clase TableRow.

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

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 de contiene un control Table , que luego contiene una sola fila de encabezado seguida de una fila para cada elemento de la 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 de RssFeedItem y agregarla a la colección Rows RowsTable. A continuación, si se crea la estructura del control a partir de ladataSource de , se debe asignar a la RssFeedItemla propiedad dataItem dataItem actual RssItem 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: crear una propiedad DataSource, invalidar el método DataBind() y crear 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 DataGrid y DataList Web 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, ItemStyley 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 están almacenados correctamente en el control ViewState de RssFeed. (Este código se ha omitido en el ejemplo anterior para mayor brevedad y porque solo consta de parte del código necesario para mantener correctamente el estado de estilo sobre postbacks).

De forma intuitiva, es posible que piense que estos estilos deben aplicarse al encabezado y los elementos del control rssFeed a medida que se crean en el CreateControlHierarchy() y métodos 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 RssFeed's ViewState, tenerlo almacenado en los controles secundarios es desperdiciado, lo que conduce a ViewStates innecesariamente inflado (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 se ha guardado 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. El método Render(), a continuación, 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, tomar el encabezado y aplicar su estilo y, a continuación, recorrer en iteración las filas restantes, aplicar el estilo adecuado (sea el 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, he examinado los pasos necesarios para agregar compatibilidad con plantillas a un control que no sea de entrada de datos. Recuerde que esto implica los tres pasos siguientes:

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

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. El 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) del control RssFeed.

Comience definiendo 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 métodos createRssFeedItem() y, si es necesario, usar 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 RssFeed representar 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, simplemente compruebe 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 desde 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 un HeaderTemplate, por lo que crea una instancia de la plantilla en el encabezado Cell. Si no se ha proporcionado headerTemplate, se agrega el encabezado predeterminado. (En la sección "Creating a Non-Templated DataBound Control", 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 RssFeedIte m 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 eldataSource de , se asigna el rssitem actual al RssFeedItem creadola propiedad DataItem y, a continuación, se llama al método RssFeedItem"s DataBind(). 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 punto 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 Botón control web dentro de la plantilla y, a continuación, responder mediante programación a su evento click? Como es probable que sepa, el DataGrid, DataListy Repeater todos tienen un evento ItemCommand que se desencadena si se desencadena un evento Command desde los intestinos de sus plantillas. Echemos un vistazo a cómo ampliar 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 botón de control web que causa una devolución de correo, tras la postback que el evento Command 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 de propagación de eventos para percolar el evento a través de la jerarquía de control hasta que algún control determine que la propagación debe detenerse.

La propagación de eventos se puede realizar 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() para 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 se propaga el evento, se ejecutará pel método OnBubbleEvent().

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, DataListy repeater para controlar el evento Command de Buttons, LinkButtonsy ImageButtons dentro de los controles. Dado que el evento Command del botón llama a RaiseBubbleEvent(), esto percola el evento hasta el elemento primario del botón. Las clases que componen los elementos de DataGrid, DataListy Repeater—el DataGridItem, DataListItemy RepeaterItem— invalidan el OnBubbleEvent() para detectar eventos de propagación. Si se ha propagado un evento Comando , estas clases de elementos detienen la propagación y crean una instancia de EventArgs adecuada. ( DataGridItemCommandEventArgs para eldataGrid de , dataListItemCommandEventArgs para elDataList de y repeaterItemCommandEventArgs para el Repeater). A continuación, se propaga hasta el DataGrid, DataListo Repeater. EldataGrid , DataListy Repeater también invalidan OnBubbleEvent(); al obtener un evento activado, se detiene la propagación y se desencadena el evento ItemCommand del control web de datos.

Con esta comprensión de cómo controla los eventos de burbujas de datos Web, echemos un vistazo a los pasos necesarios para agregar un evento ItemCommand al control rssFeed de . En primer lugar, debe invalidar el método RssFeedItem clase OnBubbleEvent(). Aquí debe escuchar eventos de Comando. Al obtener este evento, empaquete los detalles en una instancia de RssFeedItemCommandEventArgs y, a continuación, percolar el evento al control rssFeed de 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 a la RssFeedItem cuyo botón contenedor ha recorrido el evento; CommandSource, una referencia al botón que generó el evento; y CommandName y CommandArgument, que tienen los valores de las propiedades Command Name 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 el eventArgs entrante es de tipo CommandEventArgs. Si es así, crea una nueva instancia de RssFeedItemCommandEventArgs y la propaga hasta el RssFeedItemel elemento primario. (Recuerde que el RssFeedItemes realmente un control Table. A continuación, 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 comando de . Si es un evento distinto de un evento Command, OnBubbleEvent() devuelve False, lo que permite que la propagación continúe sin tamaño.

Todo lo que queda es que el rssFeed control OnBubbleEvent() se invalide. Cuando se propaga una instancia de RssFeedItemCommandEventArgs, es hora 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 al 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 de Visitar para cada elemento. Cuando el usuario hace clic en este botón, el objetivo es que se abran a la dirección URL del elemento RSS de (que se almacena en la propiedad link de ). La plantilla siguiente crearía un botón Visitar para cada elemento RssFeed, pasando la propiedad Link en el 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 RssFeedItemCommand evento a un controlador de eventos. El código de este controlador de eventos sería dolorosomente sencillo: solo una Response.Redirect() simple al valor del CommandName del botón en el que se hace 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, resulta útil crear primero el control como un control de entrada de datos y, a continuación, agregar compatibilidad con plantillas. Como se describe en la sección "Creación de un control de entrada de datos sin 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 de miembro privado 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 que se puede presentar en web. Agregar compatibilidad con plantillas al control de entrada de datos concede a los desarrolladores de páginas un alto grado de libertad al 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.