Ordenar datos en un control DataList o Repeater (C#)

por Scott Mitchell

Descargar PDF

En este tutorial se verá cómo incluir compatibilidad con la ordenación en los controles DataList y Repeater, y cómo crear un control DataList o Repeater cuyos datos se puedan paginar y ordenar.

Introducción

En el tutorial anterior ha visto cómo agregar compatibilidad de paginación a un control DataList. Ha creado un método en la clase ProductsBLL (GetProductsAsPagedDataSource) que devolvía un objeto PagedDataSource. Al enlazarlo a un control DataList o Repeater, el control DataList o Repeater mostraba solo la página de datos solicitada. Esta técnica es similar a la que usan internamente los controles GridView, DetailsView y FormView para proporcionar su funcionalidad de paginación predeterminada integrada.

Además de ofrecer compatibilidad con la paginación, el control GridView también incluye compatibilidad con la ordenación lista para usar. Ni DataList ni Repeater proporcionan funcionalidades de ordenación integradas; pero con un poco de código se pueden agregar características de ordenación. En este tutorial se verá cómo incluir compatibilidad con la ordenación en los controles DataList y Repeater, y cómo crear un control DataList o Repeater cuyos datos se puedan paginar y ordenar.

Revisión de la ordenación

Como ha visto en el tutorial Paginación y ordenación de datos de informes, el control GridView proporciona compatibilidad de ordenación lista para usar. Cada campo de GridView puede tener un elemento SortExpression asociado, que indica el campo de datos por el que se ordenan los datos. Cuando la propiedad AllowSorting de GridView se establece en true, cada campo de GridView que tiene un valor de propiedad SortExpression representa su encabezado como un control LinkButton. Cuando un usuario hace clic en un encabezado de campo concreto de GridView, se produce un postback y los datos se ordenan según el elemento SortExpression del campo en el que se haya hecho clic.

El control GridView también tiene una propiedad SortExpression que almacena el elemento SortExpression del campo de GridView por el que se ordenan los datos. Además, una propiedad SortDirection indica si los datos se van a ordenar en orden ascendente o descendente (si un usuario hace clic dos veces seguidas en el vínculo de encabezado de un determinado de campo de GridView, se alterna el criterio de ordenación).

Cuando GridView se enlaza a su control de origen de datos, entrega sus propiedades SortExpression y SortDirection al control de origen de datos. El control de origen de datos recupera los datos y, después, los ordena según las propiedades SortExpression y SortDirection proporcionadas. Después de ordenar los datos, el control de origen de datos los devuelve a GridView.

Para replicar esta funcionalidad con los controles DataList o Repeater, debe hacer lo siguiente:

  • Crear una interfaz de ordenación
  • Recordar el campo de datos por el que se va a ordenar y si se debe ordenar en orden ascendente o descendente
  • Indicar a ObjectDataSource que ordene los datos por un campo de datos determinado

Abordará estas tres tareas en los pasos 3 y 4. Después, se verá cómo incluir compatibilidad de paginación y ordenación en un control DataList o Repeater.

Paso 2: Representación de los productos en un control Repeater

Antes de preocuparse por implementar cualquiera de las funciones relacionadas con la ordenación, se enumerarán los productos en un control Repeater. Para empezar, abra la página Sorting.aspx en la carpeta PagingSortingDataListRepeater. Agregue un control Repeater a la página web y establezca su propiedad ID en SortableProducts. Desde la etiqueta inteligente de Repeater, cree un objeto ObjectDataSource denominado ProductsDataSource y configúrelo para recuperar datos del método GetProducts() de la clase ProductsBLL. Seleccione la opción (None) en las listas desplegables de las pestañas INSERT, UPDATE y DELETE.

Create an ObjectDataSource and Configure it to Use the GetProductsAsPagedDataSource() Method

Figura 1: Creación de ObjectDataSource y configuración para que use el método GetProductsAsPagedDataSource() (Haga clic para ver la imagen a tamaño completo)

Set the Drop-Down Lists in the UPDATE, INSERT, and DELETE tabs to (None)

Figura 2: Establecimiento de las listas desplegables de las pestañas INSERT, UPDATE y DELETE en (None) (Haga clic para ver la imagen a tamaño completo)

A diferencia de DataList, Visual Studio no crea automáticamente un elemento ItemTemplate para el control Repeater después de enlazarlo a un origen de datos. Además, tendrá que agregar este elemento ItemTemplate mediante declaración, ya que la etiqueta inteligente del control Repeater carece de la opción Editar plantillas que sí tienen los controles DataList. Se usará el mismo elemento ItemTemplate del tutorial anterior, que muestra el nombre del producto, el proveedor y la categoría.

Después de agregar el elemento ItemTemplate, Repeater y ObjectDataSource, el marcado declarativo del control debe tener un aspecto similar al siguiente:

<asp:Repeater ID="SortableProducts" DataSourceID="ProductsDataSource"
    EnableViewState="False" runat="server">
    <ItemTemplate>
        <h4><asp:Label ID="ProductNameLabel" runat="server"
            Text='<%# Eval("ProductName") %>'></asp:Label></h4>
        Category:
        <asp:Label ID="CategoryNameLabel" runat="server"
            Text='<%# Eval("CategoryName") %>'></asp:Label><br />
        Supplier:
        <asp:Label ID="SupplierNameLabel" runat="server"
            Text='<%# Eval("SupplierName") %>'></asp:Label><br />
        <br />
        <br />
    </ItemTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProducts">
</asp:ObjectDataSource>

En la figura 3 se muestra esta página vista en un explorador.

Each Product s Name, Supplier, and Category is Displayed

Figura 3: Se muestra cada nombre de producto, proveedor y categoría (Haga clic para ver la imagen a tamaño completo)

Paso 3: Indicaciones para que ObjectDataSource ordene los datos

Para ordenar los datos que se muestran en el control Repeater, es necesario informar a ObjectDataSource de la expresión de ordenación por la que se deben ordenar los datos. Antes de que ObjectDataSource recupere sus datos, primero activa su evento Selecting, lo que proporciona una oportunidad para especificar una expresión de ordenación. Al controlador de eventos Selecting se le pasa un objeto de tipo ObjectDataSourceSelectingEventArgs, que tiene una propiedad Arguments de tipo DataSourceSelectArguments. La clase DataSourceSelectArguments está diseñada para pasar solicitudes de datos desde un consumidor de datos al control de origen de datos, e incluye una propiedad SortExpression.

Para pasar información de ordenación desde la página ASP.NET a ObjectDataSource, cree un controlador de eventos para el evento Selecting y use el código siguiente:

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    e.Arguments.SortExpression = sortExpression;
}

