【问题标题】:How to implement two forms with separate BindProperties in Razor Pages?如何在 Razor 页面中实现两个具有单独 BindProperties 的表单?
【发布时间】:2018-07-09 01:13:18
【问题描述】:

我正在使用带有 Razor 页面的 ASP.NET Core 2,并且我试图在一个页面上拥有两个具有不同属性 (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();
        }
    }
}

点击两个提交按钮中的任何一个都会让我进入相应的帖子处理程序。 “ProdutName”和“MakerName”都正确地填充了我在相应输入字段中键入的任何内容。到目前为止,一切顺利。

但是:ModelState.IsValid() 总是返回真——不管相应属性的值是否有值。即使两个属性都为空,ModelState.IsValid() 也为真。

另外:OnPostProductAsync() 应该只验证“ProductName”,因此 OnPostMakerAsync() 应该只验证“MakerName”。

这可以做到吗?还是我对 Razor Pages 的要求太多了?有很多博客和教程向您展示如何在一个页面上拥有两个表单……但它们都使用相同的模型。我需要不同的模型!

【问题讨论】:

  • 您可以根据表单中的 asp-page-handler 属性非常简单地执行此操作。您可以在此处查看示例:youtube.com/watch?v=-6PE4p4gUYQ
  • 2021,我们仍然没有很好的方法来做到这一点! WTF 微软?每页1个表格真的吗?如果我想在一页中有用户设置和应用程序设置的表单怎么办 - 我不能。我必须创建一些模型怪物,将所有值保留在那里等等..
  • 我已经打开了一个与此github.com/dotnet/aspnetcore/issues/37606相关的问题

标签: c# asp.net-core-2.0 razor-pages


【解决方案1】:

为了使验证正常工作,您必须创建一个包含这两个属性的视图模型,并为您要检查的每个属性定义 [必需],但因为您有两个不同的表单验证它不会起作用,因为如果两个值都按要求定义,那么当您尝试验证产品时,它也会验证没有值的制造商。

您可以做的是自己进行检查。例如 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();
}

【讨论】:

  • @pitardis:感谢您的帮助。是的,您的代码确实有效。不过,我希望有一个更“优雅”的解决方案。当然,在我的实际应用程序中,视图模型比只有一两个字符串变量要复杂一些。因此,手动检查所有验证规则似乎是一项乏味的工作。但你知道吗:如果没有人想出更好的主意,我肯定会接受你的建议。
  • 据我所知,没有其他方法可以做到这一点。我已经为几家酒店做过很多次了,当你说这“看起来像是一项乏味的工作”时,我知道你的意思。
  • 谢谢!好吧,让我们看看其他人是否有另一个绝妙的主意:)
  • Allrigthy,目前没有更好的解决方案。我走的是石头路……谢谢pitaridis!
【解决方案2】:

我的解决方案不是很优雅,但它不需要您手动进行验证。您可以保留[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,因为字段键不一致,有时包含前缀。对我来说已经足够好了,因为我的表单很小,字段名称不同。

【讨论】:

    【解决方案3】:

    另一个解决方案非常接近......

    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();
            }
            // ...
        }
    }
    
    

    【讨论】:

    • 感谢您提供此解决方案。我使用了 ModelState.Clear();它工作正常。
    • 谢谢 - 保留属性允许 javascript 验证也可以工作。
    【解决方案4】:

    我遇到了同样的问题,这就是我的解决方案。

    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 类中,这样会更花哨。

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

    【讨论】:

    • 感谢您的回答,这对我很有帮助,我编辑了您的帖子以提供更易读的 IsValid 版本。
    猜你喜欢
    • 1970-01-01
    • 2023-03-31
    • 2021-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-27
    • 1970-01-01
    • 2012-05-01
    相关资源
    最近更新 更多