0

I have an Attendance program in which I want to assign Students to AttendanceTakers. I am using a table where the headers are the AttendanceTakers and the rows are Students and each cell has a RadioButton. It is basically a double array of RadioButtons. My problem is I can't get it to post.

My AttendanceTaker class

public class SessionAttendanceTaker
    {

        public int Id { get; set; }

        [ForeignKey("Session")]
        public int SessionId { get; set; }
        public Session Session { get; set; }

        [Display(Name="Attendance Taker")]
        [ForeignKey("User")]
        public string AttendanceTakerId { get; set; }
        [Display(Name = "Attendance Taker")]
        public User User { get; set; }

        public List<Student> Students { get; set; }
    }

And the Student that is in the course class

public class StudentSession 
    {

        public int Id { get; set; }

        [ForeignKey("Session")]
        [DisplayName("Session")]
        public int SessionId { get; set; }
        public Session Session { get; set; }

        [ForeignKey("Student")]
        [DisplayName("Student")]
        public int StudentId { get; set; }
        public Student Student { get; set; }

        [DisplayName("Credits Awarded")]
        public int Credit { get; set; }
}

Student class

public class Student 
    {
        public int Id { get; set; }


        [ForeignKey("User")]
        public string UserId { get; set; }

        [DisplayName("Name")]
        public virtual User user { get; set; }

        public Student() 
        {

        }
  }

The View

@using (Html.BeginForm())
{

    <div class="form-horizontal">
        <table>
            <thead>
                <tr>
                    <th> Name &nbsp; &nbsp;</th>

                    @{
                        foreach (var attendanceTaker in Model.SessionAttendanceTakers)
                        {
                            <th>@attendanceTaker.User.LastName, @attendanceTaker.User.FirstName &nbsp; &nbsp;</th>
                        }
                    }

                </tr>
            </thead>
            <tbody>

                @{
                    //See https://stackoverflow.com/questions/7667495/mvc-radiobuttons-in-foreach to try and clean the foreach
                    foreach (var studentSession in Model.StudentSessions)
                    {

                        <tr>
                            <td>
                                @studentSession.Student.User.LastName, @studentSession.Student.User.FirstName
                            </td>
                            @foreach (var attendanceTaker in Model.SessionAttendanceTakers)
                            {
                                @Html.EditorFor(Model => Model.SessionAttendanceTakers, "StudentsToAttendanceTakersModel", "" + studentSession.StudentId, new { htmlAttributes = new { @class = "form-control" } })
                            }

                        </tr>
                    }
                }
            </tbody>
        </table>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Assign" class="btn btn-default" />
            </div>
        </div>
    </div>
}

and EditorTemplate

@model IEnumerable<SessionAttendanceTaker>
@using Attendance.Models


<td>
    @Html.RadioButtonFor(model => model, new { htmlAttributes = new { @class = "form-control" } })
</td>

As an aside I would love to get rid of the foreaches as per this post but since I don't know how many attendance takers or students there will be until runtime I can't figure out how to do that besides for just moving them to the Editor and I don't see a point to that.

Also the Controller

[HttpPost]
        public ActionResult Assign(StudentsToAttendanceTakersModel model)
        {

            return RedirectToAction("Index");
        }

I have a breakpoint on the return and the attendanceTakers is null and Student sessions has a count of 0.

Additionally, using FormCollection

public ActionResult Assign(FormCollection o)

only gives me the Students who's RadioButton was clicked but not the AttendanceTaker. If more info is needed let me know. Thanks.

EDIT Model

public class StudentsToAttendanceTakersModel
    {
        public IEnumerable<StudentSession> StudentSessions { get; set; }
        public IEnumerable<SessionAttendanceTaker> SessionAttendanceTakers { get; set; }

        public StudentsToAttendanceTakersModel() { }
    }