El valor de sortExpression debe tener asignado el nombre del campo de datos por el que se van a ordenar los datos (por ejemplo, ProductName). No hay ninguna propiedad relacionada con el sentido de la ordenación, por lo que si quiere ordenar los datos en orden descendente, anexe la cadena DESC al valor de sortExpression (por ejemplo, ProductName DESC).

Pruebe con algunos valores codificados de forma rígida para sortExpression y compruebe los resultados en un explorador. Como se muestra en la figura 4, cuando se usa ProductName DESC como valor de sortExpression, los productos se ordenan por nombre en orden alfabético inverso.

The Products are Sorted by their Name in Reverse Alphabetical Order

Figura 4: Los productos se ordenan por nombre en orden alfabético inverso (Haga clic para ver la imagen a tamaño completo)

Paso 4: Creación la interfaz de ordenación y recordatorio de la expresión y el sentido de la ordenación

Al activar la compatibilidad de ordenación en GridView, cada texto de encabezado del campo susceptible de ordenación se convierte en un control LinkButton que, cuando se presiona, ordena los datos como corresponda. Esta interfaz de ordenación tiene sentido para GridView, donde los datos están perfectamente dispuestos en columnas. Pero en los controles DataList y Repeater se necesita una interfaz de ordenación diferente. Una interfaz de ordenación común para una lista de datos (en lugar de una tabla de datos) es una lista desplegable que proporciona los campos por los que se pueden ordenar los datos. Ahora implementará esta interfaz en este tutorial.

Agregue un control web DropDownList encima del control Repeater SortableProducts y establezca su propiedad ID en SortBy. En la ventana Propiedades, haga clic en los puntos suspensivos de la propiedad Items, lo que abrirá el editor de la colección ListItem. Agregue varios elementos ListItem para ordenar los datos por los campos ProductName, CategoryName y SupplierName. Agregue también un elemento ListItem para ordenar los productos por nombre en orden alfabético inverso.

Las propiedades Text de ListItem se pueden establecer en cualquier valor (como Name), pero las propiedades Value se deben establecer en el nombre del campo de datos (como ProductName). Para ordenar los resultados en orden descendente, anexe la cadena DESC al nombre del campo de datos, como ProductName DESC.

Add a ListItem for Each of the Sortable Data Fields

Figura 5: Adición de un elemento ListItem para cada uno de los campos de datos susceptibles de ordenación

Por último, agregue un control web Button a la derecha de DropDownList. Establezca ID en RefreshRepeater y su propiedad Text en Refresh.

Después de crear los elementos ListItem y agregar el botón de actualización, la sintaxis declarativa de DropDownList y Button debe ser similar a la siguiente:

<asp:DropDownList ID="SortBy" runat="server">
    <asp:ListItem Value="ProductName">Name</asp:ListItem>
    <asp:ListItem Value="ProductName DESC">Name (Reverse Order)
        </asp:ListItem>
    <asp:ListItem Value="CategoryName">Category</asp:ListItem>
    <asp:ListItem Value="SupplierName">Supplier</asp:ListItem>
</asp:DropDownList>
<asp:Button runat="server" ID="RefreshRepeater" Text="Refresh" />

Con la ordenación de DropDownList completada, ahora es necesario actualizar el controlador de eventos Selecting de ObjectDataSource de forma que use la propiedad Value del elemento SortBy``ListItem seleccionado, en lugar de una expresión de ordenación codificada de forma rígida.

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    // Have the ObjectDataSource sort the results by the selected
    // sort expression
    e.Arguments.SortExpression = SortBy.SelectedValue;
}

