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:

image

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: 

image

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:

image

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.

image

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:

image

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

26 Comments

  • Are this cool new features leading to MVVM?

  • Very cool...love these new features !!!

  • Should categoryId.HasValue be category.HasValue?

  • makes webforms much more interesting :)

  • I'm not at all ashamed to say: worst. feature. ever.

    Vision: public IQueryable GetProducts([Control] int? category)

    Reality: public IQueryable GetProducts([Control] int? categoryDropDown, [Control] string subCategoryDropDown, [QueryString] Guid? productId, [Control] string txtSearch, [QueryString] string rememberMeField, [Control] string FirstName, [Control] string LastName, [QueryString] string orderDate )

  • Scott, as your series progresses I'm hoping to see more notes about the positioning of these vNext improvements related to prior mechanisms. For example, over the years we've been frustrated with limitations in ObjectDataSource in terms of sorting, filtering, and paging. You're showing us what's coming "some time" in the future, and it looks good but we need to continue current development using existing tools until vNext is in production and stable. I'll be eager to jump on the new model binding for web forms to replace the ODS and related components and techniques. But I will need more insight from you and others to understand where this will address the old woes, and where we will still need to fall back on the old paradigms.

    I'd also be interested to know if sorting, filtering, and paging can be pushed up to the client in vNext. So for example, I'd like to eliminate a server postback when the user is just filtering or sorting a single page of data that's already on the client - and I don't want to shift to Silverlight for this.

    Sincere thanks for this and your other efforts.

  • Glad to see the empowered life to Web Forms when MVC trying to over take complete era.

  • In the last code example I assume the line:
    if (categoryId.HasValue)
    Should in fact be:
    if (category.HasValue)

  • I like this value provider binding, but I wish the mapping was set in metadata above the function and not in the signature. I foresee some really long and nasty looking method signatures that will be pushing off the side of my 24" screen.

    Also, why not allow the developer the ability to enable this for all parameters using some type of global setting?

    Example:

    [Value Provider(Control)]
    public IQueryable GetProducts(int? category, int? pageSize, string sortColumn)

    In general, it seems like we are getting really close to having built-in 2 way data binding (Object -> Form -> Object), which is something I look forward too...

  • How would the model binding system figure which control would govern the list filtering? In the above example, there are only two controls, the drop down governing the list. If there are two drop downs both binding to Category table and the intention is to make the list update only for one of the drop downs, is there any other attribute which comes to the rescue?

  • Excellent Post..
    I want to know the word "category" used in following function as parameter.
    public IQueryable GetProducts([Control] int? category)
    IS it ID of DropDownList.

  • I am sorry but this signature looks very ambiguous.
    public IQueryable GetProducts([Control] int? category)

    My doubts are

    1. How does the Bind function know which Control to look at?
    2. Don't we need a [Control("category")] to identify this?
    3. Does this mean that Control of type DropDownList accepts only SelectedValue?
    4. How could one use a TextBox and a button to filter the ResultSet (Posting back to the Page making the Text as a QueryString?)

    Please shed some more light on these.

  • Hi,

    nice features :)

    I assume we can use the [control] and [QueryString] attributes only in the code-behind of our page ?
    And is there going to be other attributes such as [Session] or [Cache] or other things ?

    I think you have made a little mistake when inserting the first screenshot for filtering, If I understand well, it should be one screenshot with "Default.aspx?keyword=chef" in the url and not "Default.aspx?q=chef" like in the second one

    Thanks for your post!

  • What's the point of all this new functionality for webforms, shouldn't it just be end of lifed so we can move on from 1999?

  • With due respect to Jamie Zawinsky: whenever .NET programmers face a problem with passing data, they say "I know, I'll use an attribute". Now they have two problems. :-)

    This is OK for quick defaults, but if you're using many such parameters it makes more sense to turn them into fields of a little view model object (especially if they're common between methods). A more convenient way for constructing object data sources (using value providers) would be a nice addition. Of course, then you've practically got the way MVC does it anyway.

    One day Web Forms and MVC will be one framework, mark my words. :-) MVC's routing and controller decoupling are nice, the control approach of Web Forms is nice, no reason we can't combine them.

  • Positive to see you're still adding to web forms. I do agree with AndyT's comment though that in reality you will end up with bloated method signatures like public IQueryable GetProducts([Control] int? categoryDropDown, [Control] string subCategoryDropDown, [QueryString] Guid? productId /* etc */).

    Would it be possible to bind an entire class DefaultModelBinder reflection style? E.g.

    public IQueryable GetProducts(ProductFilters filters) {...}

    public class ProductFilters
    {
    [Control] int? CategoryDropDown {get; set;}
    [Control] string SubCategoryDropDown {get; set;}
    [QueryString("p")] Guid? ProductId {get; set; }
    ...etc
    }

  • @AndyT

    To avoid super long method signatures I think you'll be able to create a Model and use a Complex Typed parameter:

    public class ProductFilterModel
    {
    [QueryString]
    int? category {get;set}
    [QueryString]
    int? pageSize {get;set}
    [Control]
    string sortColumn {get;set}
    }

    @Zachary Hunter: I'm pretty confident it will also work that way.

  • Two questions:

    1. How will the value providers react to invalid values?
    Eg: GetProducts([QueryString] int? category)
    -> products.aspx?category=foobar
    <- category = null?
    <- unhandled exception?
    <- something else?

    2. Will we be able to use these new technologies (model binding, strong binding, etc.) with our own controls, or will they be locked away and only available to the built-in controls?

  • Hi scott, rahul here the grid filtering which you have achieved here by making use of iquerable and there by passing the required string as a query string, you can however achieve the same more efficiently by making use of jquery.Here in you can have one input box which will act as passing the value grid and grid will re-adjust itself based on the query.what do u say about that?

  • I'm wondering what the recommended pattern is for disposing of the Northwind object. I'm assuming Northwind is a DbContext which is an IDisposable. In your previous example, you were storing it as a local variable. Now, you are storing it as an instance variable. I don't think the IQueryable part would work if you used a using statement and a local variable. It seems like what you are demonstrating, goes against recommended programming practices. If you are using Code Analysis. I'm pretty sure you'll receiving warning about not handling IDisposables properly.

  • Excellent feature.

  • Expect the next post.

  • Very nice.

    Developers these days have it good!

  • Is this the direction by Microsoft for the next version of development of Web Forms...uphill?

  • The 90 second video really helped thanks for including that

  • This is probably too late.
    But I wish ASP.NET should be overhauled and evolved into something that really deserve a word vNext.
    There should be no more WebForm, MVC or Silverlight for the web solution.
    The new ASP.NET should not rely on postback (WebForm's weak) nor restrict to request/response model (MVC's weak) nor neeed a plugin (Silverlight's weak).
    I wish the new ASP.NET should able to use XAML or HTML5 for UI markup/binding and make event-driven coding style possible by transparently utilize AJAX service call as modern web application lean toward.
    Combine all strengths of WebForm, MVC, and Silverlight into one. No Compromise!! remember?
    This model would also sit along side-by-side with Metro.
    There were .NET and ASP.NET platform.
    Now there will be Metro and (you name it) for the web platform.

Comments have been disabled for this content.