如何让 ModelState 验证通过带有外键的视图模型提交的数据?

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

注意:请忽略密码未安全存储的事实;本项目仅供自己学习,以后会解决这个问题

我有三个模型,Account、AccountType 和 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);
}

因为我想在一个视图中使用所有这三个模型,所以我为它们创建了一个 Register 视图模型:

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

这是服务于视图的相关控制器的 GET 操作方法:

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);
}

这里是注册视图本身:

@model Register

<form method="post">
    <div class="row mb-2">
        <div class="col-12">
            <select asp-for="Account.AccountTypeID" 
                    asp-items="Model.AccountTypes" 
                    class="form-select" 
                    aria-label="Default select example"
            ></select>
        </div>
    </div>
    <div class="row mb-2">
        <div class="col-12">
            <label asp-for="Account.Username" class="d-block"></label>
            <input asp-for="Account.Username" class="w-100" />
            <span asp-validation-for="Account.Username" class="text-danger"></span>
        </div>
        <div class="col-12">
            <label asp-for="Account.Password" class="d-block"></label>
            <input asp-for="Account.Password" class="w-100" />
            <span asp-validation-for="Account.Password" class="text-danger"></span>
        </div>
    </div>

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

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

我现在正处于编写控制器 POST 操作方法的阶段,目的是将视图中的绑定字段提交到数据库中,我正在 运行 进行一些麻烦:

[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);
}

当我在 ModelState.IsValid 行放置一个标记时,我看到它是 false。我了解这是因为 3 个模型字段具有空值/无效值:

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

我需要在 Register 模型中 AccountTypes 以在视图的下拉列表/select 字段中显示帐户类型列表。然后,Account.AccountTypeAccount.Person 也是必需的,因为它们建立了实体表之间的关系。所以,我想我无法从模型中删除这些字段。

我考虑过以某种方式将这些特定字段从验证过程中排除,同时将它们保留在模型中,但我不确定如何去做。此外,我有一种挥之不去的感觉,可能有更好的方法来处理我不知道的整个过程。

那么,让 ModelState 接受 POST 注册请求的最正确方法是什么?

在您的个人模型中,有几个字段标记为必填属性。由于此 ModelState 始终为 false,因为这些字段需要值并且视图或控制器(GET 调用)不提供 them.If 可能您可以删除需要的字段或为没有必填字段的人创建单独的模型。

然后要获得 selected AccountType,您需要像这样更改您的 Register 模型。

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; }
    }

并像这样在视图中更改 select。

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

显然您使用的是 net6。你有两个选择,让这个属性可以为空(也许更多)

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

或者为了防止以后出现这种情况 类 您可以从项目配置中删除 Nullable

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