Exportar (0) Imprimir
Expandir todo
Expandir Minimizar

ASP.NET 2.0 y los controles enlazados a datos: una nueva perspectiva y nuevas prácticas (artículos técnicos sobre ASP.NET)

18 de Julio de 2005

Publicado: Enero de 2005

Dino Esposito

Wintellect

Este artículo se aplica a:

Microsoft ASP.NET 1.x

Microsoft ASP.NET 2.0

Resumen: Conozca la evolución de las herramientas para generar controles enlazados a datos en ASP.NET 2.0. (19 páginas impresas.) (Este artículo contiene vínculos a páginas en inglés.)

En esta página

Por qué se requiere un nuevo modelo de origen de datos Por qué se requiere un nuevo modelo de origen de datos
Controles enlazados a datos en ASP.NET 2.0 Controles enlazados a datos en ASP.NET 2.0
Puntos de análisis Puntos de análisis
El mecanismo del enlace a datos El mecanismo del enlace a datos
Controles de lista Controles de lista
Un control de ejemplo HeadlineList Un control de ejemplo HeadlineList
Administración de colecciones personalizadas Administración de colecciones personalizadas
Algunas cuestiones sobre los controles compuestos Algunas cuestiones sobre los controles compuestos
Conclusión Conclusión

Por qué se requiere un nuevo modelo de origen de datos

El enlace a datos fue una de las sorpresas más agradables con las que se encontraron los desarrolladores en ASP.NET 1.x. Comparado con la compatibilidad de las páginas Active Server para el acceso a datos, el enlace a datos supuso una mezcla extraordinaria de simplicidad y efectividad. No obstante, al enfrentarse a las necesidades de los desarrolladores reales, resultó hasta cierto punto imperfecto. Las limitaciones no se encuentran en la funcionalidad general, sino más bien en el hecho de que los desarrolladores deben escribir una gran cantidad de código para controlar incluso las operaciones sencillas y habituales tales como la paginación, ordenación o eliminación. Para aportar una solución a este problema, en ASP.NET 2.0 se agrega un nuevo modelo de origen de datos (véase mi artículo More Load, Less Code with the Data Enhancements of ASP.NET 2.0 ). Éste consiste en diferentes controles nuevos sin IU que completan el vacío existente entre las partes visuales de los controles enlazados a datos y los contenedores de datos. Básicamente, la inmensa mayoría del código que los desarrolladores debían escribir en ASP.NET 1.x, correctamente factorizado y creado, se encuentra ahora incrustado en una nueva familia de controles: los componentes de origen de datos.

El uso de componentes de origen de datos aporta numerosas ventajas: la primera y fundamental consiste en la posibilidad de un modelo de enlace a datos completamente declarativo. Este nuevo modelo reduce el código flexible insertado en línea en los recursos de ASPX o repartido entre las clases de código subyacente. La nueva arquitectura de enlace a datos obliga a los desarrolladores a seguir unas reglas estrictas. Además, cambia la calidad del código de forma inherente. Los extensos bloques de código asociados a eventos tienden a desaparecer y son sustituidos por componentes que simplemente se conectan al marco existente. Los componentes de origen de datos se derivan de clases abstractas, implementan interfaces conocidas y ofrecen, en general, un nivel más alto de reutilización.

El excelente libro de Nikhil Kothari sobre el desarrollo de controles, Developing Microsoft ASP.NET Server Controls and Components, ayudó a miles de desarrolladores a crear controles personalizados y ofrecía ejemplos de las prácticas más adecuadas para el diseño y la implementación. Sin embargo, un libro, por muy excelente que sea, no puede sustituir a un marco de sistema mejorado. Con ASP.NET 2.0, se consigue asimismo un gráfico de clases completamente rediseñado, que agrega unas capacidades de enlace de datos más específicas conforme nos desplazamos por el árbol desde las clases de base a las de hoja. La nueva jerarquía de controles enlazados a datos facilita a los desarrolladores la elección de la clase más adecuada desde la que heredar para generar su propio control enlazado a datos personalizado.

En este artículo se ofrece un examen anticipado de los cambios en el modelo de enlace a datos de ASP.NET 2.0 que afectan a los controles personalizados. Al mismo tiempo, podrá conocer las nuevas clases base disponibles así como los nuevos requisitos para los nuevos controles personalizados de gran calidad.

Controles enlazados a datos en ASP.NET 2.0