En este momento, cuando visite la página por primera vez, los productos se ordenarán inicialmente por el campo de datos ProductName, ya que es el valor SortByListItem seleccionado de forma predeterminada (vea la figura 6). Si se selecciona otra opción de ordenación, como Category, y se hace clic en el botón de actualización, se producirá un postback y los datos se volverán a ordenar por nombre de la categoría, como se muestra en la figura 7.

The Products are Initially Sorted by their Name

Figura 6: Los productos están ordenados por nombre (Haga clic para ver la imagen a tamaño completo)

The Products are Now Sorted by Category

Figura 7: Ahora los productos se ordenan por categoría (Haga clic para ver la imagen a tamaño completo)

Nota:

Al hacer clic en el botón de actualización, los datos se vuelven a ordenar automáticamente, porque el estado de visualización del control Repeater se ha deshabilitado, lo que hace que se vuelva a enlazar a su origen de datos en cada postback. Si ha dejado habilitado el estado de visualización del control Repeater, cambiar la lista desplegable de ordenación no tendrá ningún efecto en el criterio de ordenación. Para solucionar este problema, cree un controlador de eventos para el evento Click del botón de actualización y vuelva a enlazar el control Repeater a su origen de datos (con una llamada al método DataBind() de ese control).

Recordatorio de la expresión y el sentido de la ordenación

Al crear un control DataList o Repeater susceptible de ordenación en una página en la que pueden producirse postbacks no relacionados con la ordenación, es fundamental recordar la expresión y el sentido de la ordenación de un postback a otro. Por ejemplo, imagine que ha actualizado el control Repeater de este tutorial para incluir un botón Eliminar con cada producto. Cuando el usuario hace clic en el botón Eliminar, se ejecutaría código para eliminar el producto seleccionado y, después, los datos se volverían a enlazar al control Repeater. Si los detalles de ordenación no se conservan entre postback, los datos que se muestran en la pantalla volverán al criterio de ordenación original.

En este tutorial, el control DropDownList guarda implícita y automáticamente la expresión y el sentido de la ordenación en su estado de visualización. Si usara otra interfaz de ordenación, por ejemplo, con controles LinkButton que proporcionaran distintas opciones de ordenación, tendría que recordar el criterio de ordenación entre postbacks. Esto se puede lograr si se almacenan los parámetros de ordenación en el estado de visualización de la página, se incluye el parámetro de ordenación en la cadena de consulta o mediante otra técnica de persistencia de estado.

En ejemplos posteriores de este tutorial se explora cómo conservar los detalles de ordenación en el estado de visualización de la página.

Paso 5: Adición de compatibilidad con la ordenación a un control DataList que usa la paginación predeterminada

En el tutorial anterior ha visto cómo implementar la paginación predeterminada en un control DataList. Ahora se ampliará este ejemplo anterior para incluir la capacidad ordenar los datos paginados. Para empezar, abra las páginas SortingWithDefaultPaging.aspx y Paging.aspx en la carpeta PagingSortingDataListRepeater. En la página Paging.aspx, haga clic en el botón Origen para ver el marcado declarativo de la página. Copie el texto seleccionado (vea la figura 8) y péguelo en el marcado declarativo de SortingWithDefaultPaging.aspx entre las etiquetas <asp:Content>.

Replicate the Declarative Markup in the <asp:Content> Tags from Paging.aspx to SortingWithDefaultPaging.aspx

Figura 8: Replicación del marcado declarativo en las etiquetas <asp:Content> de Paging.aspx a SortingWithDefaultPaging.aspx (Haga clic para ver la imagen a tamaño completo)

Después de copiar el marcado declarativo, copie los métodos y las propiedades de la clase de código subyacente de la página Paging.aspx en la clase de código subyacente de SortingWithDefaultPaging.aspx. Ahora, dedique un momento a ver la página SortingWithDefaultPaging.aspx en un explorador. Debe mostrar la misma funcionalidad y apariencia que Paging.aspx.

Mejora de ProductsBLL para incluir un método de paginación y ordenación predeterminadas

En el tutorial anterior se ha creado un método GetProductsAsPagedDataSource(pageIndex, pageSize) en la clase ProductsBLL que devolvía un objeto PagedDataSource. Este objeto PagedDataSource se ha rellenado con todos los productos (con el método GetProducts() de la BLL), pero al enlazarlo a DataList, solo se mostraban los registros correspondientes a los parámetros de entrada pageIndex y pageSize especificados.

Anteriormente en este tutorial se agregó compatibilidad de ordenación especificando la expresión de ordenación del controlador de eventos Selecting de ObjectDataSource. Esto funciona bien cuando se devuelve un objeto a ObjectDataSource que se puede ordenar, como el objeto ProductsDataTable devuelto por el método GetProducts(). Pero el objeto PagedDataSource devuelto por el método GetProductsAsPagedDataSource no admite la ordenación de su origen de datos interno. En su lugar, es necesario ordenar los resultados devueltos por el método GetProducts()antes de colocarlos en PagedDataSource.

