Search code examples
c#asp.net-core-mvcasp.net-core-6.0model-validation

ASP.NET Core 6.0 form validation error: "The fieldNameHere field is required." even though no such field exists


I am creating an ASP.NET Core 6.0 web application.

My validation for my form is not working, and when I output the ModelState errors, I see this:

Key: Email, Error: The Email field is required.
Key: Password, Error: The Password field is required

These errors do not show up under any of the forms on my page.

Whilst writing this post I didn't change any of the code but for some reason

Key: Password, Error: The Password field is required.

is no longer showing up now which confuses me even more.

My edit view has all the necessary fields and no matter what I try I'm not able to get rid of the errors.

To give an easier visualisation, this is how my page looks like:

EditPageView

Below is all the relevant code:

@model Replay.ViewModels.ApplicationUserRoleViewModel

@{
    ViewData["Title"] = "Edit User";
}

<h1>Edit User</h1>

<form asp-controller="Account" asp-action="Edit" method="post">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="ApplicationUser.Email" class="control-label"></label>
        <input asp-for="ApplicationUser.Email" class="form-control" />
        <span asp-validation-for="ApplicationUser.Email" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="ApplicationUser.FullName" class="control-label"></label>
        <input asp-for="ApplicationUser.FullName" class="form-control" />
        <span asp-validation-for="ApplicationUser.FullName" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="ApplicationUser.Password" class="control-label"></label>
        <input asp-for="ApplicationUser.Password" class="form-control" />
        <span asp-validation-for="ApplicationUser.Password" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="ApplicationUser.Active" class="control-label"></label>
        <input asp-for="ApplicationUser.Active" type="checkbox" class="form-check-input" />
        <span asp-validation-for="ApplicationUser.Active" class="text-danger"></span>
    </div>

    <div class="form-group">
        <table>
            @for (int i = 0; i < Model.Roles.Count; i++)
            {
                <tr>
                    <td>
                        @Html.HiddenFor(m => m.Roles[i].Id)
                        @Html.HiddenFor(m => m.Roles[i].Name)
                        @Html.DisplayFor(m => m.Roles[i].Name)
                    </td>
                    <td>@Html.CheckBoxFor(m => m.Roles[i].Selected)</td>
                </tr>
            }
        </table>
    </div>

    <button type="submit" class="btn btn-primary">Save</button>
</form>

C# code:

using Replay.Models;

namespace Replay.ViewModels
{
    public class ApplicationUserRoleViewModel
    {
        public ApplicationUser ApplicationUser { get; set; }
        public List<RoleViewModel> Roles { get; set; }

        public ApplicationUserRoleViewModel()
        {
        }

        public ApplicationUserRoleViewModel(ApplicationUser applicationUser, List<Role> roles, List<int> userRoleIds)
        {
            ApplicationUser = applicationUser;

            Roles = roles.Select(r => new RoleViewModel
            {
                Id = r.Id,
                Name = r.Name,
                Selected = userRoleIds.Contains(r.Id)
            }).ToList();
        }
    }

    public class RoleViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool Selected { get; set; }
    }
}
using System.ComponentModel.DataAnnotations;

namespace Replay.Models;

public class ApplicationUser
{
    [Key]
    public int Id { get; set; }
    public string Email { get; set; }
    public string FullName { get; set; }
    public string Password { get; set; }
    public Boolean Active { get; set; }
}
using System.ComponentModel.DataAnnotations;

namespace Replay.Models
{
    public class Role
    {
        [Key]
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(ApplicationUserRoleViewModel viewModel)
{
    if (!ModelState.IsValid)
    {
        foreach (var key in ModelState.Keys)
        {
            var state = ModelState[key];

            foreach (var error in state.Errors)
            {
                System.Diagnostics.Debug.WriteLine($"Key: {key}, Error: {error.ErrorMessage}");
            }

            var roles = _dbContext.Role.ToList();

            var userRoleIds = _dbContext.UserRoles
                                        .Where(ur => ur.UserId == viewModel.ApplicationUser.Id)
                                        .Select(ur => ur.RoleId).ToList();

            viewModel.Roles = roles.Select(r => new RoleViewModel
            {
                Id = r.Id,
                Name = r.Name,
                Selected = userRoleIds.Contains(r.Id)
            }).ToList();

            return View(viewModel);
        }
    }

    var user = _dbContext.ApplicationUser.Find(viewModel.ApplicationUser.Id);

    if (user == null)
    {
        return NotFound();
    }

    user.Email = viewModel.ApplicationUser.Email;
    user.FullName = viewModel.ApplicationUser.FullName;
    user.Password = viewModel.ApplicationUser.Password;
    user.Active = viewModel.ApplicationUser.Active;

    var userRoles = _dbContext.UserRoles.Where(ur => ur.UserId == user.Id).ToList();
    _dbContext.UserRoles.RemoveRange(userRoles);

    var selectedRoleIds = viewModel.Roles
                                   .Where(r => r.Selected)
                                   .Select(r => r.Id).ToList();

    foreach (var roleId in selectedRoleIds)
    {
        _dbContext.UserRoles.Add(new UserRole { UserId = user.Id, RoleId = roleId });
    }

    _dbContext.SaveChanges();

    return RedirectToAction("Index");
}
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Replay.Models
{
    public class UserRole
    {
        [Key]
        public int Id { get; set; }
        [ForeignKey("ApplicationUser")]
        public int UserId { get; set; }
        [ForeignKey("Role")]
        public int RoleId { get; set; }
    }
}

What have I tried:

  • removing all the form groups (namely ApplicationUser.Email and ApplicationUser.Password) and still got the errors. Maybe good interesting to know is that when removing the ApplicationUser.Email and ApplicationUser.Password from my view, it specifically said that it is now missing ApplicationUser.Email, ApplicationUser.Password, Email and Password.
  • Adding @Html.HiddenFor(), which obviously wouldn't even compile since my view model doesn't even have an Email nor Password.
  • I have also tried adding a ? to the Email and Password in my ApplicationUser class.

What was I expecting:

I was expecting the model to be valid, and not have some kind of ghost inputs, then invalidate it.


Solution

  • Thanks to @Dai and @Md-Farid-Uddin-Kiron I found the answer. I had to change my ViewModel to not directly include an EF-Model. My ApplicationUserRoleViewModel changed to this and then it worked:

    using System.ComponentModel.DataAnnotations;
    using Replay.Models;
    
    namespace Replay.ViewModels
    {
        public class ApplicationUserRoleViewModel
        {
            public int Id { get; set; }
            [Required]
            public string Email { get; set; }
            [Required]
            public string FullName { get; set; }
            [Required]
            public string Password { get; set; }
            public bool Active { get; set; }
            public List<RoleViewModel> Roles { get; set; }
        }
    
        public class RoleViewModel
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public bool Selected { get; set; }
        }
    }