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

时间:2018-01-30 07:59:24

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

我正在使用带有Razor Pages的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();
        }
    }
}

点击两个提交按钮中的任何一个都会带我进入相应的帖子处理程序。两者都是&#34; ProdutName&#34;和&#34; MakerName&#34;使用我在相应输入字段中输入的内容进行核心填充。到目前为止,非常好。

但是:ModelState.IsValid()始终返回true - 无论相应属性的值是否具有值。即使两个属性都为null,ModelState.IsValid()也为true。

另外:OnPostProductAsync()应该只验证&#34; ProductName&#34;因此OnPostMakerAsync()应该只验证&#34; MakerName&#34;。

这可以完成吗?还是我从Razor Pages那里问了太多?有很多博客和教程向您展示如何在一个页面上有两个表单......但它们都使用相同的模型。我需要不同的型号!

4 个答案:

答案 0 :(得分:5)

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

您可以做的是自己进行检查。例如,OnPostProduct可以包含以下代码:

@RunWith(MockitoJUnitRunner::class)
class RegistrationFragmentViewModelTest {
    var mViewModel: RegistrationFragmentViewModel? = null
    @Mock var mContext: Context? = null
    @Mock var mView: RegistrationView? = null

    @Before
    fun setUp() {
        mViewModel = RegistrationFragmentViewModel(mContext!!, mView!!)
    }

    @Test
    fun AfterInput_InvalidFirstName_ShowFirstNameError() {
        // Given
        val firstName = "fa"
        val email = "fanjavaid@gmail.com"
        val password = "demo"

        // When
        mViewModel?.doRegister()

        // Then
        verify(mViewModel)?.validateInput()
        verify(mView)?.showFirstNameError(mContext?.resources?.getString(R.string.registration_error_email_format)!!)
    }
}

答案 1 :(得分: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是因为字段键不一致,并且有时包含前缀。对我来说已经足够好了,因为我的表单很小,具有不同的字段名称。

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

我面临着同样的问题,那是我的解决方案。

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类中,这样会更加有趣。

希望这可以帮助某人...