Para ello, cree un método GetProductsSortedAsPagedDataSource(sortExpression, pageIndex, pageSize) en la clase ProductsBLL. Para ordenar el objeto ProductsDataTable devuelto por el método GetProducts(), especifique la propiedad Sort de su elemento DataTableView predeterminado:

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Select, false)]
public PagedDataSource GetProductsSortedAsPagedDataSource
    (string sortExpression, int pageIndex, int pageSize)
{
    // Get ALL of the products
    Northwind.ProductsDataTable products = GetProducts();
    // Sort the products
    products.DefaultView.Sort = sortExpression;
    // Limit the results through a PagedDataSource
    PagedDataSource pagedData = new PagedDataSource();
    pagedData.DataSource = products.DefaultView;
    pagedData.AllowPaging = true;
    pagedData.CurrentPageIndex = pageIndex;
    pagedData.PageSize = pageSize;
    return pagedData;
}

El método GetProductsSortedAsPagedDataSource difiere ligeramente del método GetProductsAsPagedDataSource creado en el tutorial anterior. En concreto, GetProductsSortedAsPagedDataSource acepta un parámetro de entrada adicional sortExpression y asigna este valor a la propiedad Sort del DefaultViewelemento de ProductDataTable. Unas cuantas líneas de código después, al elemento DataSource del objeto PagedDataSource se le asigna el valor DefaultView de ProductDataTable.

Llamada al método GetProductsSortedAsPagedDataSource y especificación del valor del parámetro de entrada SortExpression

Con el método GetProductsSortedAsPagedDataSource completado, el siguiente paso es proporcionar el valor de este parámetro. El objeto ObjectDataSource en SortingWithDefaultPaging.aspx está configurado actualmente para llamar al método GetProductsAsPagedDataSource, y pasa los dos parámetros de entrada mediante dos QueryStringParameters, que se especifican en la colección SelectParameters. Estos dos elementos QueryStringParameters indican que el origen de los parámetros pageIndex y pageSize del método GetProductsAsPagedDataSource procede de los elementos pageIndex y pageSize del campo de cadena de consulta.

Actualice la propiedad SelectMethod de ObjectDataSource de forma que invoque el nuevo método GetProductsSortedAsPagedDataSource. Después, agregue un elemento QueryStringParameter nuevo para acceder al parámetro de entrada sortExpression desde el elemento sortExpression del campo de cadena de consulta. Establezca el elemento DefaultValue de QueryStringParameter en ProductName.

Después de estos cambios, el marcado declarativo de ObjectDataSource debe ser similar al siguiente:

<asp:ObjectDataSource ID="ProductsDefaultPagingDataSource"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsSortedAsPagedDataSource"
    OnSelected="ProductsDefaultPagingDataSource_Selected" runat="server">
    <SelectParameters>
        <asp:QueryStringParameter DefaultValue="ProductName"
            Name="sortExpression" QueryStringField="sortExpression"
            Type="String" />
        <asp:QueryStringParameter DefaultValue="0" Name="pageIndex"
            QueryStringField="pageIndex" Type="Int32" />
        <asp:QueryStringParameter DefaultValue="4" Name="pageSize"
            QueryStringField="pageSize" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

En este momento, la página SortingWithDefaultPaging.aspx ordenará los resultados alfabéticamente por nombre del producto (vea la figura 9). Esto se debe a que, de forma predeterminada, se pasa un valor de ProductName como parámetro sortExpression del método GetProductsSortedAsPagedDataSource.

By Default, the Results are Sorted by ProductName

Figura 9: Los resultados se ordenan de forma predeterminada por ProductName (Haga clic para ver la imagen a tamaño completo)

Si agrega manualmente un campo de cadena de consulta de sortExpression, como SortingWithDefaultPaging.aspx?sortExpression=CategoryName, los resultados se ordenarán por el valor sortExpression especificado. Pero este parámetro sortExpression no se incluye en la cadena de consulta al pasar a una página de datos diferente. De hecho, al hacer clic en los botones Siguiente o Última página, se vuelve a Paging.aspx. Además, actualmente no hay ninguna interfaz de ordenación. La única manera de que un usuario pueda cambiar el criterio de ordenación de los datos paginados es si manipula la cadena de consulta directamente.

Creación de la interfaz de ordenación

En primer lugar, es necesario actualizar el método RedirectUser para dirigir al usuario a SortingWithDefaultPaging.aspx (en lugar de a Paging.aspx) e incluir el valor de sortExpression en la cadena de consulta. También debería agregar una propiedad SortExpression con nombre, de solo lectura y de nivel de página. Esta propiedad, similar a las propiedades PageIndex y PageSize creadas en el tutorial anterior, devuelve el valor del campo de cadena de consulta sortExpression, si existe, y el valor predeterminado (ProductName) si no.

Actualmente, el método RedirectUser solo acepta un único parámetro de entrada: el índice de la página se va a mostrar. Pero puede haber ocasiones en las que quiera redirigir al usuario a una página determinada de datos mediante una expresión de ordenación distinta de la especificada en la cadena de consulta. En un momento, creará la interfaz de ordenación de esta página, que incluirá una serie de controles web Button para ordenar los datos por una columna especificada. Cuando se hace clic en uno de esos controles Button, querrá redirigir al usuario y pasar el valor de expresión de ordenación adecuado. Para proporcionar esta funcionalidad, cree dos versiones del método RedirectUser. La primera debe aceptar solo el índice de página que se va a mostrar, mientras que la segunda acepta el índice de página y la expresión de ordenación.

private string SortExpression
{
    get
    {
        if (!string.IsNullOrEmpty(Request.QueryString["sortExpression"]))
            return Request.QueryString["sortExpression"];
        else
            return "ProductName";
    }
}
private void RedirectUser(int sendUserToPageIndex)
{
    // Use the SortExpression property to get the sort expression
    // from the querystring
    RedirectUser(sendUserToPageIndex, SortExpression);
}
private void RedirectUser(int sendUserToPageIndex, string sendUserSortingBy)
{
   // Send the user to the requested page with the requested sort expression
   Response.Redirect(string.Format(
      "SortingWithDefaultPaging.aspx?pageIndex={0}&pageSize={1}&sortExpression={2}",
      sendUserToPageIndex, PageSize, sendUserSortingBy));
}

En el primer ejemplo de este tutorial, se ha creado una interfaz de ordenación con un control DropDownList. En este ejemplo, se usarán tres controles web Button situados encima de DataList, uno para ordenar por ProductName, uno para CategoryName y otro para SupplierName. Agregue los tres controles web Button y establezca sus propiedades ID y Text adecuadamente:

<p>
    <asp:Button runat="server" id="SortByProductName"
        Text="Sort by Product Name" />
    <asp:Button runat="server" id="SortByCategoryName"
        Text="Sort by Category" />
    <asp:Button runat="server" id="SortBySupplierName"
        Text="Sort by Supplier" />
</p>

A continuación, cree un controlador de eventos Click para cada uno. Los controladores de eventos deben llamar al método RedirectUser y devolver al usuario a la primera página mediante la expresión de ordenación adecuada.

protected void SortByProductName_Click(object sender, EventArgs e)
{
    // Sort by ProductName
    RedirectUser(0, "ProductName");
}
protected void SortByCategoryName_Click(object sender, EventArgs e)
{
    // Sort by CategoryName
    RedirectUser(0, "CategoryName");
}
protected void SortBySupplierName_Click(object sender, EventArgs e)
{
    // Sort by SupplierName
    RedirectUser(0, "SupplierName");
}

Al visitar la página por primera vez, los datos se ordenan por el nombre del producto alfabéticamente (vea la figura 9). Haga clic en el botón Siguiente para avanzar a la segunda página de datos y, después, haga clic en el botón Ordenar por categoría. Esto le devuelve a la primera página de datos, ordenada por nombre de categoría (vea la figura 10). Del mismo modo, al hacer clic en el botón Ordenar por proveedor, se ordenan los datos por proveedor a partir de la primera página de datos. La opción de ordenación se recuerda a medida que los datos se paginan. En la figura 11 se muestra la página después de ordenar por categoría y avanzar a la decimotercera página de datos.

The Products are Sorted by Category

Figura 10: Los productos se ordenan por categoría (Haga clic para ver la imagen a tamaño completo)

The Sort Expression is Remembered When Paging Through the Data

Figura 11: La expresión de ordenación se recuerda al paginar por los datos (Haga clic para ver la imagen a tamaño completo)

Paso 6: Paginación personalizada por los registros en un control Repeater

El ejemplo de DataList examinado en el paso 5 pagina por los datos mediante la técnica de paginación predeterminada, que no es muy eficaz. Al paginar por grandes cantidades de datos, es esencial usar la paginación personalizada. En los tutoriales Paginación de forma eficaz a por grandes cantidades de datos y Ordenación de datos paginados personalizados se han visto las diferencias entre paginación predeterminada y personalizada, y los métodos creados en la BLL para usar la paginación personalizada y ordenar los datos paginados personalizados. En concreto, en estos dos tutoriales anteriores se agregaron los tres métodos siguientes a la clase ProductsBLL:

  • GetProductsPaged(startRowIndex, maximumRows) devuelve un determinado subconjunto de registros que empieza por startRowIndex y no supera maximumRows.
  • GetProductsPagedAndSorted(sortExpression, startRowIndex, maximumRows) devuelve un determinado subconjunto de registros ordenados por el parámetro de entrada sortExpression especificado.
  • TotalNumberOfProducts() proporciona el número total de registros en la tabla de base de datos Products.

Estos métodos se pueden usar para paginar y ordenar datos de forma eficaz mediante un control DataList o Repeater. Para ilustrar esto, primero se creará un control Repeater con compatibilidad de paginación personalizada; después, se agregarán funcionalidades de ordenación.

Abra la página SortingWithCustomPaging.aspx en la carpeta PagingSortingDataListRepeater y agregue un control Repeater a la página, estableciendo su propiedad ID en Products. En la etiqueta inteligente del control Repeater, elija crear un objeto ObjectDataSource denominado ProductsDataSource. Configúrelo para seleccionar los datos del método GetProductsPaged de la clase ProductsBLL.