El modelo de origen de datos de ASP.NET 2.0 no requiere necesariamente unos controles nuevos (por ejemplo GridView y FormView); sino que puede seguir funcionando con controles más convencionales como, por ejemplo, DataGrid y CheckBoxList. ¿Qué supone esto para los desarrolladores de controles? Existen dos tipos de origen de datos diferentes con los que se puede trabajar: los contenedores de datos tradicionales basados en IEnumerable como DataView y las colecciones, y los controles de origen de datos como SqlDataSource y ObjectDataSource. En definitiva, los controles enlazados a datos de ASP.NET 2.0 deben ser capaces de normalizar cualquier dato entrante en una colección enumerable independientemente del origen; ya sea éste un objeto de ADO.NET, una colección personalizada o un componente de origen de datos.

En ASP.NET 1.x, la documentación se adelanta en cierta medida al marco. La documentación identifica y explica correctamente tres tipos de controles enlazados a datos: controles estándar, de lista y compuestos. La primera categoría abarca los controles que simplemente proporcionan una implementación no vacía del método DataBind y de la propiedad DataSource. Los controles de lista son una mezcla interesante de propiedades de diseño avanzadas (por ejemplo, RepeatColumns y RepeatLayout) y plantillas de elementos fijos e incrustados que se repiten para cada elemento de datos enlazados. Por último, los controles compuestos se encargan de diseñar la interfaz de usuario final mediante la combinación de uno o varios controles existentes. La documentación aborda de un modo preciso todos los temas relacionados con la creación de estos tipos de controles; sin embargo, el marco ASP.NET no proporciona muchas clases base para simplificar la tarea del desarrollador. En la figura 1 se muestra la nueva jerarquía de los controles enlazados a datos en ASP.NET 2.0. Observe las clases base en color amarillo y su distribución en el árbol general.

Figura 1. La jerarquía de controles enlazados a datos en ASP.NET 2.0

Resulta interesante examinar las clases base que aparecen en la figura 1. En la tabla 1 se ofrece una lista detallada de las mismas.

Clase

Descripción

BaseDataBoundControl

Clase raíz de los controles enlazados a datos. Lleva a cabo el enlace a datos y valida todos los datos enlazados.

DataBoundControl

Contiene la lógica para la comunicación con los controles de origen de datos y contenedores de datos. Se debe heredar de esta clase para generar un control enlazado a datos estándar.

ListControl

Clase base de los controles de lista, proporciona una colección Items así como unas capacidades avanzadas de procesamiento de diseño.

CompositeDataBoundControl

Implementa el código típico que requieren los controles compuestos, incluido el código que restaura el árbol del control desde viewstate después de que se realice una devolución de datos.

HierarchicalDataBoundControl

Clase raíz de los controles jerárquicos basados en árbol.

Tabla 1. Clases base enlazadas a datos en ASP.NET 2.0

Cualquier desarrollador que conozca el esfuerzo que implica la creación de un control enlazado a datos con numerosas características que administre su propia colección de datos y restaure correctamente desde el valor viewstate, apreciará el extraordinario valor de estas clases. ¿Desea ver un ejemplo ilustrativo? Continúe leyendo.

Puntos de análisis

En ASP.NET Developer Center se ofrecían estos últimos meses un par de artículos sobre los controles enlazados a datos de ASP.NET 1.1: los controles RssFeed y DetailsView ( Building DataBound Templated Custom ASP.NET Server Controls y A DetailsView Control for ASP.NET 1.x , respectivamente). Si profundizamos en el código de ambos controles, veremos que emplean unas técnicas especiales en diferentes puntos del origen. Por ejemplo, vuelven a generar el árbol del control desde viewstate después de que se realice una devolución de datos a la página (es decir: una devolución de datos que no está dentro de la jurisdicción del control); exponen una colección de elementos a través de una clase de colección personalizada; permiten asignar estilos a los elementos; admiten bastantes tipos de orígenes de entrada. Para todas estas características, en ASP.NET 1.1 resulta necesario escribir código y, lo que es más importante, se debe escribir en una secuencia específica, de forma que se omitan los métodos base determinados y se sigan detenidamente las instrucciones y sugerencias de la documentación y el libro anteriormente mencionado: Developing Microsoft ASP.NET Server Controls and Components.

En ASP.NET 2.0, la mayor parte del código de estructura que se utiliza en los dos controles de ejemplo está incluido en el código de las clases base enumeradas en la tabla 1. Para comparar y contrastar los controles enlazados a datos en ASP.NET 1.1 y 2.0, me centraré en los siguientes puntos principales:

  • El mecanismo general del enlace a datos y los diferentes tipos de origen de datos

  • Colecciones y administración de viewstate

Probablemente, esta lista no es exhaustiva pero resulta suficiente para ofrecer una idea general sobre el desarrollo de los controles. Le sorprendería agradablemente comprobar la poca cantidad de código que se requiere para desarrollar unos controles personalizados con numerosas características.

El mecanismo del enlace a datos

Para generar un nuevo control enlazado a datos en ASP.NET 2.0, se debe decidir previamente qué clase se adapta mejor a nuestras necesidades. Esta decisión, sin embargo, no se limita a clases relativamente vacías como Control y WebControl o, quizá, ListControl. Examinemos las clases en profundidad. BaseDataBoundControl es la raíz de todas las clases de controles enlazados a datos. Define las propiedades DataSource y DataSourceID y valida el contenido asignado de las mismas. DataSource acepta objetos enumerables que se obtienen y asignan del mismo modo que en ASP.NET 1.x.

Mycontrol1.DataSource = dataSet;
Mycontrol1.DataBind();

DataSourceID es una cadena y hace referencia al identificador de un componente de origen de datos. Una vez que un control se enlaza a un origen de datos, cualquier interacción posterior entre los dos (tanto en la lectura como en la escritura) queda fuera de nuestro control y oculta a la vista. Este hecho muestra al mismo tiempo aspectos positivos y negativos. Una de las grandes ventajas consiste en que permite eliminar una gran cantidad de código. El marco de ASP.NET garantiza que se ejecuta el código correcto y que éste se escribe de acuerdo con las prácticas más adecuadas reconocidas. Permite una mayor productividad puesto que las páginas se crean con mayor rapidez al tener la absoluta seguridad de que no contienen ningún error. Si no le agrada esta situación (observe que es la misma situación de la que se quejaban numerosos desarrolladores de ASP.NET 1.x) puede seguir utilizando la programación convencional que pasa a través de la propiedad DataSource y el método DataBind. Asimismo, en este caso, la clase base evita el uso de prácticas habituales aunque el ahorro en el código no es tan notable.

DataBoundControl es la clase que se utiliza para controles enlazados a datos estándares y personalizados que no comparten mucho con los controles existentes. Si se debe controlar la propia colección de elementos de datos, administrar viewstate y los estilos, crear una interfaz de usuario sencilla pero hecha a medida, esta clase ofrece un óptimo punto de partida. Más interesante resulta el hecho de que la clase DataBoundControl conecta el control con los componentes de origen de datos y oculta cualquier diferencia entre los orígenes de datos enumerables y los componentes ad hoc en el nivel de la API. En resumen, cuando se hereda de esta clase sólo se necesita omitir un método que recibe una colección de datos independientemente del origen, ya sea un objeto DataSet o un componente de origen de datos más nuevo.

Examinemos en más profundidad este punto, que representa un cambio fundamental en la arquitectura.

BaseDataBoundControl omite el método DataBind (originalmente definido en Control) y lo hace llamar el método PerformSelect, que está marcado como protegido y abstracto. Como sugiere el nombre, PerformSelect debe recuperar una colección de datos operativa para que se realice el enlace. El método está protegido puesto que contiene detalles de la implementación y es abstracto (MustInherit en la jerga de Visual Basic) ya que su comportamiento sólo lo pueden precisar las clases derivadas como DataBoundControl.

Así pues, ¿cómo actúa DataBoundControl para omitir PerformSelect?

Se conecta al objeto de origen de datos y obtiene la vista predeterminada. Un objeto de origen de datos (por ejemplo, un control como SqlDataSource u ObjectDataSource) ejecuta el comando de selección y devuelve la colección resultante. El método protegido que lleva a cabo la recuperación de los datos, denominado GetData, es capaz de comprobar también la propiedad DataSource. Si DataSource no está vacío, el objeto enlazado se empaqueta en un objeto de vista de origen de datos creado dinámicamente y se devuelve.

Con el siguiente paso comienza la intervención del desarrollador de controles. Hasta el momento, de una forma completamente automatizada, las clases base han recuperado los datos bien desde los objetos de ADO.NET o desde los componentes de origen de datos. El siguiente paso depende de la función que deba realizar el control. Aquí es donde interviene el método reemplazable PerformDataBinding. El siguiente fragmento de código muestra el método tal como se implementa en DataBoundControl. Observe que el parámetro IEnumerable que el marco pasa al método simplemente contiene los datos que se van a enlazar, independientemente del origen de los mismos.

protected virtual void PerformDataBinding(IEnumerable data)
{
}

En un control enlazado a datos personalizado, sólo se requiere omitir este método y rellenar todas las colecciones específicas del control como la colección Items de numerosos controles de lista (CheckBoxList, por ejemplo). El procesamiento de la interfaz de usuario del control se lleva a cabo en el método Render o en CreateChildControls, en función de la naturaleza del control. Render es adecuado para los controles de lista; CreateChildControls resulta la opción perfecta para los controles compuestos.

