如何在 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 中,这样会更漂亮。

希望这对某人有所帮助...