0

I'm fairly new to MVC but am progressing. I have come across an issue that I can't seem to solve and would be greatful of any assistance.

When I post to the server my edits (in RoutineViewModel) are mostly lost, primitive data types are persisted (in class Routine) but collections of complex types (ICollection<RoutineExercise>) are lost.

I found this MVC Form not able to post List of objects and followed the advice to seperate the view into an EditorTemplate but this has not worked. Using the '@foreach' loop still produces all the page controls with the same id and name when you viewsource. I tried using a for (int i = 1; i <= 5; i++) type loop as many other posts suggest but get errors about not being able to apply index to my object.

Also the fact this @Html.DropDownListFor(model => Model.ExerciseId, Model.Exercises, "", new { @class = "input-sm col-md-12" }) does not select the correct list item (Model.ExerciseId has the correct value) concerns me.

Any help/advice would be great as I'm stuck and have been for 3 days now.

* POCO *

public partial class Routine
    {
        public Routine()
        {
            this.RoutineExercises = new List<RoutineExercise>();
        }

        public int Id { get; set; }
        public string RoutineName { get; set; }
        public string Description { get; set; }

        ...Other fields removed for clarity...

        public virtual ICollection<RoutineExercise> RoutineExercises { get; set; }
    }



 public partial class RoutineExercise
    {
        public int Id { get; set; }
        public int RoutineId { get; set; }
        public int Exerciseid { get; set; }
        public int SetsToDo { get; set; }
        public int RepsToDo { get; set; }

    ...Other fields removed for clarity...

        public virtual Exercise Exercise { get; set; } 
        public virtual Routine Routine { get; set; }
    }

* VIEWMODEL *

public class RoutineViewModel
    {      
    //Routine information  
        public int Id { get; set; }
        [Display(Name = "Name")]
        public string RoutineName { get; set; }
        public string Description { get; set; }                                    

    //Exercise information
        [Display(Name = "Exercise")]
        public ICollection<RoutineExercise> RoutineExercises { get; set; }
        public IEnumerable<SelectListItem> Exercises { get; set; }
        public int ExerciseId { get; set; }        

    }

* FORM *

<div class="panel-body">
    @using (Html.BeginForm("Edit", "Workout"))
    {
        @Html.AntiForgeryToken()

        <div class="form-horizontal">        

            @Html.ValidationSummary(true)
            @Html.HiddenFor(model => model.Id)
            @Html.EditorForModel()

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Save" class="btn btn-default" />
                </div>
            </div>
        </div>
    }
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>
</div>

* EDITOR TEMPLATE *

<div class="form-group">
    @Html.LabelFor(model => model.RoutineName, new { @class = "control-label col-md-1" })
    <div class="col-md-2">
        @Html.EditorFor(model => model.RoutineName)
        @Html.ValidationMessageFor(model => model.RoutineName)
    </div>

    @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-1" })
    <div class="col-md-2">
        @Html.EditorFor(model => model.Description)
        @Html.ValidationMessageFor(model => model.Description)
    </div>   
</div>

@foreach (var e in Model.RoutineExercises)
{                                                                                               
    @Html.LabelFor(model => model.RoutineExercises, new { @class = "control-label col-md-1" })                                                          
    <div class="col-md-3">
       @*TO FIX This does NOT bind the selected value*@
       @Html.DropDownListFor(model => Model.ExerciseId, Model.Exercises, "", new { @class = "input-sm col-md-12" })
    </div>                                                 
    <div class="col-md-12">
        @Html.LabelFor(model => e.SetsToDo, new { @class = "control-label col-md-2" })
        @Html.EditorFor(m => e.SetsToDo, new { @class = "control-label col-md-10" })
    </div>    
}

* CONTROLLER *

[HttpPost]
        [ValidateAntiForgeryToken]                        
        public ActionResult Edit(RoutineViewModel rvm) /*rvm always null for collections only*/
        {
            if (ModelState.IsValid)
            {
                //Save Routine
                var r = new Routine
                {
                    Id = rvm.Id,
                    RoutineName = rvm.RoutineName,
                    Description = rvm.Description,
                    RoutineFrequencyId = rvm.RoutineFrequencyId,
                    RoutineLengthId = rvm.RoutineLengthId                                        
                };

                _repo.Update(r);

                return RedirectToAction("Index");
            }

            return View(getRoutineViewModel(rvm.Id));           
        }
Community
  • 1
  • 1
DJ811
  • 5
  • 5

1 Answers1

0

First, avoid the term "complex type" unless you're actually talking about a complex type in Entity Framework. It just creates confusion, and honestly, nothing you have here is really "complex" anyways.

You will indeed need to employ a for loop with an index instead of foreach to get the proper field names for the modelbinder to work with. However, the reason you're getting an error is that ICollection is not subscriptable ([N]). You can use ElementAt(N) to pull out the item at an index, but unfortunately, Razor will still not create the right field names with that. As a result, you need to use something like List for your collection properties to edit them inline. Since you're already using a view model this is trivial. Just change the property type from ICollection<RoutineExcercise> to List<RoutineExcercise> on your view model.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Hi Chris, thanks so much for replying to my post, I really appreciate it. Your advice worked perfectly, I can now post back and update data, its like a revelation, you've made me very happy! The only bug left is the drop down list not binding initially but I'll look into that. You've definitely put me on the right track though, many many thanks. – DJ811 Nov 06 '14 at 19:47