controles de ASP.NET 2.0 y Data-Bound: una nueva perspectiva y algunas prácticas nuevas
Dino Esposito
Wintellect
Enero de 2005
Se aplica a:
Microsoft ASP.NET 1. X
Microsoft ASP.NET 2.0
Resumen: obtenga información sobre cómo evolucionan las herramientas para crear controles enlazados a datos personalizados en ASP.NET 2.0. (19 páginas impresas)
Contenido
Por qué necesitamos un nuevo modelo de origen de datos controles enlazados a datos en ASP.NET 2.0
Puntos de análisis
Mecanismo de Data-Binding
Controles de lista
Un control de ejemplo HeadlineList
Administración de colecciones personalizadas
Un Word en controles compuestos
En conclusión
Por qué necesitamos un nuevo modelo de origen de datos
El enlace de datos fue una de las sorpresas más agradables que los desarrolladores encontraron en el cuadro ASP.NET 1.x . En comparación con la compatibilidad de Active Server Pages con el acceso a datos, el enlace de datos era una combinación extraordinaria de simplicidad y eficacia. Medido frente a las necesidades de los desarrolladores reales, sin embargo, resultó un poco no infectado. Las limitaciones no están en la funcionalidad general, sino que se encuentran en el hecho de que los desarrolladores tienen que escribir mucho código para controlar incluso operaciones simples y comunes, como la paginación, la ordenación o la eliminación. Para ello, ASP.NET 2.0 agrega un nuevo modelo de origen de datos (consulte mi artículo Más carga, menos código con las mejoras de datos de ASP.NET 2.0). Consta de muchos controles sin interfaz de usuario nuevos que puenten la brecha entre las partes visuales de los controles enlazados a datos y los contenedores de datos. Básicamente, la gran mayoría de código que los desarrolladores necesitaban escribir en ASP.NET 1. x, correctamente factorizado y creado, ahora está incrustado en una nueva familia de controles: componentes del origen de datos.
Hay muchas ventajas para usar componentes de origen de datos, en primer lugar, la posibilidad de un modelo de enlace de datos totalmente declarativo. El nuevo modelo reduce el código flexible insertado insertado en recursos ASPX o disperso a través de clases de código subyacente. La nueva arquitectura de enlace de datos obliga a los desarrolladores a jugar por reglas estrictas. Además, cambia inherentemente la calidad del código. Los bloques largos de código conectados a eventos tienden a desaparecer, reemplazados por componentes que simplemente se conectan al marco existente. Los componentes del origen de datos derivan de clases abstractas, implementan interfaces conocidas y, en general, indican un mayor nivel de reutilización.
El excelente libro de Nikhil Kothari sobre el desarrollo de controles de control, Desarrollo de controles y componentes de Microsoft ASP.NET Server, ayudó a miles de desarrolladores a crear controles personalizados e ilustrar los procedimientos recomendados para el diseño y la implementación. Sin embargo, un libro, independientemente de lo grande que sea, nunca puede reemplazar un mejor marco del sistema. Con ASP.NET 2.0, también obtendrá un gráfico completamente rediseñado de clases que agregan funcionalidades de enlace de datos más específicas a medida que desplaza el árbol de las clases base a hoja. La nueva jerarquía de controles enlazados a datos facilita a todos los desarrolladores que recojan la clase adecuada de la que heredan para crear su propio control enlazado a datos personalizado.
En este artículo, obtendrá un vistazo temprano a los cambios en el modelo de enlace de datos de ASP.NET 2.0 que afectan a los controles personalizados. A lo largo del proceso, obtendrá información sobre las nuevas clases base disponibles y los nuevos requisitos para nuevos controles personalizados de alta calidad.
controles de Data-Bound en ASP.NET 2.0
El modelo de origen de datos de ASP.NET 2.0 no requiere necesariamente una nueva raza de controles (por ejemplo , GridView y FormView); sigue funcionando con controles de estilo antiguo, como DataGrid y CheckBoxList. ¿Qué significa controlar a los desarrolladores? Hay dos tipos distintos de origen de datos para tratar: contenedores de datos clásicos basados en IEnumerable, como DataView y colecciones, y controles de origen de datos como SqlDataSource y ObjectDataSource. Al final, ASP.NET controles enlazados a datos 2.0 deben ser capaces de normalizar los datos entrantes en una colección enumerable independientemente del origen, ya sea un objeto ADO.NET, una colección personalizada o un componente de origen de datos.
En ASP.NET 1. x, la documentación está de alguna manera por delante del marco. La documentación identifica y analiza correctamente tres tipos de controles enlazados a datos: controles estándar, controles de lista y controles compuestos. A la primera categoría pertenece cualquier control que simplemente proporcione una implementación no vacía del método DataBind y la propiedad DataSource . Los controles de lista son una combinación interesante de propiedades de diseño avanzadas (por ejemplo, RepeatColumns y RepeatLayout) y plantillas de elementos fijas e incrustadas que se repetirán para cada elemento de datos enlazado. Por último, los controles compuestos son controles que se encargan de diseñar la interfaz de usuario final mediante la combinación de uno o varios controles existentes. La documentación aborda con precisión cualquier problema relacionado con la creación de estos tipos de control; sin embargo, el marco de ASP.NET no proporciona muchas clases base para simplificar la tarea del desarrollador. En la figura 1 se muestra la nueva jerarquía de controles enlazados a datos en ASP.NET 2.0. Tenga en cuenta en amarillo las clases base y su distribución en el árbol general.
Figura 1. Jerarquía de controles enlazados a datos en ASP.NET 2.0
Es interesante echar un vistazo a las clases base representadas en la figura 1. Se muestran y se detallan en la tabla 1.
Clase | Descripción |
---|---|
BaseDataBoundControl | Clase raíz para los controles enlazados a datos. Realiza el enlace de datos y valida los datos enlazados. |
DataBoundControl | Contiene la lógica para comunicarse con los controles de origen de datos y los contenedores de datos. Hereda de esta clase para crear un control enlazado a datos estándar. |
ListControl | La clase base para los controles de lista proporciona una colección Items y funcionalidades avanzadas de representación de diseño. |
CompositeDataBoundControl | Implementa el código típico requerido por los controles compuestos, incluido el código que restaura el árbol del control desde viewstate después de realizar un postback. |
HierarchicalDataBoundControl | Clase raíz para controles jerárquicos basados en árboles. |
Tabla 1. Clases enlazadas a datos base en ASP.NET 2.0
Para cualquier persona que haya sufrido el dolor de crear un control enlazado a datos enriquecido con características que administra su propia colección de datos y restaura correctamente desde viewstate, estas clases son bienvenidas de forma extraordinaria. ¿Quieres un ejemplo iluminador? Siga leyendo.
Puntos de análisis
El Centro para desarrolladores de ASP.NET presentó en los últimos meses un par de artículos sobre ASP.NET controles enlazados a datos 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 profundiza en el código de ambos controles, verá que emplean técnicas especiales en varios puntos del origen. Por ejemplo, recompilan el árbol del control desde viewstate después de realizar un postback en la página (es decir, un postback no bajo la jurisdicción del control); exponen una colección de elementos a través de una clase de colección personalizada; permiten aplicar estilo a los elementos; admiten bastantes tipos de orígenes de entrada. Para cada una de estas características, en ASP.NET 1.1 tienes que escribir código y, lo que es más importante, tienes que escribirlo en una secuencia determinada, invalidar métodos base concretos y seguir cuidadosamente las instrucciones y sugerencias de la documentación y el excelente desarrollo de Microsoft ASP.NET Server Controls and Components.
En ASP.NET 2.0, la mayoría del código de fontanería usado en los dos controles de ejemplo se codifica de forma rígida en 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:
- Mecanismo general de enlace de datos y los distintos tipos de origen de datos
- Recopilaciones y administración de viewstate
La lista probablemente no es exhaustiva, pero ciertamente es suficiente para proporcionarle el panorama general del desarrollo de control. Estará satisfecho y sorprendido para ver qué poco código necesita para desarrollar controles personalizados enriquecidos.
Mecanismo de Data-Binding
Para crear un nuevo control enlazado a datos en ASP.NET 2.0, empiece por decidir qué clase mejor se adapta a usted. Sin embargo, la elección no se limita a clases relativamente vacías como Control y WebControl o, quizás, ListControl. Vamos a explorar las clases en segundo plano. BaseDataBoundControl es la raíz de todas las clases de control enlazadas a datos. Define las propiedades DataSource y DataSourceID y valida su contenido asignado. DataSource acepta objetos enumerables obtenidos y asignados al ASP.NET 1. x way.
Mycontrol1.DataSource = dataSet; Mycontrol1.DataBind();
DataSourceID es una cadena y hace referencia al identificador de un componente de origen de datos enlazado. Una vez que un control está enlazado a un origen de datos, cualquier interacción adicional entre los dos (tanto en lectura como en escritura) se controla fuera del control y se oculta desde la vista. Esto es buenas y malas noticias al mismo tiempo. Es buena (en su lugar, genial) noticias porque puede eliminar una gran cantidad de código. El marco de ASP.NET garantiza que el código correcto se ejecuta y se escribe según los procedimientos recomendados reconocidos. Eres más productivo porque creas páginas con mayor rapidez con la certeza inherente de no tener errores sutiles en el medio. Si no le gusta esta situación, mire, la misma situación que muchos desarrolladores de ASP.NET 1.x se quejaron, puede seguir la programación de estilo antiguo que pasa por la propiedad DataSource y el método DataBind . Además, en este caso, la clase base le ahorra de prácticas comunes, aunque el guardado en el código sea menos notable.
DataBoundControl es la clase que se usa para los controles estándar enlazados a datos personalizados que no tienen mucho que compartir con los controles existentes. Si tiene que controlar su propia colección de elementos de datos, administrar el estado de vista y los estilos, cree una interfaz de usuario sencilla pero de medida, esta clase proporciona un buen punto de partida. Lo más interesante es que la clase DataBoundControl conecta el control a los componentes del origen de datos y oculta cualquier diferencia entre los orígenes de datos enumerables y los componentes ad hoc en el nivel de API. En resumen, cuando hereda de esta clase, solo tiene que invalidar 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 reciente.
Vamos a expandir este punto, que representa un cambio clave en la arquitectura.
BaseDataBoundControl invalida el método DataBind (definido originalmente en Control) y hace que llame al método PerformSelect , marcado como protegido y abstracto. Como sugiere el nombre, se espera que PerformSelect recupere una colección de datos en funcionamiento para que tenga lugar el enlace. El método está protegido porque contiene detalles de implementación; es abstracto (MustInherit en la jerga de Visual Basic) porque su comportamiento solo puede ser preciso mediante clases derivadas como DataBoundControl.
¿Qué hace DataBoundControl para invalidar 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 o ObjectDataSource) ejecuta su comando select y devuelve la colección resultante. El método protegido que opera la recuperación de datos, denominada GetData, es lo suficientemente inteligente como para comprobar también la propiedad DataSource . Si dataSource no está vacío, el objeto enlazado se encapsula en un objeto de vista del origen de datos creado dinámicamente y se devuelve.
El siguiente paso comienza a implicarle como desarrollador de control. Hasta ahora, de forma totalmente automatizada, las clases base han recuperado datos de ADO.NET objetos o componentes de origen de datos. El siguiente paso depende de lo que se espera que haga el control. Aquí es donde encaja el método PerformDataBinding reemplazable. El siguiente fragmento de código muestra el método como implementado en DataBoundControl. Tenga en cuenta que el parámetro IEnumerable que el marco pasa al método simplemente contiene los datos que se van a enlazar, independientemente de su origen.
protected virtual void PerformDataBinding(IEnumerable data) { }
En un control enlazado a datos personalizado, solo tiene que invalidar este método y rellenar cualquier colección específica del control, como la colección Items de muchos controles de lista (CheckBoxList, por ejemplo). La representación de la interfaz de usuario del control se produce en el método Render o en CreateChildControls, en función de la naturaleza del control. La representación es correcta para los controles de lista; CreateChildControls es el ajuste perfecto para los controles compuestos.
Una cosa sigue siendo explicar, ¿quién inicia el proceso de enlace de datos? En ASP.NET 1. x, el enlace de datos requiere una llamada explícita al método DataBind para empezar a funcionar. En ASP.NET 2.0, esto sigue siendo necesario si enlaza datos a controles mediante la propiedad DataSource . Si, en su lugar, usa componentes de origen de datos a través de la propiedad DataSourceID , debe evitarlo. El proceso de enlace de datos se desencadena automáticamente mediante el controlador de eventos OnLoad interno definido en DataBoundControl, como se muestra en el pseudocódigo siguiente.
protected override void OnLoad(EventArgs e) { this.ConnectToDataSourceView(); if (!Page.IsPostBack) base.RequiresDataBinding = true; base.OnLoad(e); }
Cada vez que el control se carga en la página (postback o primera vez), los datos se recuperan y enlazan. Es el origen de datos el que decide si desea volver a usar una consulta o usar algunos datos almacenados en caché.
Si la página se muestra por primera vez, la propiedad RequireDataBinding también se activa para requerir el enlace de datos. Cuando el valor asignado es true, el establecedor de la propiedad llama a DataBind internamente. El pseudocódigo siguiente muestra la implementación interna del establecedor RequireDataBinding .
protected void set_RequiresDataBinding(bool value) { if (value && (DataSourceID.Length > 0)) DataBind(); else _requiresDataBinding = value; }
Como puede ver, para la compatibilidad con versiones anteriores, la llamada automática a DataBind solo tiene lugar si DataSourceID no es null, es decir, si está enlazado a un control de origen de datos de ASP.NET 2.0. A la luz de esto, si también llama a DataBind explícitamente, da como resultado un enlace doble de datos.
Tenga en cuenta que no puede establecer DataSource y DataSourceID al mismo tiempo. Cuando esto sucede, se produce una excepción de operación no válida.
Por último, una mención rápida se debe al método protegido EnsureDataBound . Definido en la clase BaseDataBoundControl , el método garantiza que el control se ha enlazado correctamente a los datos necesarios. Si RequiresDataBinding es true, el método invoca DataBind, como en el fragmento de código siguiente.
protected void EnsureDataBound() { if (RequiresDataBinding && (DataSourceID.Length > 0)) DataBind(); }
Si ha escrito controles complejos y sofisticados enlazados a datos, probablemente ya sepa lo que quiero decir. En ASP.NET 1.x, normalmente se diseña un control enlazado a datos para crear su propia interfaz de usuario en cualquiera de los dos escenarios siguientes: con acceso total al origen de datos o en función de viewstate. Cuando el control necesita administrar sus propios eventos de postback (por ejemplo, imagine un DataGrid con paginación habilitada), las dos opciones mencionadas anteriormente parecen ser dos simples extremos lejanos. En ASP.NET 1.x, estos controles (de nuevo, piensen en DataGrid) solo tenían una salida: desencadenar eventos en la página host que se van a actualizar. Este enfoque conduce al exceso conocido de código en páginas de ASP.NET 1.x , solo el problema por el que se llama a los componentes del origen de datos para corregir.
En ASP.NET 2.0, establezca RequireDataBinding en true siempre que algo suceda en la vida útil de un control para requerir el enlace de datos. Al establecer la propiedad, se desencadena el mecanismo de enlace de datos que vuelve a crear una versión actualizada de la infraestructura interna del control. El controlador de eventos OnLoad integrado también conecta el control al origen de datos. Para ser realmente eficaz, esta técnica debe basarse en controles de origen de datos inteligentes con la capacidad de almacenar en caché sus datos en algún lugar. El control SqlDataSource , por ejemplo, admite muchas propiedades para almacenar cualquier conjunto de resultados enlazado en la memoria caché de ASP.NET durante un período determinado.
Controles de lista
Los controles enlazados a datos suelen ser controles de lista. Un control de lista crea su propia interfaz de usuario repitiendo una plantilla fija para cada elemento de datos enlazado dentro de los límites del sistema central del control. Por ejemplo, un control CheckBoxList simplemente repite un control CheckBox para cada elemento de datos enlazado. Del mismo modo, un control DropDownList recorre en iteración su origen de datos y crea un nuevo <elemento de opción> dentro de una etiqueta de selección> primaria<. Además de los controles de lista, ASP.NET también incluye controles iterativos. ¿En qué difieren?
Los controles de lista e iterativos difieren en lo que respecta al nivel de personalización permitido en la plantilla repetible aplicada 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 repetidor (y el control DataList más sofisticado) es extremadamente flexible, pero no ayuda mucho a mantener el código modular y en capas. Para usar un repetidor, debe definir plantillas en la página (o en un control de usuario externo) y consumir propiedades enlazadas a datos en el origen ASPX. Es rápido, eficaz, a veces necesario, pero definitivamente no limpio y elegante.
En ASP.NET 1.x, todos los controles de lista heredan de ListControl: la única clase de la tabla 1 que se va a definir ya en 1.x. Vamos a entrar en modo de mono de código y empezar a practicar con controles enlazados a datos en ASP.NET 2.0. Empezaré creando un control HeadlineList que represente dos líneas de texto enlazado a datos para cada elemento de datos. Además, el control también incluirá algunas funcionalidades de diseño, como la representación vertical o horizontal.
Un control de ejemplo HeadlineList
Como se mencionó, ListControl es la clase base para todos los controles de lista en ASP.NET 1.x y 2.0. Bastante bien, el control HeadlineList , escrito aquí para ASP.NET 2.0, se puede migrar de nuevo a ASP.NET 1.x de una manera muy fluida. Por alguna razón, cuando se trata de crear una lista de titulares, la primera idea que se debe tener en cuenta es usar un Repetidor. Un repetidor, de hecho, lo haría realmente sencillo.
<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é pasa con este código? O, más precisamente, ¿qué se puede mejorar en este código?
NOTA: En ASP.NET 2.0, puede reemplazar DataBinder.Eval(Container.DataItem, field) por una expresión más corta que se beneficia de un nuevo método público (Eval) en la clase Page . La nueva expresión es similar a Eval(field). Internamente, Eval llama al método Eval estático en la clase DataBinder y determina el contexto de enlace correcto que se va a usar.
Los nombres de los campos se codifican de forma dura en la página ASPX. La reutilización es posible, pero solo a través de cortar y pegar. Cuanto más código agregue para enriquecer el comportamiento del repetidor, más pone en peligro la solución y su reutilización entre páginas y proyectos. Si un control de lista de titulares es lo que desea, pruebe el siguiente enfoque en su lugar.
public class HeadlineList : ListControl, IRepeatInfoUser { : }
ListControl es la clase base para los controles de lista (en la misma familia que CheckBoxList, DropDownList y similar); IRepeatInfoUser es la interfaz poco conocida que la mayoría de estos controles implementan para representarse en columnas y filas, horizontal o verticalmente. Tenga en cuenta que ListControl e IRepeatInfoUser también existen en ASP.NET 1.x y funcionan casi de la misma manera que en la versión 2.0.
Un control de lista se crea alrededor de un control que se va a repetir; este control (o gráfico de controles) es una propiedad de clase y se crea una instancia al cargar para guardar alguna CPU. Esta es la implementación de la propiedad private 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 va a repetir (el título) es una etiqueta que se crea una instancia en la primera lectura. El control HeadlineList también debe proporcionar a los usuarios una manera de influir en la apariencia a través de una variedad de propiedades como RepeatLayout, RepeatColumns y RepeatDirection. Estas propiedades se definen en muchos controles de lista estándar y, como tal, no debería ser nada nuevo para los desarrolladores. Su implementación es similar y tiene un aspecto similar 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 va a escribir para completar el control HeadlineList gira en torno a la representación. La interfaz IRepeatInfoUser cuenta varias propiedades a través de las cuales puede controlar el proceso de representación. Algunos ejemplos son las propiedades Boolean HasHeader, HasFooter y HasSeparator . Estas propiedades se implementan como haría con cualquier otra propiedad normal y las usa si es necesario 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 responsable en última instancia de la salida que se proporciona a la página. Toma el control para repetirlo y lo representa en el marcado. Se llama a RenderItem 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 auxiliar diseñado específicamente para crear nuevos controles repitiendo gráficos de controles existentes. Eso es todo lo que se necesita. Vamos a organizar una página de ejemplo y probar 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.
Ilustración 2. Control enlazado a datos HeadlineList
El control se comporta bien en tiempo de diseño sin ninguna otra inyección de código. Sin embargo, el efecto secundario más agradable de este código no es compatibilidad libre en tiempo de diseño. Para mí, es simplemente fantástico que funcione con ADO.NET objetos de origen de datos (por ejemplo, DataTable o DataSet) y componentes de origen de datos como SqlDataSource. Este código se toma, se compila en un proyecto de ASP.NET 1.x y funciona con orígenes de datos basados en IEnumerable. Llévolo a un proyecto de ASP.NET 2.0 y también funcionará, sin cambios, con objetos de origen de datos.
¿Cuál es la moral de la historia?
En ASP.NET 1.x, la clase ListControl es una excepción agradable, pero una excepción. En ASP.NET 2.0, puede crear cualquier control enlazado a datos mediante un enfoque sencillo pero eficaz similar. Al hacerlo, se aprovechan las nuevas clases base que incorporan la mayor parte de la complejidad y el código físico de la mayoría de los procedimientos recomendados conocidos.
Administración de colecciones personalizadas
ListControl es una clase demasiado especializada que realiza el enlace de datos de una manera fija que está fuera del control a menos que invalide métodos como PerformSelect, OnDataBinding y PerformDataBinding. También proporciona una propiedad de colección Items predefinida. Vamos a abordar el enlace de datos en ASP.NET 2.0 en un nivel inferior y diseñar un control ButtonList que:
- Usa una clase de colección personalizada para contener elementos constituyentes
- Administra viewstate de forma personalizada
El control ButtonList es otro control de lista que genera un botón de inserción para cada elemento de datos enlazado. Puede hacer que herede de ListControl; más, podría tomar el código fuente de HeadlineList, reemplazar Label por Button y también debería funcionar. Estoy adoptando un enfoque diferente esta vez para ilustrar el comportamiento de DataBoundControl. Para simplificar, también omitiré la interfaz IRepeatInfoUser .
public class ButtonList : System.Web.UI.WebControls.DataBoundControl { : }
Un nombre de comando y subtítulo caracterizan cada botón. Esta información se toma del origen de datos enlazado a través de un par de propiedades personalizadas, denominadas DataTextField y DataCommandField. Puede agregar fácilmente propiedades similares para proporcionar sugerencias de herramientas enlazadas a datos, o quizás 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 sobre cada botón enlazado se rellena en una colección de objetos personalizados expuestos a través de la propiedad Items . (Tenga en cuenta que Items es solo un nombre estándar, convencional, pero arbitrario, para 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 ButtonItemCollection personalizada, una colección de objetos ButtonItem . La clase ButtonItem simplemente almacena la información clave sobre un botón enlazado: propiedades Text y CommandName , además de un par de constructores y el método ToString . La clase ButtonItem es el homólogo de la clase ListItem como un control de lista genérico. A continuación se muestra 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 + "]"; } }
Ahora, ¿cómo crearía una colección de objetos ButtonItem ? En ASP.NET 1.x, debe crear una clase de colección personalizada que herede de CollectionBase y reemplazar un par de métodos como mínimo. Sin embargo, una colección personalizada es simplemente un contenedor alrededor de un objeto ArrayList sin ninguna ventaja real en términos de velocidad de acceso. La conversión, de hecho, sigue siendo necesaria. Los genéricos de .NET 2.0 proporcionan un punto de inflexión verdadero. Para compilar una colección de objetos ButtonItem , necesita el código siguiente:
public class ButtonItemCollection : Collection<ButtonItem> { }
Y también funciona mejor debido al trabajo que hace el compilador en segundo plano. El control ButtonList solo requiere dos métodos invalidados: Render y PerformDataBinding. Render supone que se rellena la colección Items ; por lo tanto, simplemente itera y genera código de 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 ? Le ayuda a lograr dos resultados. En primer lugar, puede rellenar el control de lista con elementos agregados manualmente. En segundo lugar, una vez que la colección se conserva en el estado de vista, puede volver a generar la interfaz de usuario del control en postback sin enlazar a los datos. ¿Dónde y por qué, es la colección Items rellenada en el enlace de datos? Esto es para lo que es PerformDataBinding . El método toma una lista enumerable de datos (independientemente del origen original) y lo usa para rellenar 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 el enlace de datos, este método garantiza que se rellene la colección Items . ¿Qué ocurre en los postbacks? En este caso, la colección Items debe reconstruirse a partir del estado de vista. Proporciona a la clase de colección personalizada esta capacidad a través de los métodos de la interfaz IStateManager . Estos son los métodos clave 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); }
La clase se serializa en el estado de vista mediante un objeto Pair , una especie de matriz optimizada de 2 posiciones. Puede crear dos matrices de objetos para contener nombres de texto y comandos para cada botón. A continuación, las dos matrices se empaquetan en un par e insertan en el estado de vista. Cuando se restaura el estado de vista, el par se desempaqueta y la colección Items se rellena con la información almacenada anteriormente. El uso de este enfoque es preferible sobre la serialización de la clase ButtonItem debido al peor rendimiento (tanto en el espacio como en el tiempo) del formateador binario clásico.
Sin embargo, agregar compatibilidad con viewstate a la colección no es suficiente. El control ButtonList también debe mejorarse para aprovechar las funcionalidades de serialización de la colección. Invalida 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 estado de vista del control se compone de dos elementos: el viewstate del control predeterminado más la colección Items . Los dos objetos se empaquetan en un objeto Pair . Además de los objetos Pair , puede usar objetos Triplet (matrices de tres objetos) o componer cualquier número de objetos mediante pares de Pair o Triplet.
Las colecciones personalizadas diseñadas de esta manera dan satisfacción también en tiempo de diseño. El editor de recopilación predeterminado incrustado en Visual Studio 2005 reconoce la colección y abre un cuadro de diálogo como el de la figura 3.
Figura 3. Colección ButtonList Items en tiempo de diseño
Merece la pena notar que en ASP.NET 2.0 algunos controles enlazados a datos permiten mantener los elementos enlazados a datos separados de los elementos agregados mediante programación a través de la colección Items . La propiedad AppendDataBoundItems booleana controla este aspecto de la interfaz de programación del control. La propiedad se define en ListControl (no en DataBoundControl) y el valor predeterminado es false.
Un Word en controles compuestos
La clase CompositeDataBoundControl es el punto de partida para crear controles compuestos que creo que es solo lo que se hace referencia al pensar en los controles enlazados a datos. Un control compuesto debe:
- Actúe como contenedor de nomenclatura.
- Cree su propia interfaz de usuario a través del método CreateChildControls .
- Implemente una lógica determinada para restaurar su jerarquía de elementos secundarios después del postback.
El último punto se ilustra bien en el libro de Nikhil Kothari e implementado en todos los controles integrados de ASP.NET 1.x. Si aún no has entendido completamente ese concepto hasta ahora, la buena noticia es que ahora puedes olvidarlo por completo. Ahora todo está codificado de forma rígida en la clase CompositeDataBoundControl . El aspecto principal que debe tener en cuenta es diseñar los elementos secundarios de su control. Para ello, invalide un nuevo método definido como se indica a continuación:
protected abstract int CreateChildControls( IEnumerable dataSource, bool dataBinding);
CompositeDataBoundControl hereda de DataBoundControl, por lo que la mayoría de las cosas indicadas en este artículo sobre colecciones, enlaces y viewstate también se aplican a los controles compuestos.
En conclusión
El enlace de datos y los controles enlazados a datos representaron un salto cuántico hacia delante en ASP.NET 1.x , pero dejó un número de puntos no explicados y varias preguntas sin responder. El libro de Nikhil Kothari fue una guía magnífica y autoritativa para todos los desarrolladores. ASP.NET 2.0 convierte algunos de los procedimientos recomendados de ese libro (implementados en gran medida en segundo plano de ASP.NET 1.x) en clases reutilizables y un nuevo modelo de objetos para controles enlazados a datos.
En este artículo se resaltan los principales cambios realizados entre ASP.NET 1. x y ASP.NET 2.0 y describe la forma en que van a afectar al desarrollo a través de un par de ejemplos prácticos. En el futuro, queremos tener un ojo sobre los estilos y temas en ASP.NET desarrollo de controles 2.0. Pero eso quizás proporcionará el foco para otro artículo en el futuro próximo. Permanezca atento.
Libros relacionados
- Nikhil Kothari y Vandana Datye, Developing Microsoft ASP.NET Server Controls and Components
- Dino Esposito, Presentación de Microsoft ASP.NET 2.0
Acerca del autor
Dino Esposito es un instructor y consultor de Wintellect con sede en Italia. Autor de programación de Microsoft ASP.NET y la versión más reciente de Introducción a Microsoft ASP.NET 2.0 (tanto de Microsoft Press), pasa la mayoría de sus clases de enseñanza en ASP.NET y ADO.NET y hablando en conferencias. Blog de Tour Dino en https://weblogs.asp.net/despos.