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);
            }
        }
5
  • 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. 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. Commented Apr 2, 2018 at 6:24
  • And your view model does not represent what you are trying to save in the database. Commented Apr 2, 2018 at 6: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? Commented Apr 2, 2018 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 Commented Apr 2, 2018 at 11:57
  • Thank you! I will get started on that after work today! Commented Apr 2, 2018 at 11:59

1 Answer 1

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]" ...

Sign up to request clarification or add additional context in comments.

3 Comments

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
I miss the Exercises last s on @Html.HiddenFor, I think IList<> can use index .
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!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.