A_Arnold
  • 3,195
  • 25
  • 39
  • You have not shown the model that you are using in the view, but in any case you will not be able to use a `foreach (var studentSession in Model.StudentSessions)` - refer [Post an HTML Table to ADO.NET DataTable](https://stackoverflow.com/questions/30094047/post-an-html-table-to-ado-net-datatable/30094943#30094943) for an explanation of why you would be generating `name` attributes that have no relationship to your model. –  Aug 03 '18 at 02:47
  • You need to show the model in the view. And can you confirm that for each `Student`, you want to select one `AttendanceTakers` –  Aug 03 '18 at 02:49
  • Completely spaced out. I added the Model. Yes for each Student, I want to select one of the AttendanceTakers. One AttendanceTaker many Students. – A_Arnold Aug 03 '18 at 16:11

1 Answers1

0

You're creating radio buttons which do not relate to your model, and you're trying to bind them to a complex object (SessionAttendanceTaker) - a radio button posts back a simple value (and you are not even giving the radio buttons a valid value - the 2nd parameter of RadioButtonFor() is the value).

You are editing data, so you should start by creating view models which represent what you want to display in the view.

public class StudentVM
{
    public int ID { get; set; }
    public string Name { get; set; }
    [Required(ErrorMessage = "Please select an attendance taker")]
    public int? SelectedAttendanceTaker { get; set; }
}
public class AttendanceTakerVM
{
    public int ID { get; set; }
    public string Name { get; set; }
}
public class StudentAttendanceTakersVM
{
    public List<StudentVM> Students { get; set }
    public IEnumerable<AttendanceTakerVM> AttendanceTakers { get; set; }
}

So that your view will be

@model StudentAttendanceTakersVM
....
@using (Html.BeginForm())
{
    <table>
        <thead>
            <tr>
                <th>Student</th>
                @foreach(var taker in Model.AttendanceTakers)
                {
                    <th>@taker.Name</th>
                }
                <th></th>
            </tr>
        </thead>
        <tbody>
        @for(int i = 0; i < Model.Students.Count; i++)
        {
            <tr>
                <td>
                    @Model.Students[i].Name
                    @Html.HiddenFor(m => m.Students[i].ID)
                    @Html.HiddenFor(m => m.Students[i].Name)
                </td>
                @foreach(var taker in Model.AttendanceTakers)
                {
                    <td>@Html.RadioButtonFor(m => m.Students[i].SelectedAttendanceTaker, taker.ID, new { @class = "form-control" })</td>
                }
                <td>@Html.ValidationMessageFor(m => m.Students[i].SelectedAttendanceTaker)</td>
            </tr>
        }
        </tbody>
    </table>
    <input type="submit" ... />
}

Your GET method will then initialize an instance of you view model and pass it to the view, for example, for a 'Create' method

public ActionResult Create()
{
    var students = db.Students.Select(x => new StudentVM
    {
        ID = x.Id,
        Name = x.User.FirstName + " " + x.User.LastName // adjust as required
    }).ToList();
    var attendanceTakers = db.SessionAttendanceTakers.Select(x => new AttendanceTakerVM
    {
        ID = x.Id,
        Name = x.User.FirstName + " " + x.User.LastName // adjust as required    
    });
    StudentAttendanceTakersVM model = new StudentAttendanceTakersVM
    {
        Students = students,
        AttendanceTakers = attendanceTakers
    };
    return View(model);
}

And the POST method will be

public ActionResult Create(StudentAttendanceTakersVM model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    // loop through model.Students to get the ID of the Student and its selected AttendanceTaker
    // initialize the data models and save to the database
    return RedirectToAction("Index");
}
  • It is posting the RadioButtons but not the AttendanceTakers. Error is "The parameter conversion from type 'System.String' to type 'Models.AttendanceTakerVM' failed because no type converter can convert between these types." Do you know why that is happening? – A_Arnold Aug 06 '18 at 17:50
  • Sorry, I do not understand what you mean. My code does not create any form controls for `AttendanceTakers` (and nor should it - if you need `AttendanceTakers` in the POST method, then you call the db to get them again). And the code I have shown will not throw that error, so I am not sure what you have changed. –  Aug 06 '18 at 23:50
  • The ModelState is not valid and gives me that error. It seems that it is trying to convert the object Models.AttendanceTakerVM into a string bec it is attempting to bind it. – A_Arnold Aug 07 '18 at 14:33
  • But there is nothing in my code that will do that, therefore you have something different in your code. I will create a DotNetFiddle for you later today to show how it all works –  Aug 08 '18 at 01:08
  • @A_Arnold. Refer [this DotNetFiddle](https://dotnetfiddle.net/qPaMZL). As you can see, if you submit the form, `ModelState` is not valid. –  Aug 08 '18 at 02:41
  • If I go start the program on the View making that the first page ModelState is valid if I come in through the link to the View it isn't. I don't even know what code to post bec it seems to me that all the variables are the same. I will try and make a fiddle that reproduces it. – A_Arnold Aug 08 '18 at 16:06
  • The only difference I notice is in the URL that if it is just the View name it's true but if it uses the url to pass data it's false. – A_Arnold Aug 08 '18 at 16:14
  • I fixed it. I think it was trying to bind part of my URL to the model. Thanks. – A_Arnold Aug 08 '18 at 20:44