2

I am working on my first .NET MVC project and I am to a point where I want my form to iterate through a loop of exercises in a workout (can be any number of exercises). I have a foreach loop, and everything seems to work out ok, but when I check the database, it took only the entries for the first exercise, and applies that to all the entries. I am not sure where I am messing up, or misunderstanding my issue. Any advice in the right direction would be greatly appreciated.

VIEW:

@model WorkoutGenerator.ViewModels.AddRecordViewModel

<h1>Add Exercise Record</h1>

<form asp-controller="Record" asp-action="Add" method="post">
    @foreach (var exercise in Model.Exercises)
    {
        <h4>@exercise.Exercise.Name</h4>
        <div class="form-group">
            <label asp-for="@Model.Sets"></label>
            <input class="form-control" asp-for="@Model.Sets" />
            <span asp-validation-for="@Model.Sets"></span>
        </div>
        <div class="form-group">
            <label asp-for="@Model.Reps"></label>
            <input class="form-control" asp-for="@Model.Reps" />
            <span asp-validation-for="@Model.Reps"></span>
        </div>
        <div class="form-group">
            <label asp-for="@Model.Weight"></label>
            <input class="form-control" asp-for="@Model.Weight" />
            <span asp-validation-for="@Model.Weight"></span>
        </div>
        <input type="hidden" name="ExerciseID" value="@exercise.ExerciseID" />
        <input type="hidden" name="WorkoutID" value="@exercise.WorkoutID" />
        <input type="hidden" name="OwnerID" value="@exercise.Exercise.OwnerId" />
    }

    <input type="submit" value="Add Exercise Record" />
</form>

VIEWMODEL:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using WorkoutGenerator.Models;

namespace WorkoutGenerator.ViewModels
{
    public class AddRecordViewModel
    {
        [Required(ErrorMessage = "Please enter number of sets")]
        public string Sets { get; set; }

        [Required(ErrorMessage = "Please enter number of reps")]
        public string Reps { get; set; }

        [Required(ErrorMessage = "Please enter amount of weight")]
        public string Weight { get; set; }

        //Date Created
        public DateTime DateCreated { get; set; }
        //Link to user
        public string OwnerId { get; set; }
        //Link to Exercise
        public int ExerciseID { get; set; }
        //Link to Workout
        public int WorkoutID { get; set; }

        public IList<ExerciseWorkout> Exercises { get; set; }
        public Workout Workout { get; set; }

        public AddRecordViewModel() { }
    }
}

Controller:

 public IActionResult Add(int id)
        {//Create form for each exercise to have sets reps and weight to submit
            //!!!!!!!!!!!!!!TAKEN FROM WORKOUT CONTROLLER!!!!!!!!!  MAY NEED CHANGING!!!!!!!!!!!!!!!!
            string user = User.Identity.Name;
            ApplicationUser userLoggedIn = context.Users.Single(c => c.UserName == user);
            List<ExerciseWorkout> exercises = context
                .ExerciseWorkouts
                .Include(item => item.Exercise)
                .Where(cm => cm.WorkoutID == id && cm.Workout.OwnerId == userLoggedIn.Id)//cm.Workout.OwnerId == userLoggedIn.Id returns list of owner specific workouts
                .ToList();

            Workout workout = context.Workouts.Single(m => m.WorkoutID == id);

            AddRecordViewModel viewModel = new AddRecordViewModel
            {
                Workout = workout,
                Exercises = exercises
            };

            return View(viewModel);
        }

        [HttpPost]
        public IActionResult Add(AddRecordViewModel addRecordViewModel, int id)
        {//Create records of exercise sets reps and weights to be added to database.
            if (ModelState.IsValid)
            {
                string user = User.Identity.Name;
                ApplicationUser userLoggedIn = context.Users.Single(c => c.UserName == user);
                //exercises hopefully returns list of exercises from 'int id' parameter,
                //which can then be used to iterate over each exercise put into record table
                List<ExerciseWorkout> exercises = context
                .ExerciseWorkouts
                .Include(item => item.Exercise)
                .Where(cm => cm.WorkoutID == id && cm.Workout.OwnerId == userLoggedIn.Id)
                .ToList();
                foreach (var exercise in exercises)
                {
                    Record newRecord = new Record
                    {
                        Sets = addRecordViewModel.Sets,
                        Reps = addRecordViewModel.Reps,
                        Weight = addRecordViewModel.Weight,
                        DateCreated = DateTime.Now,//TODO Make this show only day not time of day
                        OwnerId = userLoggedIn.Id,//TODO Not Sure if creation of newRecord is correct.
                        WorkoutID = addRecordViewModel.WorkoutID,
                        FK_ExerciseID = addRecordViewModel.ExerciseID//TODO ExerciseID not entering into table.
                    };
                    context.Records.Add(newRecord);
                    context.SaveChanges();
                }
                return Redirect("/Record/Index");
            }
            else
            {
                return View(addRecordViewModel);
            }
        }
