1

Is there any way I can have multiple drop-down menu displaying the data from the same database entity on the same razor page without creating several classes with new IDs (e.g. ResourceID; ResourceID1; ResourceID2)

I am able to display the drop down with the appropriate data from the MS SQL database in the 'Create.cshtml' and 'Edit.cshtml' razor pages, but the chosen data when saved displays the ID of the chosen resource instead of the name of the resource in the 'Index.cshtml', 'Detail.cshtml' and 'Delete.cshtml' views.

The Resource model:

namespace ProjectReporting.Models
{
    public class Resource
    {
        public int ID { get; set; }

        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [Display(Name = "Last Name")]
        public string LastName { get; set; }

        [Display(Name = "Long Name")]
        public string LongName { get; set; }

        [DefaultValue(true)]
        [Display(Name = "Active")]
        public bool IsActive { get; set; } = true;

        [Display(Name = "Is Manager")]
        public bool IsManager { get; set; }

        [Display(Name = "Is Forecast Owner")]
        public bool IsForecastOwner { get; set; }

        public ICollection<Project> Projects { get; set; }

    }
}

The Project model:

namespace ProjectReporting.Models
{
    public class Project
    {
        public int ID { get; set; }

        [Display(Name = "ID")]
        public int PID { get; set; }

        [Display(Name = "Project Name")]
        public string ProjectName { get; set; }

        [Display(Name = "Forecast Owner")]
        public int ResourceID { get; set; }
        public Resource Resource { get; set; }

        [Display(Name = "DSM")]
        public int? ResourceID1 { get; set; }

        public ICollection<ProjectComment> ProjectComments { get; set; }

    }
}

The Create.cshtml page:

@page
@model ProjectReporting.Pages.Projects.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Project</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Project.PID" class="control-label"></label>
                <input asp-for="Project.PID" class="form-control" />
                <span asp-validation-for="Project.PID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Project.ProjectName" class="control-label"></label>
                <input asp-for="Project.ProjectName" class="form-control" />
                <span asp-validation-for="Project.ProjectName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Project.ResourceID" class="control-label"></label>
                <select asp-for="Project.ResourceID" class="form-control" asp-items="ViewBag.ResourceID"><option value="" default="" selected="">-- Select --</option></select>
            </div>
            <div class="form-group">
                <label asp-for="Project.ResourceID1" class="control-label"></label>
                <select asp-for="Project.ResourceID1" class="form-control" asp-items="ViewBag.ResourceID1"><option value="" default="" selected="">-- Select --</option></select>
            </div>
            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="Project.IsArchived" /> @Html.DisplayNameFor(model => model.Project.IsArchived)
                </label>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

The Create.cshtml page:

namespace ProjectReporting.Pages.Projects
{
    public class CreateModel : PageModel
    {
        private readonly ProjectReporting.Data.ApplicationDbContext _context;

        public CreateModel(ProjectReporting.Data.ApplicationDbContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            ViewData["OrganisationID"] = new             SelectList(_context.ProjectType.Where(a => a.IsActive == true), "ID", "TypeName");
            ViewData["ResourceID"] = new SelectList(_context.Resource.Where(a => a.IsActive & a.IsForecastOwner == true), "ID", "LongName");
            ViewData["ResourceID1"] = new SelectList(_context.Resource.Where(a => a.IsActive == true), "ID", "LongName");
            return Page();
        }

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

        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.
        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Project.Add(Project);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

The Index.cshtml page:

@page
@model ProjectReporting.Pages.Projects.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Project[0].PID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Project[0].Organisation)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Project[0].ProjectName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Project[0].Resource)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Project[0].ResourceID1)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Project[0].IsArchived)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model.Project) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.PID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Organisation.OrgName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ProjectName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Resource.LongName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => c)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.IsArchived)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

The item.Resource.LongName works fine for the first resource, but I would like the same to happen with the item.Resource.LongName.

The Index.cshtml.cs

namespace ProjectReporting.Pages.Projects
{
    public class IndexModel : PageModel
    {
        private readonly ProjectReporting.Data.ApplicationDbContext _context;

        public IndexModel(ProjectReporting.Data.ApplicationDbContext context)
        {
            _context = context;
        }