Configure the ObjectDataSource to Use the ProductsBLL Class s GetProductsPaged Method

Figura 12: Configuración de ObjectDataSource para usar el método GetProductsPaged de la clase ProductsBLL (Haga clic para ver la imagen a tamaño completo)

Establezca las listas desplegables de las pestañas UPDATE, INSERT y DELETE en (None) y, después, haga clic en el botón Siguiente. Ahora, el asistente para la configuración de orígenes de datos solicita los orígenes de los parámetros de entrada startRowIndex y maximumRows del método GetProductsPaged. En realidad, estos parámetros de entrada se omiten. En su lugar, los valores de startRowIndex y maximumRows se pasarán a través de la propiedad Arguments en el controlador de eventos Selecting de ObjectDataSource, igual que como se ha especificadosortExpression en la primera demostración de este tutorial. Por tanto, deje las listas desplegables de orígenes de parámetros del asistente establecidas en None.

Leave the Parameter Sources Set to None

Figura 13: Los orígenes de parámetros se dejan establecidos en None (Haga clic para ver la imagen a tamaño completo)

Nota:

No establezca la propiedad EnablePaging de ObjectDataSource en true. Esto hará que e ObjectDataSource incluya automáticamente sus propios parámetros startRowIndex y maximumRows en la lista de parámetros existente de SelectMethod. La propiedad EnablePaging es útil al enlazar datos paginados personalizados a un control GridView, DetailsView o FormView, ya que estos controles esperan un determinado comportamiento de ObjectDataSource que solo está disponible cuando la propiedad EnablePaging es true. Como hay que agregar manualmente la compatibilidad de paginación para DataList y Repeater, deje esta propiedad establecida en false (el valor predeterminado), ya que se creará la funcionalidad necesaria directamente dentro de la página ASP.NET.

Por último, defina el elemento ItemTemplate del control Repeater para mostrar el nombre, la categoría y el proveedor del producto. Después de realizar estos dos cambios, la sintaxis declarativa de Repeater y ObjectDataSource debe ser similar a la siguiente:

<asp:Repeater ID="Products" runat="server" DataSourceID="ProductsDataSource"
    EnableViewState="False">
    <ItemTemplate>
        <h4><asp:Label ID="ProductNameLabel" runat="server"
            Text='<%# Eval("ProductName") %>'></asp:Label></h4>
        Category:
        <asp:Label ID="CategoryNameLabel" runat="server"
            Text='<%# Eval("CategoryName") %>'></asp:Label><br />
        Supplier:
        <asp:Label ID="SupplierNameLabel" runat="server"
            Text='<%# Eval("SupplierName") %>'></asp:Label><br />
        <br />
        <br />
    </ItemTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProductsPaged" TypeName="ProductsBLL">
    <SelectParameters>
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

Dedique un momento a visitar la página en un explorador y fíjese en que no se devuelve ningún registro. Esto se debe a que todavía debe especificar los valores de parámetro de startRowIndex y maximumRows, de ahí que se pase un valor de 0 en ambos. Para especificar estos valores, cree un controlador de eventos para el evento Selecting de ObjectDataSource y establezca estos valores de parámetro mediante programación en unos valores codificados de forma rígida de 0 y 5 respectivamente:

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    e.InputParameters["startRowIndex"] = 0;
    e.InputParameters["maximumRows"] = 5;
}

Con este cambio, cuando la página se ve en un explorador, muestra los cinco primeros productos.

The First Five Records are Displayed

Figura 14: Se muestran los cinco primeros registros (Haga clic para ver la imagen a tamaño completo)

Nota:

Los productos que se muestran en la figura 14 se ordenan por nombre de producto porque el procedimiento almacenado GetProductsPaged que realiza la consulta de paginación personalizada eficaz ordena los resultados por ProductName.

Para permitir que el usuario recorra las páginas, es necesario realizar el seguimiento del índice de fila de inicio y las filas máximas, y recordar estos valores entre postbacks. En el ejemplo de paginación predeterminado, se han usado campos de cadena de consulta para conservar estos valores; en esta demostración, se conservará esta información en el estado de visualización de la página. Cree las dos propiedades siguientes:

private int StartRowIndex
{
    get
    {
        object o = ViewState["StartRowIndex"];
        if (o == null)
            return 0;
        else
            return (int)o;
    }
    set
    {
        ViewState["StartRowIndex"] = value;
    }
}
private int MaximumRows
{
    get
    {
        object o = ViewState["MaximumRows"];
        if (o == null)
            return 5;
        else
            return (int)o;
    }
    set
    {
        ViewState["MaximumRows"] = value;
    }
}

A continuación, actualice el código en el controlador de eventos Selecting para que use las propiedades StartRowIndex y MaximumRows en lugar de los valores codificados de forma rígida de 0 y 5:

e.InputParameters["startRowIndex"] = StartRowIndex;
e.InputParameters["maximumRows"] = MaximumRows;

En este momento, la página todavía sigue mostrando solo los cinco primeros registros. Pero con estas propiedades presentes, ya se puede crear la interfaz de paginación.

Adición de la interfaz de paginación

