Ordenar datos en un control DataList o Repeater (VB)

Por Scott Mitchell

Descargar PDF

En este tutorial examinaremos 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 pueden paginar y ordenar.

Introducción

En el tutorial anterior vimos cómo agregar compatibilidad de paginación a un DataList. Creamos un nuevo método en la clase ProductsBLL (GetProductsAsPagedDataSource) que devolvía un objeto PagedDataSource. Cuando se enlazaba a un DataList o Repeater, el 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 GridView también incluye compatibilidad con la ordenación lista para usar. Ni DataList ni Repeater proporcionan funcionalidades de ordenación integradas; sin embargo, con un poco de código se pueden agregar características de ordenación. En este tutorial examinaremos 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 pueden paginar y ordenar.

Un repaso de la ordenación

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

El control GridView también tiene una propiedad SortExpression que almacena el elemento SortExpression del campo del 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 del GridView, se alterna el criterio de ordenación).

Cuando el GridView está enlazado 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, a continuación, los ordena según las propiedades SortExpression y SortDirection proporcionadas. Después de ordenar los datos, el control de origen de datos los devuelve al GridView.

Para replicar esta funcionalidad con los controles DataList o Repeater, debemos 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 al ObjectDataSource que ordene los datos por un campo de datos determinado

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

Paso 2: Mostrar los productos en un control Repeater

Antes de preocuparnos por implementar cualquiera de las funciones relacionadas con la ordenación, comencemos enumerando 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, estableciendo su propiedad ID en SortableProducts. En la etiqueta inteligente del Repeater, cree un nuevo objeto ObjectDataSource denominado ProductsDataSource y configúrelo para recuperar datos del método GetProducts() de la clase ProductsBLL. Seleccione la opción (Ninguno) en las listas desplegables de las pestañas INSERTAR, ACTUALIZAR y ELIMINAR.

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

Figura 1: Crear un ObjectDataSource y configurarlo 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 ACTUALIZAR, INSERTAR y ELIMINAR en (Ninguno) (haga clic para ver la imagen a tamaño completo)

A diferencia del DataList, Visual Studio no crea automáticamente un ItemTemplate para el control Repeater después de enlazarlo a un origen de datos. Además, debemos agregar esto ItemTemplate declarativamente, ya que la etiqueta inteligente del control Repeater carece de la opción Editar plantillas que sí tienen los controles DataList. Vamos a 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, el marcado declarativo del control Repeater y ObjectDataSource 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 desde 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: Indicar al ObjectDataSource que ordene los datos

Para ordenar los datos que se muestran en el control Repeater, es necesario informar al 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 pasa un objeto de tipo ObjectDataSourceSelectingEventArgs, que tiene una propiedad denominada 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 al ObjectDataSource, cree un controlador de eventos para el evento Selecting y use el código siguiente:

Protected Sub ProductsDataSource_Selecting(ByVal sender As Object, _
    ByVal e As ObjectDataSourceSelectingEventArgs) _
    Handles ProductsDataSource.Selecting
    e.Arguments.SortExpression = sortExpression
End Sub

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 desea 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 constate 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: Crear la interfaz de ordenación y recordar la expresión y el sentido de la ordenación

Al activar la compatibilidad de ordenación en el 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 el GridView, donde los datos están perfectamente dispuestos en columnas. Sin embargo, 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. Vamos a implementar esta interfaz en este tutorial.

Agregue un control web DropDownList encima del RepeaterSortableProducts 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 ListItem para ordenar los datos por los campos ProductName, CategoryName y SupplierName. Agregue también un 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 deben establecerse 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 ListItem para cada uno de los campos de datos susceptibles de ordenación

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

Después de crear los ListItem y agregar el botón de actualización, la sintaxis declarativa de DropDownList y Button debe ser similares 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 necesitamos actualizar el controlador de eventos Selecting del ObjectDataSource de forma que use la propiedad Value del SortBy``ListItem seleccionado, en lugar de una expresión de ordenación codificada de forma rígida.

Protected Sub ProductsDataSource_Selecting _
    (ByVal sender As Object, ByVal e As ObjectDataSourceSelectingEventArgs) _
    Handles ProductsDataSource.Selecting
    ' Have the ObjectDataSource sort the results by the
    ' selected sort expression
    e.Arguments.SortExpression = SortBy.SelectedValue
