The Grid β Your Workhorse
In most agency applications, 80% of your Kendo work is the Grid. Case lists, permit queues, payment histories β it's grids all the way down. Master its anatomy once and every screen after that is configuration, not invention.
Anatomy of a grid definition
A typical Razor definition binds the grid to a view model row type (never an EF entity) and wires an AJAX data source:
@(Html.Kendo().Grid<PermitRowVm>()
.Name("permitsGrid")
.Columns(c => { c.Bound(p => p.Number); c.Bound(p => p.Status); })
.Pageable().Sortable().Filterable()
.DataSource(ds => ds.Ajax().PageSize(25)
.Read(r => r.Action("Permits_Read", "Permits"))))
.Name() matters: it becomes the element id and the key you use from JavaScript
($("#permitsGrid").data("kendoGrid")). Columns support formatting (.Format("{0:d}")), widths,
and client templates for links and badges. Paging, sorting, and filtering are opt-in β turn
them on and the grid renders the UI, but where the work happens is the next decision.
Server vs client operations
With server operations (the default for Ajax() data sources), every page change or filter
triggers a request, and the server returns only one page of data. With
.ServerOperation(false), the first read returns everything and the browser pages and filters
in memory. The rule of thumb: server operations for anything that can grow (thousands of rows β
let SQL Server do the work), client operations only for small, stable lists where instant
filtering feels snappy and one query is cheaper than many.
ToDataSourceResult β the server half
The read action is where Kendo meets Entity Framework. The [DataSourceRequest] binder
deserializes the grid's paging/sorting/filter state, and ToDataSourceResult applies it to
your IQueryable:
public ActionResult Permits_Read([DataSourceRequest] DataSourceRequest request)
{
var rows = _db.Permits.Select(p => new PermitRowVm { Number = p.Number, Status = p.Status.Name });
return Json(rows.ToDataSourceResult(request));
}
Because rows is still an IQueryable, the filter and page are translated into SQL β the
database returns 25 rows, not 250,000. This is the single most important pattern in the whole
library: project to a view model first, keep it IQueryable, then call ToDataSourceResult
last. Call .ToList() too early and you've loaded the entire table into memory to page it.
The result JSON carries Data, Total, and any model-state Errors β the grid consumes that
shape automatically. When a grid "loads forever," check the browser's network tab first: nine
times out of ten the read action threw, returned a login redirect, or serialized a cycle because
someone returned entities instead of a projection.