Web Forms Model Binding Part 1: Selecting Data (ASP.NET vNext Series)
This is the third in a series of blog posts I'm doing on ASP.NET vNext.
The next releases of .NET and Visual Studio include a ton of great new features and capabilities. With ASP.NET vNext you'll see a bunch of really exciting improvements with both Web Forms and MVC - as well as in the core ASP.NET base foundation that both are built upon.
Today's post is the first of three posts I'll do over the next week that talk about the new Model Binding support coming to Web Forms. Model Binding is an extension of the existing data-binding system in ASP.NET Web Forms, and provides a code-focused data-access paradigm. It takes advantage of a bunch of model binding concepts we first introduced with ASP.NET MVC - and integrates them nicely with the Web Forms server control model.
Some Background on Data-binding today
While Web Forms includes a number of datasource controls (e.g. SqlDataSource, EntityDataSource, LinqDataSource) that allow you to declaratively connect server-side controls directly to datasources, many developers prefer to retain full control over their data-access logic - and write this logic using code.
In previous versions of Web Forms, this could be achieved by setting a control's DataSource property directly, and then calling its DataBind() method from the page's code-behind. While this works for many scenarios, it doesn't work well with richer data controls (like a GridView) that support automatic operations like sorting, paging and editing.
Another option available today is to use the ObjectDataSource control. This control allows for a cleaner separation of UI code from the data-access layer, and does allow data-controls to provide automatic functionality such as paging and sorting. However, while it works well for selecting data, it is still cumbersome when performing 2-way data-binding, only supports simple properties (with no "deep" binding of complex types) and often requires developers to write a lot of complex code to handle many scenarios (including common ones like validation errors).
Introducing Model Binding
ASP.NET vNext includes new support for "Model Binding" within Web Forms.
Model Binding aims to simplify working with code-focused data-access logic while still retaining the benefits of a rich, 2-way data-binding framework. It incorporates the model binding pattern we first introduced with ASP.NET MVC, while also integrating really nicely with the Web Forms server control model. It makes it easy to perform common CRUD style scenarios with Web Forms - and enables you to do so using any data access technology (EF, Linq to SQL, NHibernate, DataSets, raw ADO.NET, etc).
I'll be doing several posts this week that walkthrough how to take advantage of the new model binding capabilities. In today's post I'll demonstrate how to use Model Binding to retrieve data - and enable sorting and paging over it within a GridView control.
Retrieving Data using the SelectMethod
Model binding is a code-focused approach to data-binding. It allows you to write CRUD helper methods within the code-behind file of your page, and then easily wire them up to any server-controls within the page. The server-controls will then take care of calling the methods at the appropriate time in the page-lifecycle and data-bind the data.
To see a simple example of this in action, let's use an <asp:gridview> control. The Gridview below has 4 columns - 3 of them standard BoundFields, and the 4th a TemplateField. Note how we have set the ModelType property on the GridView to be a Category object - this enables strongly-typed data-binding within the templatefield (e.g. Item.Products.Count instead of using the Eval() method):
<asp:GridView ID="categoriesGrid" runat="server" ModelType="WebApplication1.Model.Category"
SelectMethod="GetCategories" AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="CategoryID" HeaderText="ID" />
<asp:BoundField DataField="CategoryName" HeaderText="Name" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="# of Products">
<ItemTemplate><%# Item.Products.Count %></ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
We've configured the GridView to retrieve its data using Model Binding by setting the GridView's SelectMethod property to point to the GetCategories() method within the page's code-behind file. This GetCategories() method looks like the following:
public IQueryable<Category> GetCategories() {
var northwind = new Northwind();
return northwind.Categories.Include(c => c.Products);
}
Above I'm using EF Code First to execute a LINQ query that returns a list of categories from the Northwind sample database. Note that we do not have to perform the database query within the code-behind - I could have alternatively performed this within a repository or data-access layer and just used the GetCategories() helper method to connect the control to it.
When we run the page, the GridView will call the above method to automatically retrieve the data and render it out to the page like so:
Avoiding N+1 Selects
One thing you might have noticed with the code above is that we are using the .Include(c=>c.Products) helper extension on our LINQ query. This tells EF to modify the query so that in addition to retrieving the Category information, it also includes the related Products (avoiding having to make a separate call to the database to retrieve this information for each row returned).
Sorting and paging support
We could have returned the categories from our GetCategories() method using an IEnumerable<Category> - or a type that implements that interface like List<Category>. Instead, though, we returned the categories using an IQueryable<Category> interface:
public IQueryable<Category> GetCategories() {
var northwind = new Northwind();
return northwind.Categories.Include(c => c.Products);
}
The benefit of returning IQueryable<T> is that it enables deferred execution on the query, and allows a data-bound control to further modify the query before executing it. This is particularly useful with controls that support paging and sorting. These controls can automatically add the appropriate sort and page operators onto an IQueryable<T> query before executing it. This has the benefit of making sorting and paging really easy to implement in your code - as well as making sure that the sort and page operations are done in the database and are super efficient.
To enable sorting and paging with our GridView, we will modify it to have the AllowSorting and AllowPaging properties set to true, and specify a default PageSize of 5. We will also specify an appropriate SortExpression on two of our columns:
<asp:GridView ID="categoriesGrid" runat="server" AutoGenerateColumns="false"
AllowSorting="true" AllowPaging="true" PageSize="5"
ModelType="WebApplication1.Model.Category"
SelectMethod="GetCategories">
<Columns>
<asp:BoundField DataField="CategoryID" HeaderText="ID" SortExpression="CategoryID" />
<asp:BoundField DataField="CategoryName" HeaderText="Name" SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="# of Products">
<ItemTemplate><%# Item.Products.Count %></ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And now when we run the page, we can page and sort over our data:
Only the categories visible in the current sort page will be retrieved from the database - since EF will optimize the query to perform the sort and page operation as part of the database query, and not do the sort/page in the middle-tier. This makes sorting/paging efficient even over large amounts of data.
Quick Video of Modeling Binding and SelectMethods
Damian Edwards has a great 90 second video that shows off using model binding to implement a GridView scenario that enables sorting and paging. You can watch the 90 second video here.
Summary
The new Model Binding support in ASP.NET vNext is a nice evolution of the existing Web Forms data-binding system. It borrows concepts and features from the Model Binding system in ASP.NET MVC (you'll see this more in later posts), and makes working with code-focused data-access paradigms simpler and more flexible.
In futures posts in this series I'll extend on Model Binding and look at how we can easily integrate filtering scenarios into our data selection scenarios, as well as how to handle editing scenarios (including ones using validation).
Hope this helps,
Scott
P.S. In addition to blogging, I use Twitter to-do quick posts and share links. My Twitter handle is: @scottgu