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

发布于 2025-01-15 21:00:26 字数 4555 浏览 3 评论 0原文

注意:请忽略密码未安全存储的事实;这个项目只是为了我自己的学习目的,我将来会解决这个问题。

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

这是 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 个模型字段具有 null/无效值:

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

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

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

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

是你 2025-01-22 21:00:26

显然你使用的是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>

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>
开始看清了 2025-01-22 21:00:26

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

输入图片此处描述

然后要获取选定的 AccountType,您需要像这样更改您的注册模型。

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

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" 
                    class="form-select" 
                    aria-label="Default select example"
            ></select>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文