        public IList<Project> Project { get;set; }

        public async Task OnGetAsync()
        {
            Project = await _context.Project
                .Include(p => p.Resource).ToListAsync();
        }
    }
}

I have set the FK in the migration file to create the DB to be able to retrieve the data and would like to avoid having to create one class file by resource.

table.ForeignKey(
     name: "FK_Project_Resource_ResourceID",
     column: x => x.ResourceID,
     principalTable: "Resource",
     principalColumn: "ID",
     onDelete: ReferentialAction.Restrict);
table.ForeignKey(
     name: "FK_Project_Resource_ResourceID1",
     column: x => x.ResourceID1,
     principalTable: "Resource",
     principalColumn: "ID",
     onDelete: ReferentialAction.Restrict);

The result shows the right data in the drop-down and the correct Resource ID selected when saved. However, the index, details and delete page only display the ResourceID instead of the LongName. If I use Resource.LongName for the second ResourceID1, it rightly displays the same LongName than for ResourceID.

How can I have multiple resource drop-down on the page that point to the same entity and display the LongName on the Index, Detail and Delete pages?

Twenty
  • 5,234
  • 4
  • 32
  • 67
Bernard
  • 49
  • 7
  • You could not add two foreign keys on the same entity for different resource.Besides,what is your relationships between Resource and Project, one-to-many or many-to-many? – Ryan Oct 28 '19 at 06:14
  • Hello Xing, I have already added those foreign keys as you can see from the last bit of code. The relationship is many-to-many as I can have several projects allocated to the same resource (e.g. as the forecast owner and the DSM) and the projects have multiple resources. Am I missing foreign keys in one of the tables? – Bernard Oct 28 '19 at 06:56
  • many-to-many is not configured like yours,refer to [here](https://learn.microsoft.com/en-us/ef/core/modeling/relationships#many-to-many) – Ryan Oct 28 '19 at 06:59
  • I created a new project with 3 models: Project, Resource, ProjectResource with the data provided under the link. I used Project instead of Post and Resource instead of Tag and ProjectResource instead of PostTag. I get the error mentioned above. – Bernard Oct 28 '19 at 15:28

1 Answers1

0

It is not clear about your relationships between Resource and Project, and you could not use both ResourceID and ResourceID1 for a Resource property for restoring different resources.

For many-to-many relationships, you could refer to

https://learn.microsoft.com/en-us/ef/core/modeling/relationships#many-to-many

A workaround is that you just get all resources in handler and retrieve it in view:

Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ProjectReporting.Data.ApplicationDbContext _context;

    public IndexModel(ProjectReporting.Data.ApplicationDbContext context)
    {
        _context = context;
    }

    public IList<Project> Project { get;set; }
    public IList<Resource> Resources { get; set; }

    public async Task OnGetAsync()
    {
        Resources = await _context.Resource.ToListAsync();
        Project = await _context.Projects
            .Include(p => p.Resource).ToListAsync();
    }
}

Index.cshtml:

<td>
    @Html.DisplayFor(modelItem => item.Resource.LongName)
</td>
<td>
    @{ 
        var resource = Model.Resources.FirstOrDefault(r => r.ID == item.ResourceID1);
    }
    @resource.LongName
</td>
Ryan
  • 19,118
  • 10
  • 37
  • 53
  • Hello Xing, I am pretty new to coding, so apologize for rookies errors or misunderstanding. I have tried to replicate the many-to-many scenario provided by Microsoft and have the following error: Severity Code Description Project File Line Suppression State Error CS1061 'Resource' does not contain a definition for 'ProjectResource' and no accessible extension method 'ProjectResource' accepting a first argument of type 'Resource' could be found (are you missing a using directive or an assembly reference?) ManyToMany C:\VisualStudio2019Data\ManyToMany\Data\ManyToManyContext.cs 31 Active – Bernard Oct 28 '19 at 15:04
  • I used 'Project' instead of 'Post' and 'Resource' instead 'Tag' – Bernard Oct 28 '19 at 15:11
  • I have tested your workaround and it works for what I want to do. I will dig further into the many-to-many relationship issue. Thank you. – Bernard Oct 28 '19 at 15:47