Search code examples
c#asp.netentity-frameworkasp.net-corerazor-pages

Trying to join table on foreign key, but output is incorrect. CRUD also not fully working


Here is my code:

Tracks.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Project.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Project.Pages
{
    public class TracksModel : PageModel
    {
        private Chinook db;

        public TracksModel(Chinook injectedContext)
        {
            db = injectedContext;
        }
        public IEnumerable<Track> Tracks { get; set; }
        public void OnGetAsync()
        {
            ViewData["Title"] = "Chinook Web Site - Tracks";
            Tracks = db.Tracks.Include(a => a.Album)
            .Include(a => a.Artist);
        }
        [BindProperty]
        public Track Track { get; set; }

        public IActionResult OnPost()
        {
            if (ModelState.IsValid)
            {
                db.Tracks.Add(Track);
                db.SaveChanges();
                return RedirectToPage("/tracks");
            }
            return Page();
        }

        public IActionResult DeleteTrack(int TrackId)
        {
            var track = db.Tracks.Find(TrackId);

            if (track == null) return Page();

            db.Tracks.Remove(track); db.SaveChanges(); return RedirectToPage("/tracks");
        }
    }
}

Tracks.cshtml

@page
@using Project.Models
@model Project.Pages.TracksModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<div class="row">
    <h1 class="display-2">Tracks</h1>
    <table class="table">
        <thead class="thead-inverse">
            <tr>
                <th>Artist name</th>
                <th>Album name</th>
                <th>Track ID</th>
                <th>Track name</th>
            </tr>
        </thead>

        <tbody>
            @foreach (Track track in Model.Tracks)
            {
                <tr>
                    <td>@Html.DisplayFor(modelArtists => track.Artist.Name)</td>
                    <td>@Html.DisplayFor(modelAlbums => track.Album.Title)</td>
                    <td>@track.TrackId</td>
                    <td>@track.Name</td>
                    <td><a asp-page="/Tracks/Details" asp-route-id="@track.TrackId">Details</a></td>
                    <td><a asp-page="/Tracks/Edit" asp-route-id="@track.TrackId">Edit</a></td>
                    <td><a asp-page="/Tracks/Delete" asp-route-id="@track.TrackId">Delete</a></td>
                </tr>
            }
        </tbody>
    </table>
</div>
<div class="row">
    <p>Enter a name & TrackID for a new track:&nbsp;</p>
    <form method="POST">
        <div><input asp-for="Track.Name" /></div>
        <div><input asp-for="Track.TrackId" /></div>
        <input type="submit" />
    </form>
</div>
<div>
    <a asp-page="/Index">Home</a>
</div>

AlbumTracks.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Project.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Project.Pages
{
    public class AlbumTracksModel : PageModel
    {
        private Chinook db;

        public AlbumTracksModel(Chinook injectedContext)
        {
            db = injectedContext;
        }
        public IEnumerable<Track> Tracks { get; set; }
        public void OnGetAsync(int id)
        {
            ViewData["Title"] = "Chinook Web Site - Tracks";
            Tracks = db.Tracks.Include(a => a.Album).Where(a => a.AlbumId == id);
        }

        [BindProperty]
        public Track Track { get; set; }

        public IActionResult OnPost(int id)
        {
            if (ModelState.IsValid)
            {
                db.Tracks.Add(Track);
                db.SaveChanges();
                return RedirectToPage("/Albums/AlbumTracks?id=" + id);
            }
            return Page();
        }

        public IActionResult DeleteTrack(int TrackId, int id)
        {
            var track = db.Tracks.Find(TrackId);

            if (track == null) return Page();

            db.Tracks.Remove(track);
            db.SaveChanges();
            return RedirectToPage("/Albums/AlbumTracks?id=" + id);
        }
    }
}

AlbumTracks.cshtml

@page
@using Project.Models
@model Project.Pages.AlbumTracksModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<div class="row">
    <h1 class="display-2">Tracks</h1>
    <table class="table">
        <thead class="thead-inverse">
            <tr>
                <th>Track ID</th>
                <th>Track name</th>
            </tr>
        </thead>

        <tbody>
            @foreach (Track track in Model.Tracks)
            {
                <tr>
                    <td>@track.TrackId</td>
                    <td>@track.Name</td>
                </tr>
            }
        </tbody>
    </table>
</div>
<div class="row">
    <p>Enter a name & ID for a new track:&nbsp;</p>
    <form method="POST">
        <div><input asp-for="Track.Name" /></div>
        <div><input asp-for="Track.TrackId" /></div>
        <input type="submit" />
    </form>
</div>
<div>
    <a asp-page="/Index">Home</a>
</div>

I should explain that ArtistId is a foreign key of the Albums table, and AlbumId is a foreign key of the Tracks table.

The artist data is coming through to /Tracks, but there's something wrong, as all of the output are artists beginning with the letter A. So it seems like each artist name is being repeated many more times than it should?

There is another problem with my code, as I noticed that when I try to add a track from the /AlbumTracks page I have also created, that I get a 'page not found' error for the redirect page, even though the URL appears to be valid. The tracks are added though.

Thanks in advance for any pointers.

GitHub repo

Edit: I've decided to forget about adding a track from /Tracks. I think it makes a lot more sense to just add them from /AlbumTracks instead.

Edit2: the redirection issue is sorted now. I needed to do

        return RedirectToPage("/Albums/AlbumTracks", new {id = id });

So now I need to able to add tracks as part of albums next.

Edit3: I've actually got it working like this:

    <div><input asp-for="Track.Name" /></div>
    <div><input asp-for="Track.TrackId" /></div>
    <div><input asp-for="Track.AlbumId" /></div>

It's just obviously not ideal to make the user manually enter the AlbumId of the Album they want to add a track to. It would be better to have the system take the AlbumId directly from the URL, as in

 https://localhost:5001/Albums/AlbumTracks?id=361

Solution

  • The first way:

    When you get into the Albums/AlbumTracks?id=361 page, you could see the tracks list which AlbumId equals to 361. If you post the form in this page, the request url is still Albums/AlbumTracks?id=361. So the id you get in post handler is the AlbumId you want. Change your AlbumTracks.cshtml.cs like below:

    public class AlbumTracksModel : PageModel
    {
        //...
        public IEnumerable<Track> Tracks { get; set; }
        [BindProperty]
        public Track Track { get; set; }
    
        public IActionResult OnPost(int id)
        {
            Track.AlbumId = id;  //set the albumid by the request url...
            if (ModelState.IsValid)
            {
                db.Tracks.Add(Track);
                db.SaveChanges();
                return RedirectToPage("/Albums/AlbumTracks", new { id = id });
            }
            return RedirectToPage("/Albums/AlbumTracks", new { id = id });
        }
    

    Another way is that you could set an input with the AlbumId when the page load. The AlbumTracks.cshtml rendered the Tracks for specific AlbumId and they all own the same AlbumId. You could just read the AlbumId like below in AlbumTracks.cshtml:

    @page
    @using Project.Models
    @model Project.Pages.AlbumTracksModel
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers    
    <div class="row">
        <h1 class="display-2">Tracks</h1>
        <table class="table">
            //....
        </table>
    </div>
     <div class="row">
        <p>Enter a name & TrackID for a new track:&nbsp;</p>
        <form method="POST">
            <div><input asp-for="Track.Name" /></div>
            <div><input asp-for="Track.TrackId" /></div>
    
            //add this....
            <div><input asp-for="Tracks.ElementAt(0).AlbumId" name="Track.AlbumId" /></div>
            <input type="submit" />
        </form>
    </div>
    

    Both of the two ways you could get the AlbumId.