Real example of TryUpdateModel, ASP .NET MVC 3

Since the OP asked, here's an example of the ViewModel pattern, or as I like to call it - ASP.NET MVC done properly.

So why use a view specific model

  1. You should only pass the information to your view that it needs.
  2. Often you'll need to add additional view-meta-data (such as title/description attributes). These do not belong on your entities.
  3. Using TryUpdateModel/UpdateModel is wrong. Don't use (I'll explain why).
  4. It's very rare that your view-models will exactly match your entities. People often end up adding additional cruft to their entities or (not much better) just using ViewBag rather than strongly typed view model properties.
  5. If you're using an ORM you can run into issues with Lazy loaded properties (N+1). Your views should not issue queries.

We'll start with a simple entity:

public class Product {
    public int Id {get;set;}
    public string Name {get;set;}
    public string Description {get;set;}
    public decimal Price {get;set;}
}

And let's say you have a simple form where the user can only update the Name and Description of the product. But you're using (the very greedy) TryUpdateModel.

So I use any number of tools (like Fiddler) to construct a POST myself and send the following:

Name=WhatverIWant&Description=UnluckyFool&Price=0

Well the ASP.NET MVC model binder is going to inspect the input form collection, see that these properties exist on your entity and automatically bind them for you. So when you call "TryUpdateModel" on the entity you've just retrieved from your database, all of the matching properties will be updated (including the Price!). Time for a new option.

View Specific Model

public class EditProductViewModel {
    [HiddenInput]
    public Guid Id {get;set;}

    [Required]
    [DisplayName("Product Name")]
    public string Name {get;set;}

    [AllowHtml]
    [DataType(DataType.MultilineText)]
    public string Description {get;set;}
}

This contains just the properties we need in our view. Notice we've also added some validation attributes, display attributes and some mvc specific attributes.

By not being restricted in what we have in our view model it can make your views much cleaner. For example, we could render out our entire edit form by having the following in our view:

@Html.EditorFor(model => model)

Mvc will inspect all of those attributes we've added to our view model and automatically wire up validation, labels and the correct input fields (i.e. a textarea for description).

POSTing the form

[HttpPost]
public ActionResult EditProduct(EditProductViewModel model) {

    var product = repository.GetById(model.Id);

    if (product == null) {
        return HttpNotFound();
    }

    // input validation
    if (ModelState.IsValid) {

        // map the properties we **actually** want to update
        product.Name = model.Name;
        product.Description = model.Description;

        repository.Save(product);

        return RedirectToAction("index");
    }

    return View(model)
}

It's fairly obvious from this code what it does. We don't have any undesirable effects when we update our entity since we are explicitly setting properties on our entity.

I hope this explains the View-Model pattern enough for you to want to use it.


So, such code must be situated in the Model, not in the Controller, mustn't it?

Not necessarily. Personally I prefer to put data access code in a repository. Then use constructor injection to pass some specific repository implementation to the controller (for example if I was using EF, I would write an EF repository implementation). So the controller will look like this:

public class HomeController: Controller
{
    private readonly IMyRepository _repository;
    public HomeController(IMyRepository repository)
    {
        _repository = repository;
    }

    public ActionResult Edit(int id)
    {
        var currentTesting = _repository.GetTesting(id);
        TryUpdateModel(currentTesting);
        _repository.SaveChanges();            
        return RedirectToAction("Index");
    }
}