Search code examples
c#asp.netasp.net-corecrudrazor-pages

How to just pass in primary key values to my CRUD functions?


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:&nbsp;</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.


Solution

  • 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)
        .....
    }