Web Forms Model Binding Part 3: Updating and Validation (ASP.NET 4.5 Series)

This is the fifth in a series of blog posts I'm doing on ASP.NET 4.5.

The next releases of .NET and Visual Studio include a ton of great new features and capabilities.  With ASP.NET 4.5 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 third of three posts in the series 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.

If you haven't already, read the first two parts in this series on Model Binding, which covers the basics of selecting data via the new SelectMethod property on data-controls like the GridView, as well as filtering that data based on input from the user.

In today's post we'll look at how Model Binding improves the data-binding experience when updating data.

Getting Started

Let's start with the GridView sample from the previous post, configured to use Model Binding to show some data from the Northwind Products table. The GridView calls the configured GetProducts method, which in turn is using Entity Framework Code First to simply return the Products property on my Northwind data context instance.

It's important that the DataKeyNames property is set to the primary key (or keys) of the model type, so that the GridView can round-trip this value with the page between the browser and server.

<asp:GridView ID="productsGrid" runat="server" DataKeyNames="ProductID"

    ModelType="ModelBindingPart3.Product"

    AllowPaging="true" AllowSorting="true" AutoGenerateColumns="false"

    SelectMethod="GetProducts">

    <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 _Default : Page

{

    private 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 our GetProducts() method is returning an IQueryable<Product>, users can easily page and sort through the data within our GridView.  Only the 10 rows that are visible on any given page are returned from the database.

Enabling Editing Support

We can enable the GridView's inline row editing support by setting the AutoGenerateEditButton attribute to "true". The GridView will now render an Edit link for each row, which the user can then click to flip the row into edit mode.

Next, we need to configure the GridView to call our update method (which we'll add to our code-behind in just a bit). We can do this by setting the UpdateMethod attribute to the name of our method, in this case "UpdateProduct". Our GridView mark-up now looks like this:

<asp:GridView ID="productsGrid" runat="server" DataKeyNames="ProductID"

    ModelType="ModelBindingPart3.Product"

    AllowPaging="true" AllowSorting="true"

    AutoGenerateColumns="false" AutoGenerateEditButton="true"

    SelectMethod="GetProducts" UpdateMethod="UpdateProduct">

    <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>

When we run the page now, we can click one of the ‘Edit' links next to a record to put it into edit mode:

image

We now need to implement the UpdateProduct() method in our page code-behind.  There are a couple of approaches we can take when doing this.

Approach 1

Our first approach will cause the Web Forms model binding system to pass a Product instance to our update method, which we can then use to attach to our EF code first context and apply its values to the database:

public void UpdateProduct(Product product)

{

    // 'product' was implicitly model bound using data-control values

    _db.Entry(product).State = System.Data.EntityState.Modified;

    _db.SaveChanges();

}

Our method above takes a single parameter of the type the GridView is bound to - which is the ‘Product' type. When the UpdateProduct method is invoked by the Model Binding system, it will implicitly create an instance of Product and attempt to bind the values from the data-control (our GridView) onto its members.   Our UpdateProduct method then tells EF Code First that the state of the product object was modified - which will cause it to mark that object as changed, before finally saving the changes.

Approach 2

While this approach works for simple cases, often your data-control won't be able to provide values for every member on your data object, because they weren't rendered to the page or they represent relationships to other objects. In those cases, it's a better approach to first load the object to be updated from the database (using its primary key), and then explicitly instruct the Model Binding system to bind the data-control values onto the members it can.  Let's change our update method to use this approach:

public void UpdateProduct(int productId)

{

    var product = _db.Products.Find(productId);

   

    // Explicitly model bind data-control values onto 'product'

    TryUpdateModel(product);

    _db.SaveChanges();

}

This time, the Web Forms Model Binding system will populate the productId parameter from the data-controls DataKeys collection (it does this implicitly; no value provider attributes are required). We then retrieve the product from the database, and call TryUpdateModel to model bind the values from the data-control onto the object. Once that's done, we save the changes to the database. 

Model Validation

Of course, most data models include some type of validation rules. To facilitate this, the Model Binding system in Web Forms supports model validation using the same validation attributes from the System.ComponentModel.DataAnnotations namespace that ASP.NET Dynamic Data, MVC, Entity Framework and Silverlight RIA Services does. You can decorate properties on your model classes with these attributes to provide further information about the "shape" of your model, including details about which properties require a value and the range of valid values.

Let's update our Product class with validation information matching the schema in the database:

image

We can do this by adding the following attributes to our Product class:

public class Product

{

    public int ProductID { get; set; }

    [Required, StringLength(40)]

    public string ProductName { get; set; }

    [StringLength(20)]

    public string QuantityPerUnit { get; set; }

    [DataType(DataType.Currency)]

    public decimal? UnitPrice { get; set; }

    public short? UnitsInStock { get; set; }       

    public short? UnitsOnOrder { get; set; }

    public short? ReorderLevel { get; set; }       

    public bool Discontinued { get; set; }

    public int? CategoryID { get; set; }

    public virtual Category Category { get; set; }

}

Now, when values are model bound onto this type, the Web Forms Model Binding system will track whether any of these validation rules are violated in the page's ModelState property. We can then explicitly inspect the property to ensure the model state is valid before saving changes:

public void UpdateProduct(int productId)

{

    var product = _db.Products.Find(productId);

           

    TryUpdateModel(product);

           

    // Check whether there were any validation errors

    if (ModelState.IsValid)

    {

        _db.SaveChanges();

    }           

}

To have these model state errors shown on the page when they are invalid, we can add an <asp:ValidationSummary> control, with its ShowModelStateErrors property set to true:

<asp:ValidationSummary runat="server" ShowModelStateErrors="true" />

Now, if we try to update a record with an invalid value, the GridView will stay in edit mode and the model state error message will be displayed by the ValidationSummary:

image

You can also add custom or ad-hoc error messages to the page's model state, to represent other error conditions not covered by the validation attributes.  Just like with ASP.NET MVC Controllers, the Web Forms Page base class now has a "ModelState" property that you can use to populate custom error messages that validation controls within the page can access and use to display any error message you want.

Quick Video of Modeling Binding and Filtering

Damian Edwards has a great 90 second video that shows off using model binding to implement update scenarios with model binding.  You can watch the 90 second video here.

Summary

The Model Binding system in ASP.NET Web Forms 4.5 makes it easy to work with data and user input using a code-focused data-access paradigm. It borrows some of the model binding concepts we first introduced with ASP.NET MVC, and supports a consistent way to use DataAnnotation attributes across both MVC and WebForms to apply validation behavior to model objects.  All the techniques shown here can be equally applied to the other data-controls in ASP.NET Web Forms including the FormView, DetailsView and ListView.

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

28 Comments

  • A couple of "typos":
    - by setting the AutoGenerateSelectButton attribute to “true”. <-- should be AutoGenerateEditButton?
    - same validation attributes from the System.ComponenetModel.DataAnnotations <- should be System.ComponentModel.DataAnnotations

  • @Greg W,

    >>>>> A couple of "typos":

    Good catches! I just updated to fix them.

    Thanks,

    Scott

  • Is there a rough time frame for release (3m, 6m, 1y)? Thanks.

  • I Really expected to implement this in my applications ... nicely ;)

    I hope a VNext Version, comming soon!

  • Whether web form perform any checks to ensure the id parameter value is correct? I mean it's easy for someone to send different id value in which the user maybe has no permission to edit.

  • How about nested objects/properties? Is that supported?

  • Thos blogs must have been interesting!

  • FYI your last post in my Google reader was part1. Part 2 & 3 don't show. I can't see a problem with your feed though so maybe its google reader that gets it wrong

  • I like the new model binding. One thing I'm wondering is if there is a control similar to , but for individual fields? E.g. say you want the error messages to appear right next to the fields instead of in a summary area.

    Another question is whether storing the ObjectContext/DbContext as an instance variable in the Page is a good idea since it's an IDisposable.

  • I finally bought a clean box for my beta fix this release has got the parts to be a Hugh step forward for webforms with the new model binding it will be a snap to switch between Weforms and MVC and keep our hands in both and that helps us to be more employable in the long run

    I noticed moving to Azure is going to be made simpler hurray!! but I find the issue with moving to Azure is the piercing you need to be a rocket scientist to figure it out it would be nice to see it more straight forward

    Great new stuff good work Thanks for the opportunities

  • Will we be able to use these features in our own controls?

    I keep asking, but nobody seems to have an answer! :(

    These new features look great, but if they're limited to MS-shipped framework controls, they won't be much use.

  • @pbz,

    >>>>>> Is there a rough time frame for release (3m, 6m, 1y)? Thanks.

    We haven't announced a release date yet - but it will be further out than 3 months.

    Hope this helps,

    Scott

  • @imran,

    >>>>>> Whether web form perform any checks to ensure the id parameter value is correct? I mean it's easy for someone to send different id value in which the user maybe has no permission to edit.

    By default, values are only populated from the data control that is calling the model method. In the case of data key values, they’re stored in viewstate which is encrypted such that Web Forms can detect if it has been modified. The user cannot simply pass a different ID value through the querystring or form POST and have it be automatically bound to the parameter in the update method. The developer must explicitly mark the parameter with a value provider attribute if they want to allow the value to come from anywhere other than the data control.

    Hope this helps,

    Scott

  • @Mikael,

    >>>>>> Off-topic: For some reason Google Reader doesn't seem to find your new posts. It still shows the post from 6.9.2011 (Web Forms Model Binding Part 1) as the latest and even manually updating the feed doesn't do any good. My subscription points to weblogs.asp.net/.../rss.aspx which seems to be correct. Bizarre.

    Sorry about that - we are working with google to understand why my new posts aren't showing up!

    Hopefully they will soon,

    Scott

  • @jemiller,

    >>>>>> I like the new model binding. One thing I'm wondering is if there is a control similar to , but for individual fields? E.g. say you want the error messages to appear right next to the fields instead of in a summary area. Another question is whether storing the ObjectContext/DbContext as an instance variable in the Page is a good idea since it's an IDisposable.

    Yes, we have a new control “ModelErrorMessage” which lets you show the error message for a given model state key wherever you place the control. It also optionally lets you associate it with an input control and automatically set the focus to that control if there is a model state error present, like a validator. Note, this control is not in the VS11 Developer Preview release.

    Hope this helps,

    Scott

  • @rickj1,

    >>>>> I noticed moving to Azure is going to be made simpler hurray!! but I find the issue with moving to Azure is the piercing you need to be a rocket scientist to figure it out it would be nice to see it more straight forward

    Stay tuned - you'll see some nice improvements coming to Azure soon :)

    Thanks,

    Scott

  • @RichardD,

    >>>>>> Will we be able to use these features in our own controls? I keep asking, but nobody seems to have an answer! :( These new features look great, but if they're limited to MS-shipped framework controls, they won't be much use.

    Absolutely. These new features are baked into the base DataBoundControl and CompositeDataBoundControl classes, so that any control deriving from them inherits model binding support. You can even supplement your own custom ModelDataSource implementation if you want to customize the model method invocation logic, e.g. to auto-trigger extra logic via custom model method attributes. You can also use the model binding system outside of data controls by using the UpdateModel/TryUpdateModel and ModelState members on the Page class directly. Note, these last two features are not in the VS11 Developer Preview release.

    Hope this helps,

    Scott

  • Damian Edwards mentioned at TechEd 2011 that the Membership API would be revamped/upgraded in ASP.Net 4.5 (since it's been stale for some time). Is this still planned?

  • Its really good to see all these much awaited improvements. But can we use all these features in VS 2010 or we will have to wait for VS 2012?

  • Scott, in my experience, 99.9% of the gridview data binding is always more than one table. The displaying is involved columns more than one table as well. I understand this article is just for demo purpose. But if you can always demo involving more than one table joining would be very helpful. thanks!

  • Good feature, its very interesting

    http://www.dotnettechy.com/

  • Sorry for the second message: why do you decided to add new methods to update models, select data and so on, if the grid worked with ObjectDataSource in the same way. The only thing that it could not do is a proper data-binding with IQueryable objects?
    For example, if my selection method of the ODS returns IQueryable collection and the grid can use it as you described in previous posts, it will be enough to edit/sort/filter records and to not extend grid's API.

    Thanks again,
    Vest

  • Bharat Sanchar Nigam Limited has decided to give some relief to its Broadband customers from the rising inflation.
    ===============================
    sanjeppu

    http://www.seoagency.org.uk/clients.html

  • Is is very nice & cool

  • Hi, I may be behind the times, or I may have just missed something, but...

    How does the system know about the relationship between Categories and Products?

    Is there a standard naming convention for the reference Id in the Product table (like CategoryID, in this case) ? Or does it pick up on a database foreign key or something?

    Thanks!

  • Scott, while these features are nice, I really am baffled of why the ASP.NET Team did not consider building a binder similar to the MVC binder that just binds objects to form vars/json etc. This is a basic and fairly generic feature that would work just as well in WebForms as it would in MVC and would actually go a long way towards making Web Forms better at separating concerns. While I've built tools to do this and use them extensively I still think this should be built into the platform.

    FWIW, the MVC model binder feature could easily be a general ASP.NET core feature that works for any Web application that uses FormVars (or JSON or whatever else the binder supports).

  • These new features are baked into the base DataBoundControl and CompositeDataBoundControl classes, so that any control deriving from them inherits model binding support. You can even supplement your own custom ModelDataSource implementation if you want to customize the model method invocation logic, e.g. to auto-trigger extra logic via custom model method attributes.

  • hey scottgu nice blog to learn new implementation on VS SERIES...
    KEEP GOING ...
    THANKS alot to keep us Up to Date.

Comments have been disabled for this content.