Aún queda una cuestión por explicar: ¿quién inicia el proceso de enlace a datos? En ASP.NET 1.x, el enlace a datos requiere una llamada explícita al método DataBind para comenzar a funcionar. En ASP.NET 2.0, también se requiere esta llamada si se enlazan los datos a los controles mediante la propiedad DataSource. Sin embargo, se debería evitar si se utilizan componentes de origen de datos mediante la propiedad DataSourceID. El proceso de enlace a datos se activa automáticamente por el controlador de eventos interno OnLoad que se define en DataBoundControl, como se demuestra en el siguiente pseudocódigo.

protected override void OnLoad(EventArgs e)
{
   this.ConnectToDataSourceView();
   if (!Page.IsPostBack)
       base.RequiresDataBinding = true;
   base.OnLoad(e);
}

Cuando el control se carga en la página (ya sea una devolución de datos o por primera vez), los datos se recuperan y se enlazan. El origen de datos es el que decide si continuar de nuevo con una consulta o utilizar datos almacenados en caché.

Si la página se muestra por primera vez, se activa también la propiedad RequiresDataBinding para solicitar el enlace de los datos. Cuando el valor asignado es true, el establecedor de la propiedad llama a DataBind internamente. El pseudocódigo que aparece a continuación muestra la implementación interna del establecedor RequiresDataBinding.

protected void set_RequiresDataBinding(bool value)
{
   if (value & (DataSourceID.Length > 0))
      DataBind();
   else
      _requiresDataBinding = value;
}

Como se puede comprobar, para conseguir compatibilidad con versiones anteriores la llamada automática a DataBind se lleva a cabo únicamente si DataSourceID no es nulo, es decir, si se enlaza a un control de origen de datos de ASP.NET 2.0. Por lo tanto, si también se realiza una llamada a DataBind explícitamente, el resultado será un enlace doble de los datos.

Observe que no se puede establecer al mismo tiempo DataSource y DataSourceID. Cuando esto ocurre, se obtiene una excepción de operación no válida.

Por último, debemos mencionar brevemente el método protegido EnsureDataBound. Este método, que se define en la clase BaseDataBoundControl, garantiza que el control se ha enlazado correctamente a los datos necesarios. Si RequiresDataBinding se establece en true, el método invoca a DataBind, como en el siguiente fragmento de código.

protected void EnsureDataBound()
{
  if (RequiresDataBinding & (DataSourceID.Length > 0))
      DataBind();
}

Si alguna vez ha escrito controles enlazados a datos complejos y sofisticados, es probable que entienda lo que quiero decir. En ASP.NET 1.x, un control enlazado a datos generalmente posee una arquitectura para generar su propia interfaz de usuario en uno de los dos escenarios siguientes: con acceso completo al origen de datos o basado en viewstate. Cuando el control debe administrar sus propios eventos de devolución de datos (por ejemplo, imaginemos un DataGrid con paginación activada), las dos opciones mencionadas anteriormente parecen ser dos simples extremos distantes. En ASP.NET 1.x, estos controles (pensemos de nuevo en DataGrid) sólo tenían una salida: activar los eventos en la página host para que se actualizaran. Este enfoque tiene como consecuencia el conocido exceso de código de las páginas de ASP.NET 1.x: precisamente el problema que los componentes de origen de datos se proponen solucionar.

En ASP.NET 2.0, se establece RequiresDataBinding en true siempre que ocurre algo en la vida de un control que requiere el enlace de los datos. Al establecer la propiedad se activa el mecanismo del enlace a datos que vuelve a crear una versión actualizada de la infraestructura interna del control. El controlador de eventos integrado OnLoad conecta asimismo el control al origen de datos. Para que sea realmente eficaz, esta técnica se debe basar en unos controles de origen de datos inteligentes con capacidad para almacenar los datos en alguna parte de la memoria caché. El control SqlDataSource, por ejemplo, admite numerosas propiedades para almacenar cualquier conjunto de resultados enlazados en la caché de ASP.NET durante un período especificado.

Controles de lista

Los controles enlazados a datos son con frecuencia controles de lista. Un control de lista genera su propia interfaz de usuario mediante la repetición de una plantilla fija para cada elemento de datos enlazados dentro de los límites del gran sistema del control. Por ejemplo, un control CheckBoxList simplemente repite un control CheckBox para cada elemento de datos enlazados. De modo similar, un control DropDownList procesa una iteración de su origen de datos y crea un nuevo elemento <option> en una etiqueta <select> principal. Además de los controles de lista, ASP.NET también incluye controles iterativos. ¿Cuáles son las diferencias entre ambos?

