如何在 Razor Pages 中使用单独的 BindProperties 实现两种形式?
How to implement two forms with separate BindProperties in Razor Pages?
我正在将 ASP.NET Core 2 与 Razor Pages 一起使用,我正在尝试在一个页面上使用 具有不同属性 (BindProperty) 的两种表单。
@page
@model mfa.Web.Pages.TwoFormsModel
@{
Layout = null;
}
<form method="post">
<input asp-for="ProductName" />
<span asp-validation-for="ProductName" class="text-danger"></span>
<button type="submit" asp-page-handler="Product">Save product</button>
</form>
<form method="post">
<input asp-for="MakerName" />
<span asp-validation-for="MakerName" class="text-danger"></span>
<button type="submit" asp-page-handler="Maker">Save maker</button>
</form>
以及对应的PageModel:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace mfa.Web.Pages
{
public class TwoFormsModel : PageModel
{
[BindProperty]
[Required]
public string ProductName { get; set; }
[BindProperty]
[Required]
public string MakerName { get; set; }
public async Task<IActionResult> OnPostProductAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
public async Task<IActionResult> OnPostMakerAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
}
}
点击两个提交按钮中的任何一个都会让我进入相应的 post 处理程序。 "ProdutName" 和 "MakerName" 都完全填充了我在相应输入字段中键入的内容。到目前为止,还不错。
But: ModelState.IsValid() always returns true - 不管是否对应的值属性 是否有值。 ModelState.IsValid() 即使两个属性都为空也是如此。
另外:OnPostProductAsync() 应该只验证 "ProductName",因此 OnPostMakerAsync() 应该只验证 "MakerName".
这完全可以做到吗?还是我对 Razor Pages 的要求太多了?有很多博客和教程向您展示如何在一个页面上有两个表单……但它们都使用相同的模型。我需要不同的模型!
为了使验证正常工作,您将必须创建一个包含这两个属性的视图模型,并为您要检查的每个属性定义 [Required],但因为您有两种不同的形式通过不同的验证,它不会起作用,因为如果两个值都按要求定义,那么当您尝试验证产品时,它也会验证没有值的制造商。
您可以自己做检查。例如 OnPostProduct 可以有以下代码:
public async Task<IActionResult> OnPostProductAsync()
{
if (string.IsNullOrEmpty(ProductName))
{
ModelState.AddModelError("ProductName", "This field is a required field.");
return Page();
}
// if you reach this point this means that you have data in ProductName in order to continue
return Page();
}
我的解决方案不是很优雅,但不需要您手动进行验证。您可以保留 [Required]
注释。
您的 PageModel
将如下所示 -
private void ClearFieldErrors(Func<string, bool> predicate)
{
foreach (var field in ModelState)
{
if (field.Value.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
if (predicate(field.Key))
{
field.Value.ValidationState = Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid;
}
}
}
}
public async Task<IActionResult> OnPostProductAsync()
{
ClearFieldErrors(key => key.Contains("MakerName"));
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
public async Task<IActionResult> OnPostMakerAsync()
{
ClearFieldErrors(key => key.Contains("ProductName"));
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
这不是最好的主意,因为您需要将绑定的字段名称与字符串进行比较。我使用 Contains
是因为字段键不一致,有时包含前缀。对我来说已经足够好了,因为我的表单很小,字段名称不同。
还有一个非常接近的解决方案...
public static class ModelStateExtensions
{
public static ModelStateDictionary MarkAllFieldsAsSkipped(this ModelStateDictionary modelState)
{
foreach(var state in modelState.Select(x => x.Value))
{
state.Errors.Clear();
state.ValidationState = ModelValidationState.Skipped;
}
return modelState;
}
}
public class IndexModel : PageModel
{
public class Form1Model {
// ...
}
public class Form2Model {
// ...
}
[BindProperty]
public Form1Model Form1 { get; set; }
[BindProperty]
public Form2Model Form2 { get; set; }
public async Task<IActionResult> OnPostAsync()
{
ModelState.MarkAllFieldsAsSkipped();
if (!TryValidateModel(Form1, nameof(Form1)))
{
return Page();
}
// ...
}
}
我遇到了同样的问题,这就是我的解决方案。
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
[BindProperty]
public IndexSubscribeInputModel SubscribeInput { get; set; }
[BindProperty]
public IndexContactInputModel ContactInput { get; set; }
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
SubscribeInput = new IndexSubscribeInputModel();
ContactInput = new IndexContactInputModel();
}
public void OnPostSubscribe()
{
if (IsValid(SubscribeInput))
{
return;
}
}
public void OnPostContact()
{
if (IsValid(ContactInput))
{
return;
}
}
public class IndexSubscribeInputModel
{
[Required(AllowEmptyStrings =false, ErrorMessage ="{0} é obrigatório!")]
public string Email { get; set; }
}
public class IndexContactInputModel
{
[Required(AllowEmptyStrings = false, ErrorMessage = "{0} é obrigatório!")]
public string Email { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "{0} é obrigatório!")]
public string Message { get; set; }
}
private bool IsValid<T>(T inputModel)
{
var property = this.GetType().GetProperties().Where(x => x.PropertyType == inputModel.GetType()).FirstOrDefault();
var hasErros = ModelState.Values
.Where(value => value.GetType().GetProperty("Key").GetValue(value).ToString().Contains(property.Name))
.Any(value =>value.Errors.Any());
return !hasErros;
}
我可能会将 "IsValid" 方法放在 PageModelExtensions class 中,这样会更漂亮。
希望这对某人有所帮助...
我正在将 ASP.NET Core 2 与 Razor Pages 一起使用,我正在尝试在一个页面上使用 具有不同属性 (BindProperty) 的两种表单。
@page
@model mfa.Web.Pages.TwoFormsModel
@{
Layout = null;
}
<form method="post">
<input asp-for="ProductName" />
<span asp-validation-for="ProductName" class="text-danger"></span>
<button type="submit" asp-page-handler="Product">Save product</button>
</form>
<form method="post">
<input asp-for="MakerName" />
<span asp-validation-for="MakerName" class="text-danger"></span>
<button type="submit" asp-page-handler="Maker">Save maker</button>
</form>
以及对应的PageModel:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace mfa.Web.Pages
{
public class TwoFormsModel : PageModel
{
[BindProperty]
[Required]
public string ProductName { get; set; }
[BindProperty]
[Required]
public string MakerName { get; set; }
public async Task<IActionResult> OnPostProductAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
public async Task<IActionResult> OnPostMakerAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
}
}
点击两个提交按钮中的任何一个都会让我进入相应的 post 处理程序。 "ProdutName" 和 "MakerName" 都完全填充了我在相应输入字段中键入的内容。到目前为止,还不错。
But: ModelState.IsValid() always returns true - 不管是否对应的值属性 是否有值。 ModelState.IsValid() 即使两个属性都为空也是如此。
另外:OnPostProductAsync() 应该只验证 "ProductName",因此 OnPostMakerAsync() 应该只验证 "MakerName".
这完全可以做到吗?还是我对 Razor Pages 的要求太多了?有很多博客和教程向您展示如何在一个页面上有两个表单……但它们都使用相同的模型。我需要不同的模型!
为了使验证正常工作,您将必须创建一个包含这两个属性的视图模型,并为您要检查的每个属性定义 [Required],但因为您有两种不同的形式通过不同的验证,它不会起作用,因为如果两个值都按要求定义,那么当您尝试验证产品时,它也会验证没有值的制造商。
您可以自己做检查。例如 OnPostProduct 可以有以下代码:
public async Task<IActionResult> OnPostProductAsync()
{
if (string.IsNullOrEmpty(ProductName))
{
ModelState.AddModelError("ProductName", "This field is a required field.");
return Page();
}
// if you reach this point this means that you have data in ProductName in order to continue
return Page();
}
我的解决方案不是很优雅,但不需要您手动进行验证。您可以保留 [Required]
注释。
您的 PageModel
将如下所示 -
private void ClearFieldErrors(Func<string, bool> predicate)
{
foreach (var field in ModelState)
{
if (field.Value.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
if (predicate(field.Key))
{
field.Value.ValidationState = Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid;
}
}
}
}
public async Task<IActionResult> OnPostProductAsync()
{
ClearFieldErrors(key => key.Contains("MakerName"));
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
public async Task<IActionResult> OnPostMakerAsync()
{
ClearFieldErrors(key => key.Contains("ProductName"));
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
这不是最好的主意,因为您需要将绑定的字段名称与字符串进行比较。我使用 Contains
是因为字段键不一致,有时包含前缀。对我来说已经足够好了,因为我的表单很小,字段名称不同。
还有一个非常接近的解决方案...
public static class ModelStateExtensions
{
public static ModelStateDictionary MarkAllFieldsAsSkipped(this ModelStateDictionary modelState)
{
foreach(var state in modelState.Select(x => x.Value))
{
state.Errors.Clear();
state.ValidationState = ModelValidationState.Skipped;
}
return modelState;
}
}
public class IndexModel : PageModel
{
public class Form1Model {
// ...
}
public class Form2Model {
// ...
}
[BindProperty]
public Form1Model Form1 { get; set; }
[BindProperty]
public Form2Model Form2 { get; set; }
public async Task<IActionResult> OnPostAsync()
{
ModelState.MarkAllFieldsAsSkipped();
if (!TryValidateModel(Form1, nameof(Form1)))
{
return Page();
}
// ...
}
}
我遇到了同样的问题,这就是我的解决方案。
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
[BindProperty]
public IndexSubscribeInputModel SubscribeInput { get; set; }
[BindProperty]
public IndexContactInputModel ContactInput { get; set; }
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
SubscribeInput = new IndexSubscribeInputModel();
ContactInput = new IndexContactInputModel();
}
public void OnPostSubscribe()
{
if (IsValid(SubscribeInput))
{
return;
}
}
public void OnPostContact()
{
if (IsValid(ContactInput))
{
return;
}
}
public class IndexSubscribeInputModel
{
[Required(AllowEmptyStrings =false, ErrorMessage ="{0} é obrigatório!")]
public string Email { get; set; }
}
public class IndexContactInputModel
{
[Required(AllowEmptyStrings = false, ErrorMessage = "{0} é obrigatório!")]
public string Email { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "{0} é obrigatório!")]
public string Message { get; set; }
}
private bool IsValid<T>(T inputModel)
{
var property = this.GetType().GetProperties().Where(x => x.PropertyType == inputModel.GetType()).FirstOrDefault();
var hasErros = ModelState.Values
.Where(value => value.GetType().GetProperty("Key").GetValue(value).ToString().Contains(property.Name))
.Any(value =>value.Errors.Any());
return !hasErros;
}
我可能会将 "IsValid" 方法放在 PageModelExtensions class 中,这样会更漂亮。
希望这对某人有所帮助...