Model Binding

Imagine you implemented the Edit action for an HTTP POST, and you didn't know about any of the ASP.NET MVC features that can make your life easy. Because you are a professional web developer, you realize the Edit view is going to post form values to the server. If you want to retrieve those values to update an album, you might choose to pull the values directly from the request:

[HttpPost]
public ActionResult Edit()
{
    var album = new Album();
    album.Title = Request.Form["Title"];
    album.Price = Decimal.Parse(Request.Form["Price"]);

    // ... and so on ...
}

As you can imagine, code like this becomes quite tedious. I've only shown the code to set two properties; you have four or five more to go. You have to pull each property value out of the Form collection (which contains all the posted form values, by name), and move those values into Album properties. Any property that is not of type string will also require a type conversion.

Fortunately, the Edit view carefully named each form input to match with an Album property. If you remember the HTML you looked at earlier, the input for the Title value had the name Title, and the input for the Price value had the name Price. You could modify the view to use different names (like Foo and Bar), but doing so would only make the action code more difficult to write. You'd have to remember the value for Title is in an input named “Foo” — how absurd!

If the input names match the property names, why can't you write a generic piece of code that pushes values around based on a naming convention? This is exactly what the model binding feature of ASP.NET MVC provides.

The DefaultModelBinder

Instead of digging form values out of the request, the Edit action simply takes an Album object as a parameter:

        [HttpPost]
        public ActionResult Edit(Album album)
        {
            // ...
        }

When you have an action with a parameter, the MVC runtime uses a model binder to build the parameter. You can have multiple model binders registered in the MVC runtime for different types of models, but the workhorse by default will be the DefaultModelBinder. In the case of an Album object, the default model binder inspects the album and finds all the album properties available for binding. Following the naming convention you examined earlier, the default model binder can automatically convert and move values from the request into an album object (the model binder can also create an instance of the object to populate).

In other words, when the model binder sees an Album has a Title property, it looks for a parameter named “Title” in the request. Notice I said the model binder looks “in the request” and not “in the form collection.” The model binder uses components known as value providers to search for values in different areas of a request. The model binder can look at route data, the query string, the form collection, and you can add custom value providers if you so desire.

Model binding isn't restricted to HTTP POST operations and complex parameters like an Album object. Model binding can also feed primitive parameters into an action, like for the Edit action responding to an HTTP GET request:

public ActionResult Edit(int id)
{
    // ….
}

In this scenario, the model binder uses the name of the parameter (id) to look for values in the request. The routing engine is the component that finds the ID value in the URL /StoreManager/Edit/8, but it is a model binder that converts and moves the value from route data into the id parameter. You could also invoke this action using the URL /StoreManager/Edit?id=8, because the model binder will find the id parameter in the query string collection.

The model binder is a bit like a search and rescue dog. The runtime tells the model binder it wants a value for id, and the binder goes off and looks everywhere to find a parameter with the name id.

A Word on Model Binding Security

Sometimes the aggressive search behavior of the model binder can have unintended consequences. I mentioned how the default model binder looks at the available properties on an Album object and tries to find a matching value for each property by looking around in the request. Occasionally there is a property you don't want (or expect) the model binder to set, and you need to be careful to avoid an “over-posting” attack.

Jon talks in more detail about the over-posting attack in Chapter 7, and also show you several techniques to avoid the problem. For now, keep this threat in mind, and be sure to read Chapter 7 later!

Explicit Model Binding

Model binding implicitly goes to work when you have an action parameter. You can also explicitly invoke model binding using the UpdateModel and TryUpdateModel methods in your controller. UpdateModel will throw an exception if something goes wrong during model binding and the model is invalid. Here is what the Edit action might look like if you used UpdateModel instead of an action parameter:

        [HttpPost]
        public ActionResult Edit()
        {
            var album = new Album();
            try
            {
                UpdateModel(album);
                db.Entry(album).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            catch
            {
                ViewBag.GenreId = new SelectList(db.Genres, "GenreId", 
                                                "Name", album.GenreId);
                ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", 
                                                  "Name", album.ArtistId);
                return View(album);
            }
        }

TryUpdateModel also invokes model binding, but doesn't throw an exception. TryUpdateModel does return a bool—a value of true if model binding succeeded and the model is valid, and a value of false if something went wrong.

 [HttpPost]
public ActionResult Edit()
{
    var album = new Album();
    if (TryUpdateModel(album))
    {
        db.Entry(album).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    else
    {
        ViewBag.GenreId = new SelectList(db.Genres, "GenreId", 
                                         "Name", album.GenreId);
        ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", 
                                         "Name", album.ArtistId);
        return View(album);
    }
}

A byproduct of model binding is model state. For every value the model binder moves into a model, it records an entry in model state. You can check model state anytime after model binding occurs to see if model binding succeeded:

[HttpPost]
public ActionResult Edit()
{
    var album = new Album();
    TryUpdateModel(album);
    if (ModelState.IsValid)
    {
        db.Entry(album).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    else
    {
        ViewBag.GenreId = new SelectList(db.Genres, "GenreId", 
                                         "Name", album.GenreId);
        ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", 
                                         "Name", album.ArtistId);
        return View(album);
    }
}

If any errors occurred during model binding, the model state will contain the names of the properties that caused failures, the attempted values, and the error messages. In the next two chapters you will see how model state allows HTML helpers and the MVC validation features to work together with model binding.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.138.177