Ambos difieren respecto al nivel de personalización permitido en la plantilla repetible que se aplica a cada elemento de datos. Al igual que un control CheckBoxList, un control Repeater recorre los elementos de datos enlazados y aplica la plantilla definida por el usuario. El control Repeater (y el más complejo DataList) resulta enormemente flexible, pero no ayuda a conseguir un código modular y por capas. Para utilizar un control Repeater, se deben definir plantillas en la página (o en un control de usuario externo) y utilizar las propiedades enlazadas a datos del origen de ASPX. Esto puede ser rápido, eficaz y en ocasiones necesario, pero no resulta elegante ni ingenioso.

En ASP.NET 1.x, todos los controles de lista heredan de ListControl: la única clase de la tabla 1 que se define ya en 1.x. Trasteemos un poco con el código y empecemos a practicar con los controles enlazados a datos en ASP.NET 2.0. Comenzaré por generar un control HeadlineList que presenta dos líneas de texto enlazado a datos para cada elemento de datos. Además, el control también incluirá algunas capacidades de diseño tales como el procesamiento vertical u horizontal.

Un control de ejemplo HeadlineList

Como se ha mencionado, ListControl es la clase base para todos los controles de lista en ASP.NET 1.x y 2.0. Por suerte, el control HeadlineList, escrito aquí para ASP.NET 2.0, se puede adaptar para ASP.NET 1.x de un modo muy sencillo. Por algún motivo, cuando se trata de crear una lista de titulares, la primera idea que se nos viene a la mente consiste en utilizar un Repeater. Realmente, un control Repeater, facilitaría en gran medida la tarea.

<asp:Repeater runat="server">
   <HeaderTemplate>
      <table>
   </HeaderTemplate>
   <ItemTemplate>
      <tr><td>
      <%# DataBinder.Eval(Container.DataItem, "Title") %>
      <hr>
      <%# DataBinder.Eval(Container.DataItem, "Abstract") %>
      </td></tr>
   </ItemTemplate>
   <FooterTemplate>
      </table>
   </FooterTemplate>
</asp:Repeater>

¿Qué es lo que está mal en este código? O, más exactamente, ¿qué es lo que se puede mejorar?

NOTA: en ASP.NET 2.0, se puede sustituir DataBinder.Eval(Container.DataItem, field) por una expresión más corta que aproveche un nuevo método público (Eval) en la clase Page. La nueva expresión es similar a Eval(field). De forma interna, Eval llama al método estático Eval de la clase DataBinder y determina el contexto de enlace correcto que se utilizará.

Los nombres de los campos están incluidos en el código de la página ASPX. Se puede reutilizar, aunque sólo mediante una operación de cortar y pegar. Cuanto más código se agregue para enriquecer el comportamiento del control Repeater, más se ponen en riesgo la solución y la capacidad de reutilización de la misma a través de las páginas y los proyectos. Si lo que se desea es sólo un control de lista de titulares, resulta más recomendable seguir el siguiente enfoque.

public class HeadlineList : ListControl, IRepeatInfoUser
{
  :
}

ListControl es la clase base para los controles de lista (de la misma familia que CheckBoxList, DropDownList y otros similares); IRepeatInfoUser es la interfaz poco conocida que la mayor parte de estos controles implementan para procesar columnas y filas, horizontal o verticalmente. Observe que ListControl y IRepeatInfoUser existen también en ASP.NET 1.x y funcionan casi del mismo modo que en 2.0.

Un control de lista se genera alrededor de un control que se repite; este control (o gráfico de controles) es una propiedad de clase y se genera una instancia del mismo cuando se carga para guardar alguna CPU. A continuación se muestra la implementación de la propiedad privada ControlToRepeat.

private Label _controlToRepeat;

private Label ControlToRepeat

{
   get
   {
      if (_controlToRepeat == null)
      {
         _controlToRepeat = new Label();
         _controlToRepeat.EnableViewState = false;
         Controls.Add(_controlToRepeat);
      }
      return _controlToRepeat;
   }
} 

En este caso, el control que se repite (el titular) es un control Label del que se crea una instancia en la primera lectura. El control HeadlineList también debe ofrecer a los usuarios un modo de influir en el aspecto a través de una gama de propiedades como RepeatLayout, RepeatColumns y RepeatDirection. Estas propiedades se definen en numerosos controles de lista estándares y por lo tanto no representan ninguna novedad para los desarrolladores. Su implementación es similar y se parece al código siguiente.

public virtual RepeatDirection RepeatDirection
{
   get
   {
      object o = ViewState["RepeatDirection"];
      if (o != null)
         return (RepeatDirection) o;
      return RepeatDirection.Vertical;
   }
   set
   {
      ViewState["RepeatDirection"] = value;
   }
}

