Web Forms Model Binding Part 2: Filtering Data (ASP.NET vNext Series)
This is the fourth 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 nice 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 second of three posts 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.
In my first Model Binding post I discussed the basics of selecting data. In today's post, we're going to look at how we can filter selected data based on user input, while maintaining a code focused approach to data-access. I'll show how to do this filtering using both querystring input, as well as from a value in a dropdownlist server control.
Getting Started
We'll start by adding a <asp:GridView> control to a page and configure it to use Model Binding to show some data from the Northwind Products table. Our GridView below has a SelectMethod property set to GetProducts() - which will cause it to call the GetProducts() method within our code-behind file, which in turn is using Entity Framework Code First to simply return Products to bind.
<asp:GridView ID="productsGrid" SelectMethod="GetProducts" DataKeyNames="ProductID"
AllowPaging="true" AllowSorting="true" AutoGenerateColumns="false" runat="server">
<Columns>
<asp:BoundField DataField="ProductID" HeaderText="ID" />
<asp:BoundField DataField="ProductName" HeaderText="Name" SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="Unit Price" SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock" HeaderText="# in Stock" SortExpression="UnitsInStock" />
</Columns>
</asp:GridView>
Below is what our code-behind file (which contains the GetProducts() method) looks like:
public partial class Products : Page
{
Northwind db = new Northwind();
public IQueryable<Product> GetProducts()
{
return db.Products;
}
}
Running this page yields the expected result of a table containing the product data:
Because we are returning an IQueryable<Product> from our GetProducts() method, Sorting and Paging is automatically enabled (and done in the database so it is efficient and only the 10 results we need are ever returned to the middle-tier).
Filtering using a Querystring Parameter
Let's now add some basic filtering support to our application. Specifically, let's enable users to optionally filter the product results depending on whether a product name includes a keyword. We'll specify the keyword using a querystring parameter. For example, below we've indicated we want to only show the products that have the word "chef" in their name:
If no keyword is specified then we'll want to show all of the products like before.
Updating our GetProducts Method
To enable filtering, we'll update our GetProducts() method to take a keyword parameter like below:
public IQueryable<Product> GetProducts([QueryString]string keyword)
{
IQueryable<Product> query = db.Products;
if (!String.IsNullOrWhiteSpace(keyword))
{
query = query.Where(p => p.ProductName.Contains(keyword));
}
return query;
}
If the keyword is empty or null, then the method returns all of the products in the database. If a keyword is specified then we further filter the query results to only include those product's whose names contain the keyword.
Understanding Value Providers
One of the things you might have noticed with the code above is the [QueryString] attribute that we've applied to the keyword method argument. This instructs the Model Binding system to attempt to "bind" a value from the query string to this parameter at runtime, including doing any type conversion required.
These sources of user values are called "Value Providers", and the parameter attributes used to instruct the Model Binding system which Value Provider to use are called "Value Provider Attributes". The next version of Web Forms will ship with value providers and corresponding attributes for all the common sources of user input in a Web Forms application, e.g. query string, cookies, form values, controls, viewstate, session and profile. You can also write your own custom value providers. The value providers you write can be authored to work in both Web Forms and MVC.
By default, the parameter name will be used as the key in the value provider collection, so in this case, a value will be looked for in the query string with the key "keyword", e.g. ~/Products.aspx?keyword=chef. This can be easily overridden by passing the desired key in as an argument to the parameter attribute. In our example, we might like the user to be able to use the common "q" query string key for specifying the keyword:
public IQueryable<Product> GetProducts([QueryString("q")]string keyword)
And now we can filter the results shown by passing a keyword in via the query string:
If you think about how you would do this using code today, it would involve quite a few lines, e.g. to read the value, check if it's not null, attempt to convert it to the desired type, check if the conversion was successful, then finally use the value in your query. Integrating paging and sorting support with this (let alone editing support) would make it even more complex. The Model Binding system is intended to reduce the need to write this type of code, and when you do have to, be able to reuse it cleanly throughout your application.
Filtering Using Controls
Let's now change our sample so that the user can choose a category to filter the products by from a drop-down list.
To do this, we'll add a drop-down list to our markup and configure it to get its data from a GetCategories() method within our code-behind file via its SelectMethod property:
<asp:DropDownList ID="category" SelectMethod="GetCategories"
AppendDataBoundItems="true" AutoPostBack="true"
DataTextField="CategoryName" DataValueField="CategoryID"
runat="server">
<asp:ListItem Value="" Text="(All)" />
</asp:DropDownList>
<asp:GridView ID="productsGrid" SelectMethod="GetProducts" DataKeyNames="ProductID"
AllowPaging="true" AllowSorting="true" AutoGenerateColumns="false" runat="server">
<Columns>
<asp:BoundField DataField="ProductID" HeaderText="ID" />
<asp:BoundField DataField="ProductName" HeaderText="Name" SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="Unit Price" SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock" HeaderText="# in Stock" SortExpression="UnitsInStock" />
</Columns>
</asp:GridView>
We'll then update our code-behind file to include a new GetCategories() method (to populate the DropDownList control), and an updated GetProducts() method that filters based on the category selected within the DropDownList:
public partial class Products : Page
{
Northwind db = new Northwind();
//
// Select method for DropDownList
public IEnumerable<Category> GetCategories()
{
return db.Categories.ToList();
}
//
// Select method for GridView
public IQueryable<Product> GetProducts([Control] int? category)
{
IQueryable<Product> query = db.Products;
if (categoryId.HasValue)
{
query = query.Where(p => p.CategoryID == category);
}
return query;
}
}
Notice above how we've changed the GetProducts() method to take a nullable category as a parameter. I'm then using the [Control] attribute on the category paraemter to bind it to the value of the "category" DropDownList control. Within the GetProducts() method I'm checking to see if the default (All) value is selected - and only filtering to a specific category if a non-All item has been picked from the DropDownList.
The Model Binding system will track the values of parameters to select methods to detect if they've changed across post-backs, and if so, will automatically cause the associated data-control to automatically re-bind itself. This makes it easier to build pages that bind only when necessary, without having to write a lot of code or be intimately aware of the web forms page lifecycle.
And now when we run our page again, we can select either the default (All) option in the DropDownList to show all products, or instead filter the products by a selected category:
Sorting and Paging is once again automatically enabled (and done in the database so it is efficient) regardless of whether we are filtered or not.
Quick Video of Modeling Binding and Filtering
Damian Edwards has a great 90 second video that shows off using model binding to implement a GridView scenario with filtering. 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 makes it simple to filter data based on user input using a code-focused data-access paradigm. You can use the Value Provider Attributes to instruct Model Binding where to get the filter values from, and you can even write your own Value Providers for more complex scenarios.
In the next post, we'll look at how we can enable editing scenarios using Model Binding.
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