'Link not rendering when using asp-page TagHelper

I have one page that does not render a link in HTML when using the asp-page TagHelper. I have seen this before, but it was due to a typo or the page not existing.

Of the two links in the _Layout below,

  • Users renders as http://localhost/ReportGroups/Admin/Users
  • Roles renders as http://localhost/ReportGroups
  • Navigating to Roles manually results in a 404 error.

_Layout.cshtml

The link is part of an Admin menu. The commented out FontAwesome icons are part of my testing. /Admin/Users works fine, but /Admin/RoleList does not work. It was previously /Admin/Roles but I built a new copy of the file up in parts to see if anything was throwing an error, or if it was a reserved word.

@if (User.Identity.IsAuthenticated)
{
    if (User.IsInRole("Admin"))
    {
        <div id="nav-admin">
            <ul>
                <li><a asp-page="/Admin/Users"> Users</a></li>@*< i class="fas fa-user-secret" title="Users"></i>*@
                <li><a asp-page="/Admin/RoleList"> Roles</a></li>
                @*<i class="fas fa-id-card" title="Roles"></i>*@
            </ul>
        </div>
    }
}

RoleList.cshtml

@page "{roleId}/{userId}"
@model RoleListModel
@{
    ViewData["PageTitle"] = "Admin Tools - Roles";
    ViewData["IconClass"] = "";
    ViewData["IconTitle"] = "Roles";
}

<table>
    <thead>
        <tr>
            <th>Role</th>
            <th>User Name</th>
            <th>First Name</th>
            <th>Last Name</th>
            <th>&nbsp;</th>
        </tr>
    </thead>
    <tbody>
        @foreach (ApplicationRole role in Model.AppRoles)
        {

            <tr>
                <td colspan="4"><strong>@role.Name</strong></td>
                <td>
                    <a asp-page="/App/RoleEdit" asp-route-roleId="@role.Id"><i class="fas fa-edit"></i></a>
                </td>
            </tr>

            IList<ApplicationUser> usersOfRole = Model.AppRoleUsersDict[role];
            foreach (ApplicationUser user in usersOfRole)
            {
                <tr>
                    <td>@role.Name</td>
                    <td>@user.UserName</td>
                    <td>@user.FirstName</td>
                    <td>@user.LastName</td>
                    <td>
                        <form method="post" asp-page-handler="delete">
                            <button type="submit" asp-page="/App/GroupEdit" asp-page-handler="delete" asp-route-roleId="@role.Id" asp-route-userId="@user.Id">
                                <i class="fas fa-trash-alt" style="color:red"></i>
                            </button>
                        </form>
                    </td>
                </tr>
            }
        }

    </tbody>
</table>

RoleList.cshtml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ctrack.ReportGroups.Data;
using Ctrack.ReportGroups.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Ctrack.ReportGroups.Pages
{
    public class RoleListModel : PageModel
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly RoleManager<ApplicationRole> _roleManager;
        private readonly ApplicationDbContext _db;

        public RoleListModel(
            ApplicationDbContext db,
            UserManager<ApplicationUser> userManager,
            RoleManager<ApplicationRole> roleManager)
        {
            _db = db;
            _userManager = userManager;
            _roleManager = roleManager;
        }

        public IList<ApplicationRole> AppRoles { get; set; }

        public IDictionary<ApplicationRole, IList<ApplicationUser>> AppRoleUsersDict { get; set; }

        /// <summary>
        /// List all Roles, and the Users assigned to them.  Exposes a form to delete the relationship for each user.
        /// </summary>
        /// <returns>Task&gt;IActionResult&gt;</returns>
        public async Task<IActionResult> OnGetAsync()
        {

            AppRoles = _roleManager.Roles.ToList();
            AppRoleUsersDict = new Dictionary<ApplicationRole, IList<ApplicationUser>>();

            foreach (ApplicationRole role in AppRoles)
            {
                IList<ApplicationUser> usersOfRole = await _userManager.GetUsersInRoleAsync(role.Name);
                AppRoleUsersDict.Add(role, usersOfRole);
            }

            return Page();
        }

        /// <summary>
        /// Removes a role from a User
        /// </summary>
        /// <param name="roleId"><see langword="string"/> containing RoleId</param>
        /// <param name="userId"><see langword="string"/> containing UserId</param>
        /// <returns>Task&gt;IActionResult&gt;</returns>
        public async Task<IActionResult> OnPostDeleteAsync(string roleId, string userId)
        {
            ApplicationUser user = await _userManager.FindByIdAsync(userId);
            ApplicationRole role = await _roleManager.FindByIdAsync(roleId);

            try
            {
                await _userManager.RemoveFromRoleAsync(user, role.Name);
                TempData["Notification"] = $"Successfully removed Role {role.Name} from User {user.UserName}.";
            }
            catch (Exception ex)
            {
                TempData["ErrorMessage"] = $"Exception {ex.GetType()} encountered while removing Role {role.Name} from User {user.UserName}.";
            }
            return Page();
        }
    }
}

_ViewImports.cshtml

@using Microsoft.AspNetCore.Identity
@using CtrackReportGroupsRtca
@using Ctrack.ReportGroups.Data
@using Ctrack.ReportGroups.Identity
@using System.Security.Claims
@using Microsoft.AspNetCore.Html;
@namespace Ctrack.ReportGroups.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


Solution 1:[1]

The reason that it does not work is that you have not defined the roleId and userId values. Change your link to be something like this:

<a asp-page="/Admin/RoleList" asp-route-roleId="YourRoleID" asp-route-userId="YourUserID"> Roles</a>

Solution 2:[2]

I'm using .NET Core 2.2, and had a similar problem. I fixed it by using

asp-page="/ThePage/Index"

instead of

asp-page="/ThePage"

This might be cloaking another root issue though.

3rd party edit

As John pointed out aspnetcore/issues/7656 contains

In asp.net core Razor Pages, the asp-page anchor tag helper does not create the 'href' if we don't include the ".../Index" for default (index) pages inside sub folders.

and

This is by design. The argument to asp-page is a page name not a URL Path.

Solution 3:[3]

In my case, I forgot to include the @page directive at the top of my page:

@page

<h1>Hello, world!</h1>

Solution 4:[4]

Include this code in your current _ViewImports.cshtml, Solve for me!

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 pitaridis
Solution 2 surfmuggle
Solution 3 Josh Noe
Solution 4 this.hart