Monday 1 December 2008

Updating the Model Object from the Form Post Variables

We were having trouble controlling the form post variables that are used in MVC to update our model.  How do you know which fields for your model have been updated and which fields are just defaulted to null on an action?  One option is to have each of the fields being updated as a parameter to the action, but this is messy and requires a lot of manual intervention to setup your model again.

The best solution we’ve found is to use the form collection.  This post by Scott Gu on the MVC Beta release overviews how it can be done.  I’m going to go into a little more detail specific to how that might be useful.

This method is fantastic for allowing you to specify which fields have changed in the post and updating the model object like so.

public ActionResult Add(FormCollection form)
{
    ItemMaster itemMaster = new ItemMaster();
 
    try
    {
        TryUpdateModel(itemMaster, form);
 
        List<ValidationErrorResult> results = _ItemRepository.Add(itemMaster.Item);
 
        if (results.Count > 0)
        {
            // TODO: setup the validation failures.
            return View(typeof(Views.en.StockItemMaster.Index), itemMaster);
        }
        else
        {
            return RedirectToAction("Index/" + itemMaster.Item.Name.ToString());
        }
    }
    catch (Exception e)
    {
        HandleIt.CatchTheException(e);
        return (View(typeof(Views.en.StockItemMaster.Index), itemMaster));
    }
}

On MVC View all you need to do is specify the input fields that are to be used to update the model and the item will be updated for you.  In my case the ItemMaster is my model object for a strongly typed view.  My form looks something like this:

<table class="TableFullWidth">
<tr>
    <td class="ColumnRightAlign">
        <label class="FieldLabel" for="Item.Name">
            Part No.</label>
    </td>
    <td>
        <input name="Item.Name" style="width: 75px" value="<%= ViewData.Eval("Item.Name") %>"
            FocusOnMe="true" &lt;%= ViewData.Eval("ItemKeyFieldSearch") %&gt; />
    </td>
    <td colspan="4">
        <input name="Item.Description" style="width: 275px" value="<%= ViewData.Eval("Item.Description") %>" />
    </td>
</tr>

Note: See we’re not using HTML Helpers for this view.  There is a good reason for that which I’m not going into in this post.

The input tags are wrapped up in a form with the action set to the Add method.  When I call TryUpdateModel the model will update and the ModelState will be populated with the exceptions.  The following test is a great example.

FormCollection form = new FormCollection();
form.Add("Item.ItemCodeID", "string");
ResetController();
ViewResult result = (ViewResult)_Target.Save(_TestItems[0].ItemCodeID, form);
Assert.AreEqual("StockItemMaster/Index", result.ViewName);
Assert.AreEqual(_TestItems[0].Name, ((ItemMaster)result.ViewData.Model).Item.Name);

This is a problem that because the ItemCodeID field is an integer field and I’ve set a string into the form post variable.  If I breakpoint my code and investigate the result you can see the error:

image

You can see the error message noting that the value “string” for your form is invalid.  If we were to use UpdateModel instead of TryUpdateModel, we would be catching an exception for each of these errors. 

It’s worth noting that UpdateModel is not a very good idea because if you’re dealing with the Entity Framework then your EntityKey field is going to cause an error every time unless you happen to post a form variable with the key in it. 

You can now use the ModelState in your view to display all your validation errors specific to the database or data model you’re using.

No comments: