Question

I am using a ListView to display paginated data:

<asp:ListView ID="listOfItems" runat="server" DataSourceID="ItemsDataSource" EnableModelValidation="True" InsertItemPosition="FirstItem" ItemPlaceholderID="ItemRowContainer">
    <LayoutTemplate>
        <div class="tablecontainer">
            <div class="pagination-top">
                <custom:TablePaginationControl ID="TablePaginationControl1" runat="server" ControlID="listOfItems" ShowPageSizeList="true" />
            </div>
            <table class="list-view">
                <tr>
                    <th class="first-column" width="350px">
                        <asp:LinkButton ID="SortByName" runat="server" CommandArgument="Name" CommandName="SortMainList" OnCommand="SortItems" Text="<%$ Resources:Name %>"></asp:LinkButton>
                    </th>
                    ...
                </tr>
                <tbody>
                    <tr runat="server" id="ItemRowContainer" />
                </tbody>
            </table>
        </div>
    </LayoutTemplate>
    ...
</asp:ListView>

The datasource definition:

<asp:ObjectDataSource ID="ItemsDataSource" runat="server" EnablePaging="True" InsertMethod="AddItems" SelectCountMethod="SelectItemsCount" SelectMethod="SelectItems" TypeName="SHLCentral.TheLibrary.Web.View.DocumentManagementControl, ClientPortal.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cd2852a10d692fb9" UpdateMethod="UpdateItems">
    ...
</asp:ObjectDataSource>

The revelant code behind is made of these two methods:

public IEnumerable<ListDocumentsResult> SelectItems(
    int maximumRows,
    int startRowIndex)
{
    var results = Controller.ListDocuments(new ListDocumentsRequest());

    PropertyInfo sortProperty;
    try
    {
        sortProperty = typeof (ListDocumentsResult).GetProperty((string) ViewState["mainListSortColumn"]);
    }
    catch
    {
        sortProperty = null;
    }
    Func<ListDocumentsResult, object> sortFunction = sortProperty == null
                            ? (Func<ListDocumentsResult, object>) (ldr => ldr.LastUpdatedDate)
                            : (ldr => sortProperty.GetValue(ldr, new object[0]));

    return
        (sortProperty == null || !((bool) ViewState["mainListSortAsc"])
             ? results.OrderByDescending(sortFunction)
             : results.OrderBy(sortFunction))
            .Skip(startRowIndex)
            .Take(maximumRows);
}

protected void SortItems(object sender, CommandEventArgs e)
{
    if (e.CommandName == "SortMainList")
    {
        var sortColumn = (string) e.CommandArgument;
        if ((string)ViewState["mainListSortColumn"] == sortColumn)
        {
            ViewState["mainListSortAsc"] = !(bool)ViewState["mainListSortAsc"];
        }
        else
        {
            ViewState["mainListSortAsc"] = true;
            ViewState["mainListSortColumn"] = sortColumn;
        }
        DataBind();
    }
}

So my intention this: when the users clicks on the LinkButton contained in the "Name" column header (I left out all but one column for clarity), the SortItems method is called: it sets the sorted column name and sort order into the ViewState, then reloads the ListView using the DataBind method. In the Select method of the ObjectDataSource, we read this ViewState values and use them to order the data.

Putting breakpoints on all these methods, I can see there is this sequence of calls when I click the LinkButton:

  • OnLoad
  • SortItems
  • SelectItems

The problem I have is that when I get to the SelectItems method, the ViewState is totally empty (it has 0 keys): if I set a breakpoint on the Load method of the page, I see the control containing all this is only loaded once. The DataBind method does not seem to trigger any loading of the control, it seems to be just triggering the SelectItems method of a new instance of the control (meaning that if, instead of using ViewState, I set an instance field in the SortItems method, the field is null when getting in the SelectItems method).

I am sure that the ViewState is active on the page (I can find the ViewState keys on the browser side using a Firefox extension for instance).

There is something not quite clear to me about the life cycle of the page/control. Could someone explain what it is to me?

Was it helpful?

Solution

There exist much, much simpler approach.

First, instead of a custom CommandName, you put a built-in name into the sort link button. The name is Sort.

You have then

   <asp:LinkButton ID="SortByName" runat="server" CommandArgument="Name" CommandName="Sort" />

Then, on your ObjectDataSource you add the SortParameterName to be something like OrderBy:

   <ObjectDataSource .... SortParameterName="OrderBy" />

Then you modify your data provider method to be:

 public IEnumerable<ListDocumentsResult> SelectItems(
   string OrderBy,
   int maximumRows,
   int startRowIndex)

The data source will provide the value automatically based on the command argument (Name) and it will automatically append DESC whenever you click the command button for the second time (it is because the ListView persists the state of sort order in its viewstate automatically, you don't have to reinvent this!)

Then, you don't need this ugly delegates to order by strings for linq. Instead, download the Dynamic Linq library:

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

find the Dynamic.cs file, include it in your project and it will add a bunch of additional linq operators, including the OrderBy which accepts strings and which automatically supports DESC (!).

You then just

 public IEnumerable<ListDocumentsResult> SelectItems(
   string OrderBy,
   int maximumRows,
   int startRowIndex)
 {

     Controller.ListDocuments(new ListDocumentsRequest())
        .OrderBy(OrderBy)
        .Skip(startRowIndex)
        .Take(maximumRows);
 }

This is just as simple!

Be warned though that there's a small bug (or an inconvenience) in the dynamic linq - it throws an exception when the sort order is empty.

Find then this code (line 47 and down)

    public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) {
        if (source == null) throw new ArgumentNullException("source");
        if (ordering == null) throw new ArgumentNullException("ordering");

and change it manually to

    public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) {
        if ( string.IsNullOrEmpty( ordering ) ) return source;
        if (source == null) throw new ArgumentNullException("source");
        if (ordering == null) throw new ArgumentNullException("ordering");

Done.

OTHER TIPS

If the SelectMethod is not static, the ObjectDataSource control will create a new instance of the type specified in TypeName and call the method on that instance.

You either need to add a parameter for the sort expression to your select method and set the SortParameterName property on the ObjectDataSource, or you need to handle the ObjectCreating event and set the ObjectInstance to the existing control instance.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top