GridView data sets

If you use GridView or Repeater, one of the common requirements is to allow the users to sort the collection, or to split large tables into smaller pages.

DotVVM offers a class called GridViewDataSet<T> which can help with sorting, paging, and single-row editing. This class contains the following properties:

  • Items is a List<T> of items that are currently visible to the user
  • SortingOptions property contains the metadata of how the data is sorted (column name, and direction)
  • PagingOptions property provides information for the DataPager component, like page size, current page index, or the total number of rows in the table

Use the data set

The data set object can be defined like this:

public class CustomerListViewModel : DotvvmViewModelBase
{
    public GridViewDataSet<CustomerListDto> Customers = new GridViewDataSet<CustomerListDto>()
    {
        PagingOptions = 
        {
            PageSize = 20
        },
        SortingOptions = 
        {
            SortExpression = nameof(CustomerListDto.LastName)
        }
    };
    ...
}

In the page, you can just bind the data set to the control's DataSource property. Also, you can add the DataPager control, and bind it to the same object.

<dot:GridView DataSource="{value: Customers}">
    <Columns>
    ...
    </Columns>
</dot:GridView>

<dot:DataPager DataSet="{value: Customers}" />

Load data from IQueryable

Most developers prefer using ORMs, such as Entity Framework which work with the IQueryable interface.

DotVVM data sets make it very easy to load data from IQueryable providers:

public class CustomerListViewModel : DotvvmViewModelBase
{
    public GridViewDataSet<CustomerListDto> Customers = ...;

    public override PreRender()
    {
        if (Customers.IsRefreshRequired) 
        {
            IQueryable<CustomerListDto> queryable = _customerService.GetCustomerList();
            Customers.LoadFromQueryable(queryable);
        }
        base.PreRender();
    }
}

Notice that the GetCustomerList method doesn't get any information about the column we use for sorting, or how many records we want. The LoadFromQueryable method will append OrderBy, Skip and Take calls on the IQueryable before it calls ToList, so only the rows that are actually needed will be fetched from the database.

Please note that some IQueryable providers cannot perform paging when the sort expression is not set. For example, if you try to use PagingOptions without SortingOptions with Entity Framework or Entity Framework Core, you'll get System.NotSupportedException: The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'. In such case, you need to set the SortingOptions.SortExpression property to a name of the property that shall be used for sorting.

Load data manually

If you don't use IQueryable, you can load data on your own by assigning the Items and PagingOptions.TotalItemsCount properties on the data set.

You can read all information about the current page and sort preferences from the PagingOptions and SortingOptions properties.

public class CustomerListViewModel : DotvvmViewModelBase
{
    public GridViewDataSet<CustomerListDto> Customers = ...;

    public override PreRender()
    {
        if (Customers.IsRefreshRequired) 
        {
            int totalRowCount;
            Customers.Items = _customerService.GetCustomerList(
                pageIndex: Customers.PagingOptions.PageIndex,
                pageSize: Customers.PagingOptions.PageSize,
                sortExpression: Customers.SortingOptions.SortExpression,
                sortDescending: Customers.SortingOptions.SortDescending,
                out totalRowCount
            );
            Customers.PagingOptions.TotalItemsCount = totalRowCount;
            Customers.IsRefreshRequired = false;
        }
        base.PreRender();
    }
}

Refresh the data set

The data sets are commonly loaded in the PreRender viewmodel lifecycle method, because it happens after methods invoked from commands are executed.

In many cases, the data in the data set stay unchanged, so it doesn't need to be loaded on every postback. That's why we check the IsRefreshRequired property - it is be true on the first request, but once the data is loaded, it is be reset to false (LoadFromQueryable resets this property automatically).

If the user changes the sort order in the GridView by clicking on a column header, or selects a different page in the DataPager, the IsRefreshRequired will be set to true. You can also call RequestRefresh method on the data set - it is quite useful if you want to create e. g. a refresh button.

<div class="filters">
    <dot:TextBox Text="{value: SearchText}" placeholder="Search customers" />
    <dot:Button Text="Search" Click="{value: Customers.RequestRefresh()}" />
</div>

See also