Search code examples
asp.netasp.net-mvcasp.net-corerazorrazor-pages

Trying to write CRUD fucntions for ASP .NET Core razor pages. Why doesn't my delete function work?


GitHub

Delete.cshtml.cs

#region snippet_All
using Project.Models; //using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace Project.Pages.Albums //ContosoUniversity.Pages.Students
{
    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(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Album = await _context.Albums
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.AlbumId == id);

            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("./Index");
            }
            catch (DbUpdateException /* ex */)
            {
                //Log the error (uncomment ex variable name and write a log.)
                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}
#endregion

Delete.cshtml

@page
@model Project.Pages.Albums.DeleteModel

@{
    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="Delete">
        <input type="hidden" asp-for="AlbumId" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Album.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="./Delete" asp-route-id="@album">Delete</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>
        @* <p>Enter a value and Album ID for a new album title:&nbsp;</p>
    <form method="POST">
        <div><input asp-for="Album.Title" /></div>
        <div><input asp-for="Album.AlbumId" /></div>
        <input type="submit" />
    </form> *@
</div>

Album.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.Title);
            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();
        }

        // void RemoveAlbum(int albumId)
        // {
        //     var album = db.Albums.Find(albumId);
        //     if (album == null)
        //         throw new ArgumentOutOfRangeException();
        //     db.Albums.Remove(album);
        //     db.SaveChanges();
        // }

        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");
        }
    }
}

My project builds and runs fine, but the Delete button does nothing but refresh the page. I've tried reloading the page, but that makes no odds. I'm not convinced <td><a asp-page="./Delete" asp-route-id="@album">Delete</a></td> is right, but when I try to plug the AlbumId in there instead of just album, I always get errors. Any help please? TIA.


Solution

  • According to your code and description, it looks that in the Album.cshtml page, you will list the albums, and if click the "delete" hyperlink, it will redirect to the Delete.cshtml page and show the selected album, after that, click the "Delete" button to delete the item and redirect to the Index page. If that is the case, please refer the following steps to modify your code.

    First, in the Album.cshtml page:

    Since the Delete.cshtml page located in the "Albums" folder, when you add the hyperlink (<a> tag), the asp-page attribute should like this: asp-page="/Albums/Delete".

    The <a> tag like this: <a asp-page="/Albums/Delete" asp-route-id="@album">Delete</a>.

    Second, in the Delete.cshtml.cs file:

    If you check the Album.cshtml.cs file or use F12 developer tool to check the parameter, you can see the id is a string value, instead of int type. So, change the code as below:

        public async Task<IActionResult> OnGetAsync(string? id, bool? saveChangesError = false)
        {
    
            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();
        }
    

    and in the Delete.cshtml page, you should also add the tag helper reference at the head of the page, and use asp-route-id attribute to pass the albumid to the Post method, code like this:

    @page  "{id?}"
    @model Project.Pages.Albums.DeleteModel
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    
    <div>
        
        <form method="post">
            @*<input type="hidden" asp-for="Album.AlbumId" />*@
            <input type="submit" value="Delete" asp-route-id="@Model.Album.AlbumId" class="btn btn-danger" /> |
            <a asp-page="/Index">Back to List</a>
        </form>
    </div>
    

    After modified, the result as below (After test your sample on my machine, I have deleted them):

    enter image description here

    Here are some references about Asp.net Core Razor Page routing and event handler, you could refer them:

    Razor Pages in ASP.NET Core

    Razor Pages Routing

    Handler Methods in Razor Pages

    The detail updated page resource as below:

    album.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>
                    </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>
            @* <p>Enter a value and Album ID for a new album title:&nbsp;</p>
        <form method="POST">
            <div><input asp-for="Album.Title" /></div>
            <div><input asp-for="Album.AlbumId" /></div>
            <input type="submit" />
        </form> *@
    </div>
    

    album.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.Title);
                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();
            }
    
            // void RemoveAlbum(int albumId)
            // {
            //     var album = db.Albums.Find(albumId);
            //     if (album == null)
            //         throw new ArgumentOutOfRangeException();
            //     db.Albums.Remove(album);
            //     db.SaveChanges();
            // }
    
            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="hidden" asp-for="Album.AlbumId" />*@
            <input type="submit" value="Delete" asp-route-id="@Model.Album.AlbumId" class="btn btn-danger" /> |
            <a asp-page="/Index">Back to List</a>
        </form>
    </div>
    

    Delete.cshtml.cs:

    #region snippet_All
    using Project.Models; //using ContosoUniversity.Models;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using Microsoft.EntityFrameworkCore;
    using System.Threading.Tasks;
    
    namespace Project.Pages.Albums //ContosoUniversity.Pages.Students
    {
        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)
            {
    
                if (id == null)
                {
                    return NotFound();
                }
    
                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("/Index");
                }
                catch (DbUpdateException /* ex */)
                {
                    //Log the error (uncomment ex variable name and write a log.)
                    return RedirectToAction("/Albums/Delete",
                                         new { id, saveChangesError = true });
                }
            }
        }
    }
    #endregion