Here is my code:
albums.cshtml
@page
@model Project.Pages.AlbumsModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div class="row">
<h1 class="display-2">Albums</h1>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Album ID, artist ID, Album title</th>
</tr>
</thead>
<tbody>
@foreach (string album in Model.Albums)
{
<tr>
<td>@album</td>
<td><a asp-page="/Albums/Delete" asp-route-id="@album">Delete</a></td>
<td><a asp-page="/Albums/Edit" asp-route-id="@album">Edit title</a></td>
</tr>
}
</tbody>
</table>
</div>
<div class="row">
<p>Enter a title & ArtistID for a new album: </p>
<form method="POST">
<div><input asp-for="Album.Title" /></div>
<div><input asp-for="Album.ArtistId" /></div>
<input type="submit" />
</form>
</div>
albums.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Project.Models;
using System;
namespace Project.Pages
{
public class AlbumsModel : PageModel
{
private Chinook db;
public AlbumsModel(Chinook injectedContext)
{
db = injectedContext;
}
public IEnumerable<string> Albums { get; set; }
public void OnGet()
{
ViewData["Title"] = "Chinook Web Site - Albums";
Albums = db.Albums.Select(s => s.AlbumId.ToString() + ". " + s.ArtistId + ". " + s.Title);
}
[BindProperty]
public Album Album { get; set; }
public IActionResult OnPost()
{
if (ModelState.IsValid)
{
db.Albums.Add(Album);
db.SaveChanges();
return RedirectToPage("/albums");
}
return Page();
}
public IActionResult DeleteAlbum(int AlbumId)
{
var album = db.Albums.Find(AlbumId);
if (album == null) return Page();
db.Albums.Remove(album); db.SaveChanges(); return RedirectToPage("/albums");
}
}
}
delete.cshtml
@page "{id?}"
@model Project.Pages.Albums.DeleteModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<p class="text-danger">@Model.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Album</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Album.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Album.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Album.ArtistId)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Album.ArtistId)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Album.AlbumId)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Album.AlbumId)
</dd>
</dl>
<form method="post">
<input type="submit" value="Delete" asp-route-id="@Model.Album.AlbumId" class="btn btn-danger" /> |
<a asp-page="/Albums">Back to List</a>
</form>
</div>
delete.cshtml.cs
#region snippet_All
using Project.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace Project.Pages.Albums
{
public class DeleteModel : PageModel
{
private readonly Chinook _context;
public DeleteModel(Chinook context)
{
_context = context;
}
[BindProperty]
public Album Album { get; set; }
public string ErrorMessage { get; set; }
public async Task<IActionResult> OnGetAsync(string? id, bool? saveChangesError = false) //changed int -> string
{
if (id == null)
{
return NotFound();
}
//using string.split method to split the id parameter, and get the albumid.
var albumid = System.Convert.ToInt32(id.ToString().Split('.')[0]);
Album = await _context.Albums
.AsNoTracking()
.FirstOrDefaultAsync(m => m.AlbumId == albumid);
if (Album == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var album = await _context.Albums.FindAsync(id);
if (album == null)
{
return NotFound();
}
try
{
_context.Albums.Remove(album);
await _context.SaveChangesAsync();
return RedirectToPage("/Albums");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("/Albums/Delete",
new { id, saveChangesError = true });
}
}
}
}
#endregion
edit.cshtml
@page
@model Project.Pages.Albums.EditModel
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Album</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Album.AlbumId" />
<div class="form-group">
<label asp-for="Album.Title" class="control-label"></label>
<input asp-for="Album.Title" class="form-control" />
<span asp-validation-for="Album.Title" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="/Albums">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
edit.cshtml.cs
using Project.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
namespace Project.Pages.Albums
{
public class EditModel : PageModel
{
private readonly Chinook _context;
public EditModel(Chinook context)
{
_context = context;
}
[BindProperty]
public Album Album { get; set; }
public string ErrorMessage { get; set; }
#region snippet_OnGetPost
// public async Task<IActionResult> OnGetAsync(int? id)
// {
// if (id == null)
// {
// return NotFound();
// }
// Album = await _context.Albums.FindAsync(id);
// if (Album == null)
// {
// return NotFound();
// }
// return Page();
// }
public async Task<IActionResult> OnGetAsync(string? id, bool? saveChangesError = false) //changed int -> string
{
if (id == null)
{
return NotFound();
}
//using string.split method to split the id parameter, and get the albumid.
var albumid = System.Convert.ToInt32(id.ToString().Split('.')[0]);
Album = await _context.Albums
.AsNoTracking()
.FirstOrDefaultAsync(m => m.AlbumId == albumid);
if (Album == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
var albumToUpdate = await _context.Albums.FindAsync(id);
if (albumToUpdate == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Album>(
albumToUpdate,
"album",
s => s.Title))
{
await _context.SaveChangesAsync();
return RedirectToPage("/Albums");
}
return Page();
}
#endregion
private bool AlbumExists(int id)
{
return _context.Albums.Any(e => e.AlbumId == id);
}
}
}
My code build and runs OK, but when I go to edit, I get, for instance
This localhost page can’t be found No web page was found for the web address: https://localhost:5001/Albums/Edit/353.%20123.%20Test
I'm sure it should just be passing in the AlbumId
, as in
https://localhost:5001/Albums/Edit/353
...and when I manually change the URL to that, it works.
The delete function works from, for instance...
https://localhost:5001/Albums/Delete/353.%20123.%20Test2
...where '353' is the AlbumId
, '123' is the ArtistId
, and 'Test2' is the Title
.
...but that actually does work. So how can I just get both Delete and Edit to work from the AlbumId
? That is all that's needed, right? TIA for any help.
In my opinion your problem lies in the way you return the information from the Album.cshtml.cs. Instead of build an IEnumerable of strings return the IEnumerable of Album
public IEnumerable<Album> Albums { get; set; }
public void OnGetAsync()
{
ViewData["Title"] = "Chinook Web Site - Albums";
Albums = db.Albums;
}
Now, in the Album.cshtml file you can use the strongly typed Model to create the list of each album with links to delete and edit each row with the proper ID
<thead class="thead-inverse">
<tr>
<th>Album ID</th>
<th>artist ID</th>
<th>Album title</th>
<th></th><th></th>
</tr>
</thead>
<tbody>
@foreach (Album album in Model.Albums)
{
<tr>
<td>@album.AlbumID</td>
<td>@album.ArtistID</td>
<td>@album.Title</td>
<td><a asp-page="/Albums/Delete" asp-route-id="@album.AlbumID">Delete</a></td>
<td><a asp-page="/Albums/Edit" asp-route-id="@album.AlbumID">Edit title</a></td>
</tr>
}
</tbody>
Finally you can change back the Edit and Delete code to receive the integer passaed in tha asp-route-id
public async Task<IActionResult> OnGetAsync(int id, bool? saveChangesError = false)
{
Album = await _context.Albums
.AsNoTracking()
.FirstOrDefaultAsync(m => m.AlbumId == id);
if(Album == null)
.....
}