Sunday, November 25, 2012

WebGrid: Stronly Typed with Server Paging

Continuing with the WebGrid, I first made it strongly typed. To achieve that, I created a derived type WebGrid. The source came from the reference at the end of the post, and the code I used is displayed below too. While it does not seem to change much in terms of functionality, the main advantage is that the IntelliSense and compiler checking will work with the grid now.

Next I added server paging. Why wouldn't I use the built-in paging? Well, the database table behind WebGrid may have hundreds of records. I wouldn't want to pass it all to my ViewModel and then to the view just to display 5 or 10 of the records I actually need. It is handy that the WebGrid paging is in the form of http://website/Items/ShowAll?page=3. This way my controller knows which page is to be displayed and can preselect the data just for this page only.

To implement paging, I added the TotalRows to the model - that will specify the total number of records in the database table.

The controller method now looks as follows:

public ActionResult Index(int page=1)
{
 int totalRecords;
 List<YahooData> datas = GetData(out totalRecords, pageSize: 5, pageIndex: page - 1);
 List<YahooSymbol> symbols = db.YahooSymbols.ToList();
 YahooSymbol symbol = symbols.First();
 int id = symbol.YahooSymbolID;
 return View(new YahooViewModel(id, symbol, symbols, datas, totalRecords));
}

public List<YahooData> GetData(out int totalRecords, int pageSize, int pageIndex)
{
 List<YahooData> data = GetData();
 totalRecords = data.Count;
 if(pageSize > 0 && pageIndex >=0)
 {
  data = data.Skip(pageIndex*pageSize).Take(pageSize).ToList();
 }
 return data.ToList();
}

The concept is quite simple - get data from the database table, identify the records that will be displayed on the WebGrid page that is requested, and only pass those records to the view. The WebGrid part of the view now looks as follows

<div id="webgrid" style="float:left; min-width:500px;">
 @{ var grid = new WebGrid<ViewModels.YahooData>(null, rowsPerPage: 5, defaultSort: "YahooSymbolName");
    grid.Bind(Model.Datas, rowCount: Model.TotalRows, autoSortAndPage: false);
    @grid.GetHtml(columns: grid.Columns( 
    grid.Column("DataName", header:"Company", format:@<text>@Html.ActionLink((string)item.DataName, "Details", "Company", new {id=item.SymbolId}, null)</text>),
    grid.Column("DateTime", header:"Time", style:"time", format:@<text>@item.DateTime.ToString("dd/MM/yyyy hh:mm")</text>), 
    grid.Column("LTP"), grid.Column("Volume"), grid.Column("Ask"), grid.Column("Bid"), grid.Column("High"), grid.Column("Low")),
    tableStyle: "webGrid", headerStyle: "header", alternatingRowStyle: "alt");
  }
</div>

My plan from here is to implement AJAX updates to the WebGrid content.

The strongly typed WebGrid samples:

//Strongly Typed WebGrid
public class WebGrid<T> : WebGrid
{
 // Wrapper for System.Web.Helpers.WebGrid that preserves the item type from the data source
 public WebGrid(IEnumerable<T> source = null, IEnumerable<string> columnNames = null, string defaultSort = null, int rowsPerPage = 10, bool canPage = true, bool canSort = true, string ajaxUpdateContainerId = null, string ajaxUpdateCallback = null, string fieldNamePrefix = null, string pageFieldName = null, string selectionFieldName = null, string sortFieldName = null, string sortDirectionFieldName = null)
  : base(source.SafeCast<object>(), columnNames, defaultSort, rowsPerPage, canPage, canSort, ajaxUpdateContainerId, ajaxUpdateCallback, fieldNamePrefix, pageFieldName, selectionFieldName, sortFieldName, sortDirectionFieldName)
 {
 }
 public WebGridColumn Column(string columnName = null, string header = null, Func<T, object> format = null, string style = null, bool canSort = true)
 {
  Func<dynamic, object> wrappedFormat = null;
  if (format != null)
  {
   wrappedFormat = o => format((T)o.Value);
  }
  WebGridColumn column = base.Column(columnName, header, wrappedFormat, style, canSort);
  return column;
 }
 public WebGrid<T> Bind(IEnumerable<T> source, IEnumerable<string> columnNames = null, bool autoSortAndPage = true, int rowCount = -1)
 {
  base.Bind(source.SafeCast<object>(), columnNames, autoSortAndPage, rowCount);
  return this;
 }
}

public static class EnumerableExtensions
{
 public static IEnumerable<TTarget> SafeCast<TTarget>(this IEnumerable source)
 {
  return source == null ? null : source.Cast<TTarget>();
 }
}
//WebGrid extensions
public static class WebGridExtensions
{
 // Light-weight wrapper around the constructor for WebGrid so that we get take advantage of compiler type inference
 public static WebGrid<T> Grid<T>(this HtmlHelper htmlHelper, IEnumerable<T> source, IEnumerable<string> columnNames = null,
   string defaultSort = null, int rowsPerPage = 10, bool canPage = true, bool canSort = true,
   string ajaxUpdateContainerId = null, string ajaxUpdateCallback = null, string fieldNamePrefix = null,
   string pageFieldName = null, string selectionFieldName = null, string sortFieldName = null, string sortDirectionFieldName = null)
 {
  return new WebGrid<T>(source, columnNames, defaultSort, rowsPerPage,
    canPage, canSort, ajaxUpdateContainerId, ajaxUpdateCallback, fieldNamePrefix, 
    pageFieldName, selectionFieldName, sortFieldName, sortDirectionFieldName);
 }

 // Light-weight wrapper around the constructor for WebGrid so that we get take advantage of compiler type inference and to automatically call Bind to disable the automatic paging and sorting (use this for server-side paging)
 public static WebGrid<T> ServerPagedGrid<T>(this HtmlHelper htmlHelper, IEnumerable<T> source, int totalRows, IEnumerable<string> columnNames = null,
   string defaultSort = null, int rowsPerPage = 10, bool canPage = true, bool canSort = true, string ajaxUpdateContainerId = null, 
   string ajaxUpdateCallback = null, string fieldNamePrefix = null,
   string pageFieldName = null, string selectionFieldName = null, string sortFieldName = null, string sortDirectionFieldName = null)
 {
  var webGrid = new WebGrid<T>(null, columnNames, defaultSort, rowsPerPage, canPage,
    canSort, ajaxUpdateContainerId, ajaxUpdateCallback, fieldNamePrefix,
    pageFieldName, selectionFieldName, sortFieldName, sortDirectionFieldName);
  return webGrid.Bind(source, rowCount: totalRows, autoSortAndPage: false); ;
 }
}

References

Get the Most out of WebGrid in ASP.NET MVC
by . Also posted on my website

No comments: