skip to Main Content

I am building a C# assignment and facing an issue. I have 2 models students and course. I needed a place to show all the students related to a course and ability to add new student. So I created a new view model CourseViewModel:

using ProblemAssignment2.Models;

namespace ProblemAssignment2.ViewModels
{
    public class CourseViewModel
    {
        public Course Course { get; set; }
        public Student NewStudent { get; set; }
    }
}

I have a Manage.cshtml page in views/Course folder.

@model ProblemAssignment2.ViewModels.CourseViewModel

<h1>Manage Course</h1>

<div>
    <h4>@Model.Course.Title</h4>
    <table class="table">
        <thead>
            <tr>
                <th>Last Name</th>
                <th>First Name</th>
                <th>Email</th>
                <th>Status</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var student in Model.Course.Students)
            {
                <tr>
                    <td>@student.LastName</td>
                    <td>@student.FirstName</td>
                    <td>@student.Email</td>
                    <td>@student.EnrollmentStatus</td>
                </tr>
            }
        </tbody>
    </table>
</div>

<h2>Create Student</h2>
<form asp-action="createStudent" method="post">
    <div class="form-group">
        <label asp-for="NewStudent.LastName" class="control-label">Last Name</label>
        <input asp-for="NewStudent.LastName" name="Last" class="form-control" />
        <span asp-validation-for="NewStudent.LastName" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="NewStudent.FirstName" class="control-label">First Name</label>
        <input asp-for="NewStudent.FirstName" name="First" class="form-control" />
        <span asp-validation-for="NewStudent.FirstName" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="NewStudent.Email" class="control-label">Email</label>
        <input asp-for="NewStudent.Email" name="Email" class="form-control" />
        <span asp-validation-for="NewStudent.Email" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Create Student</button>
</form>

<h2>Send Confirmation Message</h2>
<form asp-action="SendConfirmationMessage" method="post">
    <input type="hidden" name="courseId" asp-for="@Model.Course.CourseID" />
    <button type="submit" class="btn btn-secondary">Send Confirmation Message</button>
</form>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

In CourseController, I have added 2 methods to show view and create student:

public async Task<IActionResult> Manage(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var course = await _context.Courses
                               .Include(c => c.Students)
                               .FirstOrDefaultAsync(m => m.CourseID == id);

    if (course == null)
    {
        return NotFound();
    }

    var viewModel = new CourseViewModel
            {
                Course = course,
                NewStudent = new Student()
            };

    return View(viewModel);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> createStudent(int id, CourseViewModel viewModel)
{
    Debug.WriteLine(viewModel.Course.CourseID);
    viewModel.NewStudent.EnrollmentStatus = Student.Status.InvitationSent;

    if (ModelState.IsValid)
    {
        var course = await _context.Courses.FindAsync(id);

        if (course == null)
        {
            return NotFound();
        }

        viewModel.NewStudent.CourseID = id;
        viewModel.NewStudent.EnrollmentStatus = Student.Status.InvitationSent; // Default status

        _context.Students.Add(viewModel.NewStudent);

        await _context.SaveChangesAsync();

        return RedirectToAction(nameof(Manage), new { id = viewModel.Course.CourseID });
    }

    // If ModelState is invalid, return the Manage view with the current viewModel
    return View(nameof(Manage), id);
}

I am able to view all the students related to course. But when I add some data to form and try to submit it, I get null exceptions.

I added breakpoints on createstudent and found out that id value is 0 and viewModel.Course and viewModel.NewStudent both are null. What can be reason that on submit of form both these values are getting set to null?

I have checked in manage function I am setting course value and also initializing newStudent to a object.

2

Answers


  1. Issue 1: Your API action expects to receive the request body with the nested object NewStudent, but you are sending the value without nesting them in the NewStudent object. For example the <input> element with the name="Last".

    When you work with the name attribute, you must ensure the field name matches the property in the request body.

    For example:

    <input asp-for="NewStudent.LastName" name="NewStudent.LastName" class="form-control" />
    <input asp-for="NewStudent.FirstName" name="NewStudent.FirstName" class="form-control" />
    <input asp-for="NewStudent.Email" name="NewStudent.Email" class="form-control" />
    

    Or you can work without the name attribute as the asp-for tag helper will help you generate the name attribute (unless you need to customize the field (path) according to the request body in your API action)

    <input asp-for="NewStudent.LastName" class="form-control" />
    <input asp-for="NewStudent.FirstName" class="form-control" />
    <input asp-for="NewStudent.Email" class="form-control" />
    

    For the Course object, you should send the value with <input type="hidden" />

    <input type="hidden" asp-for="Course.CourseID" />
    <input type="hidden" asp-for="Course.Title" />
    

    Issue 2: Your API action expects a parameter id, but you are not sending it.

    The final <form> element should be as below:

    <form asp-action="createStudent" method="post" asp-route-id="@Model.Course.CourseID">
    
        <input type="hidden" asp-for="Course.CourseID" />
        <input type="hidden" asp-for="Course.Title" />
    
        <div class="form-group">
            <label asp-for="NewStudent.LastName" class="control-label">Last Name</label>
            <input asp-for="NewStudent.LastName" name="NewStudent.LastName" class="form-control" />
            <span asp-validation-for="NewStudent.LastName" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="NewStudent.FirstName" class="control-label">First Name</label>
            <input asp-for="NewStudent.FirstName" name="NewStudent.FirstName" class="form-control" />
            <span asp-validation-for="NewStudent.FirstName" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="NewStudent.Email" class="control-label">Email</label>
            <input asp-for="NewStudent.Email" name="NewStudent.Email" class="form-control" />
            <span asp-validation-for="NewStudent.Email" class="text-danger"></span>
        </div>
        <button type="submit" class="btn btn-primary">Create Student</button>
    </form>
    
    Login or Signup to reply.
  2. Here is why your post method is returning zero

    Your post action method definition is defined wrong. According to flow of ASP.NET MVC or even core in form format you are attaching a viewModel to a form, so, in signature of the post method you can not add additional parameters unless you explicitly add that information into a URL as already mentioned in this thread. Since you are not passing any course related info ID or title when submitting the form, therefore, your course object will surely be empty or null. So, to map course object necessary info, Simply add a hidden input values to send the course ID and title to your post action method

    Add below line within your Form

    ...
    
    
    <input type="hidden" asp-for="Course.CourseID" />
    <input type="hidden" asp-for="Course.Title" />
    
    ...
    

    Change your post action method definition as below

    ...
    
    public async Task<IActionResult> createStudent(CourseViewModel viewModel)
    
    ... 
    

    This will solve your problem.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search