Brian Burns
  • 113
  • 2
  • 13
  • Your view makes no sense. First you cannot use a `foreach` loop to generate form controls for a collection- refer [Post an HTML Table to ADO.NET DataTable](http://stackoverflow.com/questions/30094047/html-table-to-ado-net-datatable/30094943#30094943). Second your binding to the same property (`Sets` etc) in each iteration - the ModelBinder will only bind the first value with a matching name. Its hard to understand what your trying to achieve here. –  Apr 02 '18 at 06:24
  • And your view model does not represent what you are trying to save in the database. –  Apr 02 '18 at 06:37
  • That would explain the type error I keep receiving about the view model. Unfortunately I have little experience with the view model, so you know of an example or tutorial I could follow? – Brian Burns Apr 02 '18 at 11:51
  • You would need a view model containing properties `Sets`, `Reps` and `Weight` plus one for the `ExerciseWorkout` ID (they are the only form controls you need), plus any other properties of `ExerciseWorkout` you want to display in the view, and you pass a collection of that model to the view, and post back the collection (refer also [Post an HTML Table to ADO.NET DataTable](https://stackoverflow.com/questions/30094047/post-an-html-table-to-ado-net-datatable/30094943#30094943) –  Apr 02 '18 at 11:57
  • Thank you! I will get started on that after work today! – Brian Burns Apr 02 '18 at 11:59

1 Answers1

1

The problem is that the names of Hidden input are all the same. When the controller receives it, it will be treated as if you have only one field to submit.

If you want to submit multiple collection fields from your form.

You can use @Html.HiddenFor with For Loop

@model WorkoutGenerator.ViewModels.AddRecordViewModel

<h1>Add Exercise Record</h1>

<form asp-controller="Record" asp-action="Add" method="post">
    @for (int i = 0; i < Model.Exercises.Count(); i++)
    {
        <h4>@exercise.Exercise.Name</h4>
        <div class="form-group">
            <label asp-for="@Model.Sets"></label>
            <input class="form-control" asp-for="@Model.Sets" />
            <span asp-validation-for="@Model.Sets"></span>
        </div>
        <div class="form-group">
            <label asp-for="@Model.Reps"></label>
            <input class="form-control" asp-for="@Model.Reps" />
            <span asp-validation-for="@Model.Reps"></span>
        </div>
        <div class="form-group">
            <label asp-for="@Model.Weight"></label>
            <input class="form-control" asp-for="@Model.Weight" />
            <span asp-validation-for="@Model.Weight"></span>
        </div>o.exercise.ExerciseID

        @Html.HiddenFor(o=>Model.Exercises[i].ExerciseID)
        @Html.HiddenFor(o=>Model.Exercises[i].WorkoutID)
        @Html.HiddenFor(o=>Model.Exercises[i].OwnerId)
    }

    <input type="submit" value="Add Exercise Record" />
</form>

Or You can use do like this

@model WorkoutGenerator.ViewModels.AddRecordViewModel
@{
    int i = 0;
}
<h1>Add Exercise Record</h1>

<form asp-controller="Record" asp-action="Add" method="post">
    @foreach (var exercise in Model.Exercises)
    {
        <h4>@exercise.Exercise.Name</h4>
        <div class="form-group">
            <label asp-for="@Model.Sets"></label>
            <input class="form-control" asp-for="@Model.Sets" />
            <span asp-validation-for="@Model.Sets"></span>
        </div>
        <div class="form-group">
            <label asp-for="@Model.Reps"></label>
            <input class="form-control" asp-for="@Model.Reps" />
            <span asp-validation-for="@Model.Reps"></span>
        </div>
        <div class="form-group">
            <label asp-for="@Model.Weight"></label>
            <input class="form-control" asp-for="@Model.Weight" />
            <span asp-validation-for="@Model.Weight"></span>
        </div>
        <input type="hidden" name="ExerciseID[@i]" value="@exercise.ExerciseID" />
        <input type="hidden" name="WorkoutID[@i]" value="@exercise.WorkoutID" />
        <input type="hidden" name="OwnerID[@i]" value="@exercise.OwnerId" />
        @i++;
    }

    <input type="submit" value="Add Exercise Record" />
</form>

Then the input name will look like name="ExerciseID[0]",name="ExerciseID[1]" ...

D-Shih
  • 44,943
  • 6
  • 31
  • 51
  • For some reason, it still isn't iteration through, I tried the hiddenfor option. Does my model "viewmodel.addrecordviewmodel" need a List<> around it? I have tried that but always end up getting a data dictionary of wrong type error when I go to the "get" version of my add route – Brian Burns Apr 02 '18 at 04:27
  • I miss the `Exercises` last `s` on `@Html.HiddenFor`, I think `IList<>` can use index . – D-Shih Apr 02 '18 at 04:30
  • 1
    Now I am getting a contraint issue between my tables. I am going to go ahead and mark you as the answer, in hopes that once I get this other issue worked out, your answer will work. Thank you for your help either way! – Brian Burns Apr 02 '18 at 04:42