End Sub

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 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 aquí para ver la imagen a tamaño completo)

The Products are Now Sorted by Category

Figura 7: Los productos están ordenados por categoría (haga clic aquí 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 este vuelva a enlazarse 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 (llamando al método DataBind() de dicho control).

Recordar 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 imperativo recordar la expresión y el sentido de la ordenación de un postback a otro. Por ejemplo, imagine que hemos actualizado el control Repeater en 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, a continuación, los datos se volverían a enlazar al control Repeater. Si los detalles de ordenación no se conservan en postback, los datos que se muestran en la pantalla volverán al criterio de ordenación original.

En este tutorial, el DropDownList guarda implícita y automáticamente la expresión y el sentido de la ordenación en su estado de visualización. Si estuviéramos usando una interfaz de ordenación diferente con, por ejemplo, controles LinkButton que proporcionaran distintas opciones de ordenación, tendríamos que tener cuidado de recordar el criterio de ordenación entre postbacks. Esto se puede lograr almacenando los parámetros de ordenación en el estado de visualización de la página, incluyendo el parámetro de ordenación en la cadena de consulta o a través de alguna otra técnica de persistencia de estado.

En ejemplos futuros 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: Agregar compatibilidad de ordenación a un control DataList que usa la paginación predeterminada

En el tutorial anterior vimos cómo implementar la paginación predeterminada en un DataList. Vamos a ampliar este ejemplo anterior para incluir funciones de ordenación de los datos paginados. Comience abriendo 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 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.

Mejorar ProductsBLL para incluir un método de paginación y ordenación predeterminados

En el tutorial anterior creamos un método GetProductsAsPagedDataSource(pageIndex, pageSize) en la clase ProductsBLL que devolvió un objeto PagedDataSource. Este objeto PagedDataSource se rellenó con todos los productos (a través del método GetProducts() de la BLL), pero al enlazarlo al DataList, solo se mostraban los registros correspondientes a los parámetros de entrada pageIndex y pageSize especificados.

Anteriormente en este tutorial agregamos compatibilidad de ordenación especificando la expresión de ordenación del controlador de eventos Selecting del 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(). Sin embargo, 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 el PagedDataSource.

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

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Select, False)> _
Public Function GetProductsSortedAsPagedDataSource _
    (sortExpression As String, pageIndex As Integer, pageSize As Integer) _
    As PagedDataSource
    ' Get ALL of the products
    Dim products As Northwind.ProductsDataTable = GetProducts()
    'Sort the products
    products.DefaultView.Sort = sortExpression
    ' Limit the results through a PagedDataSource
    Dim pagedData As New PagedDataSource()
    pagedData.DataSource = products.DefaultView
    pagedData.AllowPaging = True
    pagedData.CurrentPageIndex = pageIndex
    pagedData.PageSize = pageSize
    Return pagedData
End Function

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 DefaultView de ProductDataTable. Unas cuantas líneas de código más adelante, al DataSource del objeto PagedDataSource se le asigna el DefaultView de ProductDataTable.

Llamar al método GetProductsSortedAsPagedDataSource y especificar el 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 ObjectDataSource en SortingWithDefaultPaging.aspx está configurado actualmente para llamar al método GetProductsAsPagedDataSource, y pasa los dos parámetros de entrada a través de sus 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 proceden de los elementos pageIndex y pageSize del campo de cadena de consulta.

Actualice la propiedad SelectMethod del ObjectDataSource de forma que invoque el nuevo método GetProductsSortedAsPagedDataSource. A continuación, agregue un 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 sortExpression especificado. Sin embargo, este parámetro sortExpression no se incluye en la cadena de consulta al pasar a una página de datos diferente. ¡De hecho, si hacemos clic en los botones Siguiente o Última página, volveremos 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 manipulando la cadena de consulta directamente.

Crear 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íamos 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. Sin embargo, puede haber ocasiones en las que queremos 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, crearemos 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, queremos redirigir al usuario pasando el valor de expresión de ordenación adecuado. Para proporcionar esta funcionalidad, crearemos 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 ReadOnly Property SortExpression() As String
    Get
        If Not String.IsNullOrEmpty(Request.QueryString("sortExpression")) Then
            Return Request.QueryString("sortExpression")
        Else
            Return "ProductName"
        End If
    End Get