Se usará la misma interfaz de paginación (Primera, Anterior, Siguiente, Última) del ejemplo de paginación predeterminado, incluido el control web Label que muestra qué página de datos se ve y cuántas páginas totales existen. Agregue los cuatro controles web Button y el control Label debajo de Repeater.

<p>
    <asp:Button runat="server" ID="FirstPage" Text="<< First" />
    <asp:Button runat="server" ID="PrevPage" Text="< Prev" />
    <asp:Button runat="server" ID="NextPage" Text="Next >" />
    <asp:Button runat="server" ID="LastPage" Text="Last >>" />
</p>
<p>
    <asp:Label runat="server" ID="CurrentPageNumber"></asp:Label>
</p>

A continuación, cree controladores de eventos Click para los cuatro botones. Cuando se haga clic en uno de estos botones, es necesario actualizar StartRowIndex y volver a enlazar los datos al control Repeater. El código de los botones Primera, Anterior y Siguiente es sencillo, pero en cuanto al botón Última, ¿cómo se determina el índice de fila inicial de la última página de datos? Para calcular este índice, así como para determinar si se deben habilitar los botones Siguiente y Última, es necesario saber cuántos registros en total se paginan. Se puede determinarlo con una llamada al método TotalNumberOfProducts() de la clase ProductsBLL. Se creará una propiedad de solo lectura y de nivel de página denominada TotalRowCount que devuelve los resultados del método TotalNumberOfProducts():

private int TotalRowCount
{
    get
    {
        // Return the value from the TotalNumberOfProducts() method
        ProductsBLL productsAPI = new ProductsBLL();
        return productsAPI.TotalNumberOfProducts();
    }
}

Con esta propiedad, ahora se puede determinar el índice de fila de inicio de la última página. En concreto, es un resultado de número entero de TotalRowCount menos 1, dividido entre MaximumRows y multiplicado por MaximumRows. Ahora puede escribir los controladores de eventos Click de los cuatro botones de la interfaz de paginación:

protected void FirstPage_Click(object sender, EventArgs e)
{
    // Return to StartRowIndex of 0 and rebind data
    StartRowIndex = 0;
    Products.DataBind();
}
protected void PrevPage_Click(object sender, EventArgs e)
{
    // Subtract MaximumRows from StartRowIndex and rebind data
    StartRowIndex -= MaximumRows;
    Products.DataBind();
}
protected void NextPage_Click(object sender, EventArgs e)
{
    // Add MaximumRows to StartRowIndex and rebind data
    StartRowIndex += MaximumRows;
    Products.DataBind();
}
protected void LastPage_Click(object sender, EventArgs e)
{
    // Set StartRowIndex = to last page's starting row index and rebind data
    StartRowIndex = ((TotalRowCount - 1) / MaximumRows) * MaximumRows;
    Products.DataBind();
}

Por último, debe deshabilitar los botones Primera y Anterior en la interfaz de paginación al ver la primera página de datos y los botones Siguiente y Última al ver la última. Para ello, agregue el código siguiente al controlador de eventos Selecting de ObjectDataSource:

// Disable the paging interface buttons, if needed
FirstPage.Enabled = StartRowIndex != 0;
PrevPage.Enabled = StartRowIndex != 0;
int LastPageStartRowIndex = ((TotalRowCount - 1) / MaximumRows) * MaximumRows;
NextPage.Enabled = StartRowIndex < LastPageStartRowIndex;
LastPage.Enabled = StartRowIndex < LastPageStartRowIndex;

Después de agregar estos controladores de eventos Click y el código para habilitar o deshabilitar los elementos de la interfaz de paginación según el índice de fila de inicio actual, pruebe la página en un explorador. Como se muestra en la figura 15, al visitar por primera vez la página, se deshabilitarán los botones Primera y Anterior. Al hacer clic en Siguiente, se muestra la segunda página de datos, mientras que al hacer clic en Última, se muestra la última página (vea las figuras 16 y 17). Al ver la última página de datos, los botones Siguiente y Última están deshabilitados.

The Previous and Last Buttons are Disabled When Viewing the First Page of Products

Figura 15: Los botones Anterior y Última están deshabilitados al ver la primera página de productos (Haga clic para ver la imagen a tamaño completo)

The Second Page of Products are Displayed

Figura 16: Se muestra la segunda página de productos (Haga clic para ver la imagen a tamaño completo)

Clicking Last Displays the Final Page of Data

Figura 17: Al hacer clic en Última, se muestra la última página de datos (Haga clic para ver la imagen a tamaño completo)

Paso 7: Inclusión de compatibilidad de ordenación con el control Repeater paginado personalizado

Ahora que ha implementado la paginación personalizada, ya puede incluir compatibilidad con la ordenación. El método GetProductsPagedAndSorted de la clase ProductsBLL tiene los mismos parámetros de entrada startRowIndex y maximumRows que GetProductsPaged, pero permite un parámetro de entrada sortExpression más. Para usar el método GetProductsPagedAndSorted de SortingWithCustomPaging.aspx, debe seguir estos pasos:

  1. Cambiar la propiedad SelectMethod de ObjectDataSource de GetProductsPaged a GetProductsPagedAndSorted.
  2. Agregar un objeto sortExpressionParameter a la colección SelectParameters de ObjectDataSource.
  3. Crear una propiedad SortExpression privada y de nivel de página que conserve su valor entre postbacks mediante el estado de visualización de la página.
  4. Actualizar el controlador de eventos Selecting de ObjectDataSource para asignar al parámetro sortExpression de ObjectDataSource el valor de la propiedad SortExpression de nivel de página.
  5. Crear la interfaz de ordenación.

