Real example of TryUpdateModel, ASP .NET MVC 3

Sir Hally picture Sir Hally · Oct 12, 2011 · Viewed 40.5k times · Source

I can't understand, how to use TryUpdateModel and save the MVC architecture at the same time.

If I am not mistaken, work with datacontexts must be in the Model. So, such code

var db=new TestEverybody();//it is class, which was generated by EntityFramework 
var currentTesting=db.Testing.(t => t.id == id).First();

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

But the ussual examples of TryUpdateModel usage is following:

    public ActionResult Edit(Testing obj)//Testing collection
    {
        var db = new TestEverybody();
        var currentTesting=db.Testing.(t => t.id == obj.id).First();
        TryUpdateModel(currentTesting);
        db.SaveChanges();            
        return RedirectToAction("Index");
    }

Doesn't this way break the MVC architecture? We work with database in the controller, not in the special Model class.

So, what is the best way to use TryUpdateModel in a real project?

Answer

Ben Foster picture Ben Foster · Oct 15, 2011

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.