End Property
Private Sub RedirectUser(ByVal sendUserToPageIndex As Integer)
    ' Use the SortExpression property to get the sort expression
    ' from the querystring
    RedirectUser(sendUserToPageIndex, SortExpression)
End Sub
Private Sub RedirectUser(ByVal sendUserToPageIndex As Integer,
    ByVal sendUserSortingBy As String)
    ' 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))
End Sub

En el primer ejemplo de este tutorial, creamos una interfaz de ordenación con un DropDownList. En este ejemplo, vamos a usar 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, estableciendo 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 Sub SortByProductName_Click(sender As Object, e As EventArgs) _
    Handles SortByProductName.Click
    'Sort by ProductName
    RedirectUser(0, "ProductName")
End Sub
Protected Sub SortByCategoryName_Click(sender As Object, e As EventArgs) _
    Handles SortByCategoryName.Click
    'Sort by CategoryName
    RedirectUser(0, "CategoryName")
End Sub
Protected Sub SortBySupplierName_Click(sender As Object, e As EventArgs) _
    Handles SortBySupplierName.Click
    'Sort by SupplierName
    RedirectUser(0, "SupplierName")
End Sub

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, a continuación, haga clic en el botón Ordenar por categoría. Esto nos 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 aquí 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. Si volvemos a los tutoriales Paginar de forma eficaz a través de grandes cantidades de datos y Ordenar datos paginados personalizados, en ellos vimos 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 agregamos 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, comencemos creando un control Repeater con compatibilidad de paginación personalizada; después, agregaremos 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 ACTUALIZAR, INSERTAR y ELIMINAR en (Ninguno) y, a continuación, 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 del ObjectDataSource, sencillamente igual que como especificamos sortExpression en la primera demostración de este tutorial. Por lo tanto, deje las listas desplegables de orígenes de parámetros del asistente establecidas en Ninguno.

Leave the Parameter Sources Set to None

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

Nota:

No establezca la propiedad EnablePaging del ObjectDataSource en true. Esto hará que el 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 del ObjectDataSource que solo está disponible cuando la propiedad EnablePaging es true. Como tenemos que agregar manualmente la compatibilidad de paginación para DataList y Repeater, deje esta propiedad establecida en false (el valor predeterminado), ya que crearemos 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 del Repeater y el 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 a través de un explorador y fíjese en que no se devuelve ningún registro. Esto se debe a que todavía tenemos que 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 del 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 Sub ProductsDataSource_Selecting(sender As Object, _
    e As ObjectDataSourceSelectingEventArgs) _
    Handles ProductsDataSource.Selecting
    e.InputParameters("startRowIndex") = 0
    e.InputParameters("maximumRows") = 5
End Sub

Con este cambio, cuando la página se visualice a través de 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 un 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, usamos campos de cadena de consulta para conservar estos valores; en esta demostración, vamos a conservar esta información en el estado de visualización de la página. Cree las dos propiedades siguientes:

Private Property StartRowIndex() As Integer
    Get
        Dim o As Object = ViewState("StartRowIndex")
        If o Is Nothing Then
            Return 0
        Else
            Return CType(o, Integer)
        End If
    End Get
    Set(ByVal value As Integer)
        ViewState("StartRowIndex") = value
    End Set
End Property
Private Property MaximumRows() As Integer
    Get
        Dim o As Object = ViewState("MaximumRows")
        If o Is Nothing Then
            Return 5
        Else
            Return CType(o, Integer)
        End If
    End Get
    Set(ByVal value As Integer)
        ViewState("MaximumRows") = value
    End Set
End Property

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") = 0
e.InputParameters("maximumRows") = 5

En este momento, nuestra página todavía sigue mostrando solo los cinco primeros registros. Sin embargo, con estas propiedades presentes, estaremos listos para crear la interfaz de paginación.

Agregar la interfaz de paginación

Vamos a usar la misma interfaz de paginación (Primera, Anterior, Siguiente, Última) usada en el ejemplo de paginación predeterminado, incluido el control web Label que muestra qué página de datos se está viendo y cuántas páginas totales existen. Agregue los cuatro controles web Button y el control Label debajo del 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. Podemos determinarlo llamando al método TotalNumberOfProducts() de la clase ProductsBLL. Vamos a crear una propiedad de solo lectura y de nivel de página denominada TotalRowCount que devuelve los resultados del método TotalNumberOfProducts():