El otro fragmento de código que se escribe para completar el control HeadlineList gira en torno al procesamiento. La interfaz de IRepeatInfoUser incluye diversas propiedades mediante las que se puede controlar el procesamiento. Ejemplos de ello son las propiedades booleanas HasHeader, HasFooter y HasSeparator. Estas propiedades se implementan del mismo modo que cualquier otra propiedad normal y se utilizan si se requieren en el método de interfaz RenderItem.

public void RenderItem(ListItemType itemType, int repeatIndex, 
RepeatInfo repeatInfo, HtmlTextWriter writer)
{
   string format = "<b>{0}</b><hr style='solid 1px black'>{1}";
   Label lbl = ControlToRepeat;
   int i = repeatIndex;
   lbl.ID = i.ToString();
   string text = String.Format(format, Items[i].Text, Items[i].Value);
   lbl.Text = text;
   lbl.RenderControl(writer);
}

RenderItem es el responsable final del resultado que se muestra en la página. Toma el control para repetir y lo procesa para el marcado. RenderItem se llama desde Render.

protected override void Render(HtmlTextWriter writer)
{
   if (Items.Count >0)
   {
      RepeatInfo ri = new RepeatInfo();
      Style controlStyle = (base.ControlStyleCreated 
                                  ? base.ControlStyle : null);
      ri.RepeatColumns = RepeatColumns;
      ri.RepeatDirection = RepeatDirection;
      ri.RepeatLayout = RepeatLayout;
      ri.RenderRepeater(writer, this, controlStyle, this);
   }
}

RepeatInfo es un objeto de ayuda específicamente diseñado para generar nuevos controles mediante la repetición de los gráficos de controles existentes. Eso es todo lo que se requiere. Creemos una página de ejemplo y probemos el control.

<expo:headlinelist id="HeadlineList1" runat="server" 
       repeatlayout="Table" repeatdirection="Vertical" repeatcolumns="2" 
       datatextfield="LastName" datavaluefield="Notes" />

En la figura 2 se muestra el control en acción.

Figura 2. El control enlazado a datos HeadlineList

El control se comporta correctamente en tiempo de diseño sin más adiciones de código. Sin embargo, la mayor ventaja de este código no radica en que admita tiempo de diseño gratuito. En mi opinión, es sencillamente fantástico que funcione con objetos de origen de datos de ADO.NET (por ejemplo, DataTable o DataSet) y componentes de origen de datos como SqlDataSource. Este código se compila en un proyecto de ASP.NET 1.x y funciona con orígenes de datos basados en IEnumerable. Y si se incluye en un proyecto de ASP.NET 2.0 funcionará también (sin necesidad de cambios) con objetos de origen de datos.

¿Cuál es la moraleja de esta historia?

En ASP.NET 1.x, la clase ListControl es una excepción agradable; pero en cualquier caso es una excepción. En ASP.NET 2.0, se puede generar cualquier control enlazado a datos mediante un enfoque sencillo y eficaz similar a éste. De este modo, se puede beneficiar de las nuevas clases base que incorporan la mayor parte de la complejidad e incluyen en el código la mayoría de las prácticas más adecuadas conocidas.

Administración de colecciones personalizadas

ListControl es una clase demasiado especializada que realiza el enlace a datos de un modo fijo que queda fuera de nuestro control a menos que se omitan métodos como PerformSelect, OnDataBinding y PerformDataBinding. Asimismo, proporciona una propiedad de colección Items definida previamente. Vamos a tratar el enlace a datos en ASP.NET 2.0 a un nivel inferior y a diseñar un control ButtonList que:

  • Utilice una clase de colección personalizada que incluya los elementos constituyentes

  • Administre el valor viewstate de un modo personalizado

El control ButtonList es otro control de lista que da como resultado un botón para cada elemento de datos enlazados. Se puede hacer que herede de ListControl; incluso se puede tomar el código fuente de HeadlineList, sustituir Label por Button y debería funcionar también. Esta vez adoptaré un enfoque diferente para mostrar el comportamiento de DataBoundControl. Por motivos de simplicidad, pasaré por alto asimismo la interfaz IRepeatInfoUser.

public class ButtonList : System.Web.UI.WebControls.DataBoundControl
{
   :
}

Cada botón se caracteriza mediante un título y un nombre de comando. Esta información se obtiene del origen de datos enlazados mediante un par de propiedades personalizadas, denominadas DataTextField y DataCommandField. Se pueden agregar fácilmente propiedades similares para ofrecer informaciones sobre herramientas enlazadas a datos o quizá direcciones URL.

public virtual string DataCommandField
{
   get
   {
      object o = ViewState["DataCommandField"];
      if (o == null)
         return ";
      return (string)o;
   }
   set { ViewState["DataCommandField"] = value; }
}

