Home > Net >  How to get ModelState to Validate Data Submitted Through a View Model With Foreign Keys?
How to get ModelState to Validate Data Submitted Through a View Model With Foreign Keys?

Time:03-22

Note: Please ignore the fact that the passwords aren't stored securely; this project is just for my own learning purposes, and I will address this issue in the future.

I have three models, Account, AccountType, and Person:

public class Account
{
    [Key]
    public int AccountID { get; set; }

    [ForeignKey("AccountType")]
    public int AccountTypeID { get; set; }

    [ForeignKey("Person")]
    public int PersonID { get; set; }

    [Required]
    public string Username  { get; set; }

    [Required]
    public string Password { get; set; }

    [Required]
    public DateOnly DateCreated { get; set; } = DateOnly.FromDateTime(DateTime.Now);

    [Required]
    public DateOnly DateModified { get; set; } = DateOnly.FromDateTime(DateTime.Now);

    public virtual AccountType AccountType { get; set; }
    public virtual Person Person { get; set; }
}

public class AccountType
{
    [Key]
    public int AccountTypeID { get; set; }

    [Required]
    public string AccountTypeName { get; set; }
}


public class Person
{
    [Key]
    public int PersonID { get; set; }
    
    [Required]
    [DisplayName("First Name")]
    public string FirstName { get; set; }

    [Required(AllowEmptyStrings = true)]
    [DisplayFormat(ConvertEmptyStringToNull = false)]
    [DisplayName("Middle Name")]
    public string MiddleName { get; set; }
    
    [Required]
    [DisplayName("Last Name")]
    public string LastName { get; set; }
    
    [Required]
    [DisplayName("Email")]
    public string Email { get; set; }

    [Required(AllowEmptyStrings = true)]
    [DisplayFormat(ConvertEmptyStringToNull = false)]
    [DisplayName("Phone")]
    public string Phone { get; set; }
    
    [Required]
    public DateOnly CreatedDate { get; set; } = DateOnly.FromDateTime(DateTime.Now);
    
    [Required]
    public DateOnly ModifiedDate { get; set; } = DateOnly.FromDateTime(DateTime.Now);
}

Since I want to use all three of these models in one view, I created a Register view model for them:

public class Register
{
    public List<SelectListItem> AccountTypes { get; set; }
    public Account Account { get; set; }
    public Person Person { get; set; }
}

Here is the relevant controller's GET action method, which serves the view:

public IActionResult Register()
{
    var Register = new Register();
    Register.AccountTypes = _db.AccountTypes.Select(accType => new SelectListItem
    {
        Value = accType.AccountTypeID.ToString(),
        Text = accType.AccountTypeName
    }).ToList();
    return View(Register);
}

And here is the Register view itself:

@model Register

<form method="post">
    <div >
        <div >
            <select asp-for="Account.AccountTypeID" 
                    asp-items="Model.AccountTypes" 
                     
                    aria-label="Default select example"
            ></select>
        </div>
    </div>
    <div >
        <div >
            <label asp-for="Account.Username" ></label>
            <input asp-for="Account.Username"  />
            <span asp-validation-for="Account.Username" ></span>
        </div>
        <div >
            <label asp-for="Account.Password" ></label>
            <input asp-for="Account.Password"  />
            <span asp-validation-for="Account.Password" ></span>
        </div>
    </div>

    <partial name="/Views/Person/_Create.cshtml" for="Person" />

    <button type="submit" >Create</button>
</form>

I'm now at the stage of coding the controller's POST action method, with the goal of submitting the bound fields from the view into a database, and I'm running into some trouble:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Register(Register Register)
{
    if (ModelState.IsValid)
    {
        _db.Persons.Add(Register.Person);
        _db.Accounts.Add(Register.Account);
        _db.SaveChanges();
    }
    return View(Register);
}

When I place a marker at the ModelState.IsValid line, I see that it's false. I understand that it's because 3 model fields have null / invalid values:

  1. AccountTypes
  2. Account.AccountType
  3. Account.Person

I need AccountTypes in the Register model to display the list of accounts types in the view's dropdown / select field. Then, Account.AccountType and Account.Person are also necessary since they establish the relationships between the tables for Entity. So, I guess that I can't remove these fields from the model.

I've considered somehow excluding these particular fields from the validation process while keeping them in the model, but I'm not exactly sure how to go about that. Moreoever, I've got a nagging feeling that there may be a much better way of handling this entire process that I'm unaware of.

So, what's the most proper way of getting ModelState to accept the POST register request?

CodePudding user response:

In your Person Model, there are a few fields marked as required properties. Due to this ModelState is always false, because those fields required values and view or controller(GET call) does not provide them.If posibble you can remove the requied fields or create a separate model for person without required fields.

enter image description here

Then to get selected AccountType, you need to change your Register model like this.

public class Register
    {
        public Register()
        {
            Person = new Person();
            Account = new Account();
        }
        public int AccountTypeId { get; set; }
        public List<SelectListItem> AccountTypes { get; set; }
        public Account Account { get; set; }
        public Person Person { get; set; }
    }

and change select in View like this.

  <select asp-for="AccountTypeId" 
                    asp-items="Model.AccountTypes" 
                     
                    aria-label="Default select example"
            ></select>

CodePudding user response:

Apparently you are using net6. You have two choices, make this properties nullable (and maybe some more)

 public virtual AccountType? AccountType { get; set; }
 public virtual Person? Person { get; set; }

or to prevent this in the future for another classes you can remove Nullable from project config

<PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <!--<Nullable>enable</Nullable>-->
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  • Related