如何让 ModelState 使用外键验证通过视图模型提交的数据?
注意:请忽略密码未安全存储的事实;这个项目只是为了我自己的学习目的,我将来会解决这个问题。
我有三个模型,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/无效值:
AccountTypes
Account.AccountType
Account.Person
我需要 AccountTypes
code> 在注册模型中显示视图的下拉/选择字段中的帐户类型列表。然后,Account.AccountType
和 Account.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:
AccountTypes
Account.AccountType
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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
显然你使用的是net6。您有两种选择,使此属性可以为空(也许更多),
或者为了防止将来其他类出现这种情况,您可以从项目配置中删除 Nullable
Apparently you are using net6. You have two choices, make this properties nullable (and maybe some more)
or to prevent this in the future for another classes you can remove Nullable from project config
在您的人员模型中,有一些字段标记为必填属性。由于此 ModelState 始终为 false,因为这些字段需要值,而视图或控制器(GET 调用)不提供它们。如果可能,您可以删除必填字段或为没有必填字段的人员创建单独的模型。
然后要获取选定的 AccountType,您需要像这样更改您的注册模型。
并像这样更改视图中的选择。
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.
Then to get selected AccountType, you need to change your Register model like this.
and change select in View like this.