Toda la información encontrada acerca de cada botón enlazado se reúne en una colección de objetos personalizados que se exponen mediante la propiedad Items. (Tenga en cuenta que Items no es más que un nombre estándar, convencional y arbitrario de esta propiedad.)

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public virtual ButtonItemCollection Items
{
   get
   {
      if (_items == null)
      {
         _items = new ButtonItemCollection();
         if (base.IsTrackingViewState)
            _items.TrackViewState();
      }
      return _items;
   }
}

La colección Items es una instancia de la clase personalizada ButtonItemCollection: una colección de objetos ButtonItem. La clase ButtonItem sólo almacena la información clave acerca de un botón enlazado: las propiedades Text y CommandName, además de un par de constructores y el método ToString. La clase ButtonItem es el equivalente a la clase ListItem como control de lista genérico. Veamos un ejemplo.

public class ButtonItem
{
   private string _text;
   private string _command;

   public ButtonItem(string text, string command) {
      _text = text;
      _command = command;
   }
   public string Text {
      get {return _text;}
      set {_text = value;}
   }
   public string CommandName {
      get { return _command; }
      set { _command = value; }
   }
   public override string ToString() {
      return "Button [" + Text + "]";
   }
}

¿Cómo crearíamos ahora una colección de objetos ButtonItem? En ASP.NET 1.x, se debe generar una clase de colección personalizada que herede de CollectionBase y omita un par de métodos al mínimo. Sin embargo, una colección personalizada es simplemente el empaquetador de un objeto ArrayList sin ninguna ventaja en términos de velocidad de acceso. De hecho, sigue siendo necesaria la conversión. Los componentes genéricos en .NET 2.0 ofrecen una ventaja crucial. Para generar una colección de objetos ButtonItem se requiere el siguiente código:

public class ButtonItemCollection : Collection<ButtonItem>
{
}

También funciona mejor debido al trabajo que el compilador realiza entre bastidores. El control ButtonList requiere sólo dos métodos omitidos: Render y PerformDataBinding. Render asume que la colección Items está llena; por lo que simplemente realiza una iteración y genera código marcado.

protected override void Render(HtmlTextWriter writer)
{
   for(int i=0; i<Items.Count; i++)
   {
      ButtonItem item = Items[i];
      Button btn = new Button();
      btn.Text = item.Text;
      btn.CommandName = item.CommandName;
      btn.RenderControl(writer);
   }
}

¿Por qué es importante la colección Items? Porque ayuda a conseguir dos resultados. Primero, se puede llenar el control de lista con elementos agregados manualmente. Segundo, una vez que la colección se mantiene en viewstate, se puede volver a generar la interfaz de usuario del control mediante devolución de datos sin enlazar a los datos. ¿Dónde se llena la colección Items y con qué se llena en un enlace a datos? Para esto se utiliza PerformDataBinding. Este método toma una lista enumerable de datos (independientemente del origen) y la utiliza para llenar la colección Items.

protected override void PerformDataBinding(IEnumerable dataSource)
{
   base.PerformDataBinding(dataSource);
   string textField = DataTextField;
   string commandField = DataCommandField;

   if (dataSource != null) {
   foreach (object o in dataSource)
   {
      ButtonItem item = new ButtonItem();
      item.Text = DataBinder.GetPropertyValue(o, textField, null);
      item.CommandName = DataBinder.GetPropertyValue(o, 
                                             DataCommandField, null);
      Items.Add(item);
   } 
   }
}

Siempre que se requiera enlace a datos, este método garantiza que la colección Items esté llena. ¿Qué ocurre tras las devoluciones de datos? En este caso la colección Items se debe volver a construir desde viewstate. Se proporciona a la clase de colección personalizada esta capacidad mediante los métodos de la interfaz IStateManager. A continuación, se muestran los métodos fundamentales de la interfaz:

public void LoadViewState(object state)
{
   if (state != null) {
      Pair p = (Pair) state;
      Clear();
      string[] rgText = (string[])p.First;
      string[] rgCommand = (string[])p.Second;

      for (int i = 0; i < rgText.Length; i++)
         Add(new ButtonItem(rgText[i], rgCommand[i]));
   }
}

public object SaveViewState()
{
   int numOfItems = Count;
   object[] rgText = new string[numOfItems];
   object[] rgCommand = new string[numOfItems];

   for (int i = 0; i < numOfItems; i++) {
      rgText[i] = this[i].Text;
      rgCommand[i] = this[i].CommandName;
   }

   return new Pair(rgText, rgCommand);
}

Esta clase se serializa a sí misma en viewstate mediante un objeto Pair: un tipo de matriz de dos posiciones optimizada. Se deben crear dos matrices de objetos para incluir texto y nombres de comandos para cada botón. Las dos matrices se empaquetan después en un par y se insertan en viewstate. Cuando se restaura viewstate, este par se desempaqueta y la colección Items se rellena con la información previamente almacenada. El uso de este enfoque es preferible a convertir la clase ButtonItem en serializable debido al peor rendimiento (en espacio y en tiempo) del formateador binario clásico.

