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
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 theNewStudent
object. For example the<input>
element with thename="Last"
.When you work with the
name
attribute, you must ensure the field name matches the property in the request body.For example:
Or you can work without the
name
attribute as theasp-for
tag helper will help you generate thename
attribute (unless you need to customize the field (path) according to the request body in your API action)For the
Course
object, you should send the value with<input type="hidden" />
Issue 2: Your API action expects a parameter
id
, but you are not sending it.The final
<form>
element should be as below: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
Change your post action method definition as below
This will solve your problem.