Reputation: 121
I'm trying to get a delete operation to work on an entity with a composite key on a Razor page using ASP.NET Core 3.0 and Entity Framework 6. The entity in question is a CourseAssignment
whose composite key is composed of an InstructorID
value and a CourseID
value.
For an entity with an ordinary, single-field key—say, a Course
entity with a CourseID
primary key—the code on the razor page is this:
<form method="post">
<input type="hidden" asp-for="Course.CourseID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
Which produces a URL like this: https://localhost:44388/Courses/Delete?id=1045
The C# code that acts on this is:
public async Task<IActionResult> OnPostAsync(int? id) {
if (id == null) {
return NotFound();
}
Course = await _context.Courses.FindAsync(id);
if (Course != null) {
_context.Courses.Remove(Course);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
How do I change both the razor page and the C# OnPostAsync
action to handle the entity with a composite key requiring two values?
Thanks!
Upvotes: 6
Views: 14369
Reputation: 121
Well, as it so happens, my problem was in the POST code of Index.cshtml
, which calls the Delete page, rather than in the POST code of the Delete page itself. There was also an issue in the OnGetAsync()
method of the Delete.cshtml.cs
page, rather than its OnPostAsnc()
method (although that had to be adjusted to accept two parameters as well).
With the hints provided in Cameron Tinker and Jeremy Caney answers, I got it figured out. The link to the delete page for an entity with a non-composite primary key looks like this:
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>-->
For an entity with a composite key, it needs to look something like this:
<a asp-page="./Delete" asp-route-courseID="@item.CourseID" asp-route-instructorID="@item.InstructorID">Delete</a>
The last part of the asp-route
helper is the name of the parameter that will be passed. (That probably doesn't matter except for clarity in the Razor page itself, which would be an issue if you were just passing a constant as the value, instead of the value of a named property like I'm doing.)
The C# code that acts on this is in the OnGetAsync()
method of the called page, Delete.cshtml.cs
:
public async Task<IActionResult> OnGetAsync(int? courseID, int? instructorID) {
if (courseID == null || instructorID == null) {
return NotFound();
}
CourseAssignment = await _context.CourseAssignments
.AsNoTracking()
.Include(c => c.Course)
.Include(c => c.Instructor)
.FirstOrDefaultAsync(m => m.CourseID == courseID && m.InstructorID == instructorID);
if (CourseAssignment == null) {
return NotFound();
}
return Page();
}
Upvotes: 4
Reputation: 7613
You can pass multiple values from the form by adding additional hidden fields. So, let us say the secondary field in your composite primary key is Course.Instructor
; in that case, you might add:
<input type="hidden" asp-for="Course.Instructor" />
You would then extend the signature for your OnPostAsync()
action to include that field:
public async Task<IActionResult> OnPostAsync(int? id, string instructor) { … }
Note: If you’re using C# 8.0’s nullable annotation context, then you’ll likely want
string?
for your parameter type.
At that point, you should be able pass the value on as a second parameter to FindAsync()
:
Course = await _context.Courses.FindAsync(id, instructor);
Note: This works because the
FindAsync()
method accepts aparams Object[] keyValues
which correspond to the primary key constraint.
Obviously, you’ll need to confirm that the name
generated for your hidden field matches your action parameter name, or otherwise provide a binding hint.
Upvotes: 3
Reputation: 9789
Update your HTML form to something like the following:
<form method="post">
<input type="hidden" asp-for="Course.CourseID" />
<input type="hidden" asp-for="Course.InstructorID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
Create a new class for handling the composite key. You can use your existing view model class too.
public class DeleteCourseRequest {
public int CourseId { get; set; }
public int InstructorId { get; set; }
}
Then update your controller's OnPostAsync action to something like this:
public async Task<IActionResult> OnPostAsync(DeleteCourseRequest request) {
if (request == null) {
return NotFound();
}
Course = await _context.Courses.FirstOrDefaultAsync(c => c.CourseID == request.CourseID && c.InstructorID == request.InstructorID);
if (Course != null) {
_context.Courses.Remove(Course);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
The issue is that when doing a Post with more than one parameter, you would either have to use [FromBody]
or [FromUri]
bindings on your parameters. This tells ASP.NET Web API where to parse the parameters. Another option is to use a view model to pass your information from your form to your controller.
Upvotes: 4