No obstante, agregar compatibilidad con viewstate a la colección no resulta suficiente. También se debe mejorar el control ButtonList para que aproveche la ventaja que ofrecen las capacidades de serialización de la colección. Se deben omitir LoadViewState y SaveViewState en la clase de control.

protected override void LoadViewState(object savedState)
{
   if (savedState != null) {
      Pair p = (Pair) savedState;
      base.LoadViewState(p.First);
      Items.LoadViewState(p.Second);
   }
   else
      base.LoadViewState(null);
}

protected override object SaveViewState()
{
   object baseState = base.SaveViewState();
   object itemState = Items.SaveViewState();
   if ((baseState == null) & (itemState == null))
      return null;
   return new Pair(baseState, itemState);
}

El viewstate del control se compone de dos elementos: el viewstate predeterminado y la colección Items. Los dos objetos se empaquetan en un objeto Pair. Además de los objetos Pair, se pueden utilizar los objetos Triplet (matrices de tres objetos) o crear cualquier serie de objetos que utilicen pares de Pair o Triplet.

Las colecciones personalizadas diseñadas de este modo también aportan mejoras satisfactorias en el tiempo de diseño. El editor de colección predeterminado incrustado en Visual Studio 2005 reconoce la colección y muestra un cuadro de diálogo como el que aparece en la figura 3.

Figura 3. La colección ButtonList Items en tiempo de diseño

Merece la pena destacar que en ASP.NET 2.0 algunos controles enlazados a datos permiten mantener los elementos enlazados a datos separados de los elementos que se agregan mediante programación a través de la colección Items. La propiedad booleana AppendDataBoundItems controla este aspecto de la interfaz de programación del control. Esta propiedad se define en ListControl (no en DataBoundControl) y se establece de forma predeterminada en false.

Algunas cuestiones sobre los controles compuestos

La clase CompositeDataBoundControl es el punto de inicio para la creación de controles compuestos, que probablemente es en lo que pensamos cuando hablamos de controles enlazados a datos. Un control compuesto debe:

  • Actuar como un contenedor de nombres.

  • Crear su propia interfaz de usuario mediante el método CreateChildControls.

  • Implementar una lógica particular para restaurar su jerarquía de elementos secundarios después de una devolución de datos.

Este último punto se explica perfectamente en el libro de Nikhil Kothari y se implementa en todos los controles integrados de ASP.NET 1.x. Si aún no ha comprendido del todo ese concepto hasta el momento, la buena noticia consiste en que ahora podrá olvidarse de él por completo. Ahora todo está incluido en el código en la clase CompositeDataBoundControl. El principal aspecto al que se debe prestar atención es el diseño de los elementos secundarios del control. Para ello, se debe omitir un nuevo método que se define del siguiente modo:

protected abstract int CreateChildControls(
      IEnumerable dataSource, bool dataBinding);

CompositeDataBoundControl hereda de DataBoundControl, por lo que la mayor parte de las afirmaciones realizadas en este artículo tienen que ver con las colecciones, el enlace y viewstate aplicados también a controles compuestos.

Conclusión

El enlace a datos y los controles enlazados a datos representaron un extraordinario avance en ASP.NET 1.x pero dejaron varias cuestiones sin explicar y preguntas sin responder. El libro de Nikhil Kothari fue una guía excelente y con autoridad para todos los desarrolladores. ASP.NET 2.0 convierte algunas de las prácticas más adecuadas de ese libro (implementadas en gran medida ya entre bastidores en ASP.NET 1.x) en clases que se pueden reutilizar y un nuevo modelo de objeto para los controles enlazados a datos.

En este artículo se analizan los principales cambios realizados entre ASP.NET 1. x y ASP.NET 2.0 y se muestra el modo en que afectará al desarrollo a través de un par de ejemplos prácticos. Dando un paso más, vigilaremos los estilos y temas del desarrollo de controles en ASP.NET 2.0. Pero eso puede que sirva de tema para otro artículo en un futuro próximo. Permanezca alerta.

Bibliografía relacionada

Acerca del autor

Dino Esposito vive en Italia. Es profesor y asesor de Wintellect. Autor de Programming ASP.NET y del nuevo Introducing ASP.NET 2.0 (ambos de Microsoft Press), pasa la mayor parte de su tiempo dando clases sobre ASP.NET y ADO.NET y ofreciendo conferencias. Visite el blog de Dino en http://weblogs.asp.net/despos .

Mostrar:
© 2015 Microsoft