如何在 Razor 页面中实现两个具有单独 BindProperties 的表单?

Posted

技术标签:

【中文标题】如何在 Razor 页面中实现两个具有单独 BindProperties 的表单?【英文标题】:How to implement two forms with separate BindProperties in Razor Pages? 【发布时间】: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相关的问题 【参考方案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 版本。

以上是关于如何在 Razor 页面中实现两个具有单独 BindProperties 的表单?的主要内容,如果未能解决你的问题,请参考以下文章

在 MVC 5 Razor 页面中实现 bootstrap.chosen

如何在剃须刀页面中实现 Treeview

如何匹配 Razor Pages 中的多个路由?

在 Hibernate 中实现基于条件的搜索页面的优雅方式

在 Blazor (.razor) 组件中添加多个组件

如何在具有两个属性、每个属性的必要权重和最小总值的 3d 数组中实现背包算法?