Search code examples
asp.netasp.net-mvcasp.net-mvc-2updatemodel

Update complex model in ASP.NET MVC 2?


How can I update a complex model? I have the following View:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Tidrapportering.Models.Week>" %>
<% using (Html.BeginForm())
   {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
    <legend>Fields</legend>
    <table width="100%">
        <% foreach (var task in Model.Tasks)
           { %>
        <tr>
            <td>
                <%: task.Customer.CustomerName %>
            </td>
            <td>
                <%: task.TaskName %>
            </td>
            <td>
                <% foreach (var ts in task.TimeSegments)
                   { %>
                <%= Html.TextBox("Hours", ts.Hours)%>
                <% } %>
            </td>
        </tr>
        <% } %>
    </table>
    <p>
        <input type="submit" value="Spara tid" />
    </p>
</fieldset>
<% } %>
<div>
    <%: Html.ActionLink("Back to List", "Index") %>
</div>

And I tried update it as usual just calling UpdataModel on the model object: UpdateModel(week); But that didn't work. So I read something about having to update each property separately in complex models, and tried to adapt it to my situation. Here's my attempt in the Controller:

    [HttpPost]
    public ActionResult EditTasks(int id, FormCollection collection)
    {
        //try
        //{
            Week week = _model.GetWeek(id);


            foreach (var task in week.Tasks)
            {
                foreach (var timeSegment in task.TimeSegments)
                {                        
                    UpdateModel(timeSegment.Hours, "Hours");
                }
            }


            //UpdateModel(week);
            _model.Save();
            return RedirectToAction("Index");
        //}
        //catch
        //{
        //    return View();
        //}
    }

But that didn't work either. It seems to work if the property is a string, but this is an int, and the compiler complains that it must be a reference type to be used as a TModel.

I don't know if this is the way to go, I just need to understand how to be able to update a complex type model like this. This can't be too uncommon so there must be some standard method, but I can't figure it out...

Any ideas?

UPDATE:

The following works:

Action method:

    [HttpPost]
    public ActionResult EditTasks(int id, FormCollection collection)
    {
        try
        {
            Week week = _model.GetWeek(id);
            for (int i = 0; i < week.TaskList.Count; i++)
            {
                var task = week.TaskList[i];
                for (int j = 0; j < task.TimeSegmentList.Count; j++)
                {
                    int hours = Int32.Parse(collection["TaskList[" + i + "].TimeSegmentList[" + j + "].Hours"]);
                    week.TaskList[i].TimeSegmentList[j].Hours = hours;
                }
            }

            //UpdateModel(week);
            _model.Save();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

View:

    <% for (int i = 0; i < Model.TaskList.Count; i++)
       {
           var task = Model.TaskList[i];
    %>
    <tr>
        <td>
            <%: task.Customer.CustomerName %>
        </td>
        <td>
            <%: task.TaskName %>
        </td>
        <% for (int j = 0; j < task.TimeSegmentList.Count; j++)
           { %>
        <td>
            <%: Html.TextBoxFor(model => model.TaskList[i].TimeSegmentList[j].Hours, new { @class = "hourInput" })%>
        </td>
        <% } %>
    </tr>
    <% } %>

However, the updating has to be manual like this, which seems unnecessarily complex. I found a post by Phil Haack (http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx) which seems to suggest this should be possible to do in a simpler way, like this:

    [HttpPost]
    public ActionResult EditTasks(Week week)
    {
        try
        {
            UpdateModel(week);
            _model.Save();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

I.e. simply receiving the Week as the parameter for the method (in Haacks example it doesn't even seem he has to call UpdateModel, just having the View bound to the objects seems to be enough, so I would only have to save it in the database... But it doesn't work for me. The Week object that is returned doesn't seem to be the same one sent to the view, it has no items in its collections of tasks, e.g.

So why doesn't this work?


Solution

  • Try with for instead of foreach

    <fieldset>
        <legend>Fields</legend>
        <table width="100%">
            <% for (int i = 0; i < Model.Tasks.Count; i++)
               { 
                  var task = Model.Tasks[i];
            %>
            <tr>
                <td>
                    <%: task.Customer.CustomerName %>
                </td>
                <td>
                    <%: task.TaskName %>
                </td>
                <td>
                    <% for (int j = 0; j < task.TimeSegments.Count; j++)
                       { %>
                    <%= Html.TextBox("Model.Tasks["+i+"].TimeSegments["+j+"].Hours")%>
                    <% } %>
                </td>
            </tr>
            <% } %>
        </table>
        <p>
            <input type="submit" value="Spara tid" />
        </p>
    </fieldset>