Para empezar, actualice la propiedad SelectMethod de ObjectDataSource; para ello, agregue un objeto sortExpressionParameter. Asegúrese de que la propiedad Type de Parameter del objeto sortExpression está establecida en String. Después de completar estas dos primeras tareas, el marcado declarativo del control ObjectDataSource debería tener un aspecto similar al siguiente:

<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsPagedAndSorted"
    OnSelecting="ProductsDataSource_Selecting">
    <SelectParameters>
        <asp:Parameter Name="sortExpression" Type="String" />
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

A continuación, necesita una propiedad SortExpression de nivel de página cuyo valor se serialice en el estado de visualización. Si no se ha establecido ningún valor de expresión de ordenación, use ProductName como valor predeterminado:

private string SortExpression
{
    get
    {
        object o = ViewState["SortExpression"];
        if (o == null)
            return "ProductName";
        else
            return o.ToString();
    }
    set
    {
        ViewState["SortExpression"] = value;
    }
}

Antes de que ObjectDataSource invoque el método GetProductsPagedAndSorted, es necesario establecer sortExpressionParameter en el valor de la propiedad SortExpression. En el controlador de eventos Selecting, agregue la siguiente línea de código:

e.InputParameters["sortExpression"] = SortExpression;

Lo único que queda es implementar la interfaz de ordenación. Como en el último ejemplo, se implementará mediante tres controles web Button que permiten al usuario ordenar los resultados por nombre de producto, categoría o proveedor.

<asp:Button runat="server" id="SortByProductName"
    Text="Sort by Product Name" />
<asp:Button runat="server" id="SortByCategoryName"
    Text="Sort by Category" />
<asp:Button runat="server" id="SortBySupplierName"
    Text="Sort by Supplier" />

Cree controladores de eventos Click para estos tres controles Button. En el controlador de eventos, restablezca StartRowIndex a 0, establezca SortExpression en el valor adecuado y vuelva a enlazar los datos al control Repeater:

protected void SortByProductName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "ProductName";
    Products.DataBind();
}
protected void SortByCategoryName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "CategoryName";
    Products.DataBind();
}
protected void SortBySupplierName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "CompanyName";
    Products.DataBind();
}

Así de simple. Aunque han sido necesarios una serie de pasos para implementar la paginación y ordenación personalizadas, fueron muy similares a los de la paginación predeterminada. En la figura 18 se muestran los productos al ver la última página de datos cuando se ordenan por categoría.

The Last Page of Data, Sorted by Category, is Displayed

Figura 18: Se muestra la última página de datos ordenada por categoría (Haga clic para ver la imagen a tamaño completo)

Nota:

En ejemplos anteriores, al ordenar por proveedor, se usaba el valor SupplierName como expresión de ordenación. Pero en la implementación de paginación personalizada, debe usar CompanyName. Esto se debe a que el procedimiento almacenado encargado de implementar la paginación personalizada, GetProductsPagedAndSorted, pasa la expresión de ordenación en la palabra clave ROW_NUMBER(), y la palabra clave ROW_NUMBER() requiere el nombre de columna real en lugar de un alias. Por tanto, se debe usar CompanyName (el nombre de la columna de la tabla Suppliers) en lugar del alias usado en la consulta SELECT (SupplierName) en la expresión de ordenación.

Resumen

Ni DataList ni Repeater ofrecen compatibilidad de ordenación integrada, pero con un poco de código y una interfaz de ordenación personalizada, se puede agregar esa funcionalidad. Al implementar la ordenación, pero no la paginación, la expresión de ordenación se puede especificar desde l objeto DataSourceSelectArguments pasado al método Select de ObjectDataSource. La propiedad SortExpression de este objeto DataSourceSelectArguments se puede asignar en el controlador de eventos Selecting de ObjectDataSource.

Para agregar funcionalidades de ordenación a un control DataList o Repeater que ya proporciona compatibilidad con la paginación, el enfoque más sencillo es personalizar la capa de lógica de negocios de manera que incluya un método que acepte una expresión de ordenación. Después, esta información se puede pasar a través de un parámetro en el elemento SelectParameters de ObjectDataSource.

Este tutorial completa el análisis sobre la paginación y ordenación con los controles DataList y Repeater. En el siguiente y último tutorial verá cómo agregar controles web Button a las plantillas de DataList y Repeater para proporcionar algo de funcionalidad personalizada iniciada por el usuario según cada elemento.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP y ASP.NET, y fundador de 4GuysFromRolla.com, trabaja con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él a través de mitchell@4GuysFromRolla.com. o de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores. El revisor principal de este tutorial ha sido David Suru. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.