Private ReadOnly Property TotalRowCount() As Integer
    Get
        'Return the value from the TotalNumberOfProducts() method
        Dim productsAPI As New ProductsBLL()
        Return productsAPI.TotalNumberOfProducts()
    End Get
End Property

Con esta propiedad, ahora podemos 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 podemos escribir los controladores de eventos Click de los cuatro botones de la interfaz de paginación:

Protected Sub FirstPage_Click(sender As Object, e As EventArgs) _
    Handles FirstPage.Click
    'Return to StartRowIndex of 0 and rebind data
    StartRowIndex = 0
    Products.DataBind()
End Sub
Protected Sub PrevPage_Click(sender As Object, e As EventArgs) _
    Handles PrevPage.Click
    'Subtract MaximumRows from StartRowIndex and rebind data
    StartRowIndex -= MaximumRows
    Products.DataBind()
End Sub
Protected Sub NextPage_Click(sender As Object, e As EventArgs) _
    Handles NextPage.Click
    'Add MaximumRows to StartRowIndex and rebind data
    StartRowIndex += MaximumRows
    Products.DataBind()
End Sub
Protected Sub LastPage_Click(sender As Object, e As EventArgs) _
    Handles LastPage.Click
    'Set StartRowIndex = to last page's starting row index and rebind data
    StartRowIndex = ((TotalRowCount - 1) \ MaximumRows) * MaximumRows
    Products.DataBind()
End Sub

Por último, debemos 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 del ObjectDataSource:

' Disable the paging interface buttons, if needed
FirstPage.Enabled = StartRowIndex <> 0
PrevPage.Enabled = StartRowIndex <> 0
Dim LastPageStartRowIndex As Integer = _
    ((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: Incluir compatibilidad de ordenación con el control Repeater paginado personalizado

Ahora que hemos implementado la paginación personalizada, estamos listos para 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, es necesario realizar los pasos siguientes:

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

Comience actualizando la propiedad SelectMethod del 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, necesitamos 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 Property SortExpression() As String
    Get
        Dim o As Object = ViewState("SortExpression")
        If o Is Nothing Then
            Return "ProductName"
        Else
            Return o.ToString()
        End If
    End Get
    Set(ByVal value As String)
        ViewState("SortExpression") = value
    End Set
End Property

Antes de que ObjectDataSource invoque el método GetProductsPagedAndSorted, necesitamos 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 hicimos en el último ejemplo, vamos a implementarla 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 Sub SortByProductName_Click(sender As Object, e As EventArgs) _
    Handles SortByProductName.Click
    StartRowIndex = 0
    SortExpression = "ProductName"
    Products.DataBind()
End Sub
Protected Sub SortByCategoryName_Click(sender As Object, e As EventArgs) _
    Handles SortByCategoryName.Click
    StartRowIndex = 0
    SortExpression = "CategoryName"
    Products.DataBind()
End Sub
Protected Sub SortBySupplierName_Click(sender As Object, e As EventArgs) _
    Handles SortBySupplierName.Click
    StartRowIndex = 0
    SortExpression = "CompanyName"
    Products.DataBind()
End Sub

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. Sin embargo, en la implementación de paginación personalizada, debemos 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 lo tanto, debemos 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 dicha funcionalidad. Al implementar ordenación, pero no paginación, la expresión de ordenación se puede especificar a través del objeto DataSourceSelectArguments pasado al método Select del ObjectDataSource. Esta propiedad DataSourceSelectArguments del objeto SortExpression se puede asignar en el controlador de eventos Selecting del ObjectDataSource.

Para agregar capacidades 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 empresarial de manera que incluya un método que acepte una expresión de ordenación. A continuación, esta información se puede pasar a través de un parámetro en el elemento SelectParameters del ObjectDataSource.

Este tutorial completa nuestro análisis sobre la paginación y ordenación con los controles DataList y Repeater. En el siguiente —y último— tutorial veremos 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/ASP.NET y fundador de 4GuysFromRolla.com, lleva trabajando 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 a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Muchos revisores han evaluado esta serie de tutoriales. El revisor principal de este tutorial ha sido David Suru. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.