Search code examples
asp.net-coreentity-framework-core

Why is binding to a ViewModel Not Working here?


I have a ViewModel with two Models - UserSystemPermissions and SystemModel I want to pass two pieces of data from a view and bind it to the afforementioned ViewModel.

UserSystemPermissions.SystemID
SystemModel.SystemID

I can see the data coming over with Edge Network Tools in a browser window but the data is not binding to the ViewModel.

Here is the ViewModel, two models, controller, and view in question.

//UserSystemPermissionsViewModel
using NuGet.ProjectModel;
using SPLWeb.Models;
using SPLWeb.ViewModels;
using SPLWeb.Models.DB;

namespace SPLWeb.ViewModels
{
    public class UserSystemPermissionsViewModel
    {
        public UserSystemPermission? UserSystemPermission { get; set; }
        public SystemModel? SystemModel { get; set; }

    }
}

...and the two models

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace SPLWeb.Models.DB;

public partial class SystemModel
{
    [Key]
    public int SystemId { get; set; }
    public string? SystemName { get; set; }
    public string? Description { get; set; }
    public bool DisabledBit { get; set; }
    public string? ChangedBy { get; set; }
    public DateTime? ChangedDate { get; set; }
    public virtual ICollection<FacilitySystem> FacilitySystems { get; set; } = new List<FacilitySystem>();

    //public virtual ICollection<JobSystemPermission> JobSystemPermissions { get; set; } = new List<JobSystemPermission>();

    public virtual ICollection<SystemJob> SystemJobs { get; set; } = new List<SystemJob>();
    //public virtual ICollection<User> Users { get; set; } = new List<User>();
    public virtual ICollection<SystemUser>? SystemUsers { get; set; }
    //public virtual UserSystemPermission UserSystemPermission { get; set; }

    public virtual ICollection<SystemPermission> SystemPermissions { get; set; } = new List<SystemPermission>();
    //public virtual ICollection<UserSystemPermission> UserSystemPermissions { get; set; } = new List<UserSystemPermission>();
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace SPLWeb.Models.DB;

public partial class UserSystemPermission
{
    [Key]
    public int UserSystemPermissionId { get; set; }
    public int? UserId { get; set; }
    public int? SystemPermissionId { get; set; }
    public string? Notes { get; set; }
    public bool Active { get; set; }
    public string? AddedByUser { get; set; }
    public string? DeactivatedBy { get; set; }
    public string? ReactivatedBy { get; set; }
    public DateTime? LastModifiedDate { get; set; }
    public virtual SystemPermission? SystemPermission { get; set; }
    public virtual User? User { get; set; }
   // public virtual SystemModel? SystemModel { get; set; }
    //public virtual SystemUser? SystemUser { get; set; } 
}

View that is passing the data

@model SPLWeb.ViewModels.UserSystemPermissionsViewModel

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

<h1>Create</h1>

<h4>UserSystemPermission</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="PresentSystemPermissionSelect" >
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            
            <div class="form-group">
                <label for="UserSystemPermission.UserId" class="control-label"></label>
                <select for="UserSystemPermission.UserId" class ="form-control" required="required" asp-items="ViewBag.UserList"></select>
                @*<input asp-for="User.FullName" disabled class="form-control" />*@
            </div>
            <div class="form-group">
                <label for="SystemModel.SystemId" class="control-label"></label>
                <select for="SystemModel.SystemId" class ="form-control" required="required" asp-items="ViewBag.SystemList"></select>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

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

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

And, finally, the controll task that I am passing the data too (or trying to :(

public async Task<IActionResult> PresentSystemPermissionSelect([Bind("UserSystemPermission.UserId", "SystemModel.SystemId")] UserSystemPermissionsViewModel userSystemPermissionsViewModel)
{
    // pass a list of permissions for the selected systems to the next screen which will allow
    //    the user to select a permission

    var userSystemPermissionContext = _context.UserSystemPermissions
       .Include(u => u.SystemPermission)
       .Include(u => u.SystemPermission.SystemModel)
       .Include(u => u.User);

   
    // Get a list of permissions for the chosen system
    var systemPermisionList = (from s in _context.SystemPermissions
                               orderby s.Description
                               where s.SystemId == userSystemPermissionsViewModel.SystemModel.SystemId
                               select s).ToList();

    ViewData["UserList"] = new SelectList(_context.Users.OrderBy(s => s.NameLast).OrderBy(s => s.NameFirst), "UserId", "FullName", userSystemPermissionsViewModel.UserSystemPermission.UserId);
    ViewData["SystemList"] = new SelectList(_context.SystemModels.OrderBy(e => e.SystemName), "SystemId", "SystemName", userSystemPermissionsViewModel.SystemModel.SystemId);
    ViewData["SystemPermissionList"] = new SelectList(_context.SystemPermissions.OrderBy(e => e.Description), "SystemPermissionId", "Description");
    return View("SystemPermissionSelect", systemPermisionList);
}

I have tried "newing up" the ViewModel before using it, in which case, I have a ViewModel with two models but no data. If I don't create a ViewModel first I simply have two NULL models inside the ViewModel after the binding in the ActionResult header.

I have scoured the Internet - including SO and cannot seem to get this working.

I considered not using the ASP tag helpers and just passing in two pieces of data to the controller but I prefer to manage the lists with <select asp-for ...> tag because I've had much better results than just @Html.DropDown...

As mentioned earlier, I can see the data coming across the network in Edge Network Tools but the data is simply not binding.


Solution

  • I considered not using the ASP tag helpers and just passing in two pieces of data to the controller but I prefer to manage the lists with <select asp-for ...> tag

    If you want to pass two pieces of data to the controller and with <select asp-for ...> tag

    Try to remove [Bind("UserSystemPermission.UserId", "SystemModel.SystemId")] and make some change as below :

           [HttpPost]
            public async Task<IActionResult> PresentSystemPermissionSelect(int? UserId, int? SystemId)
            {
                 ...//do your staff
                return View();
            }
    

    In Create.cshtml , use name attribute to bind:

    <select name="UserId" class="form-control" required="required" asp-items="ViewBag.UserList"></select>
     <select name="SystemId" class="form-control" required="required" asp-items="ViewBag.SystemList"></select>
    

    result: enter image description here