web-dev-qa-db-ja.com

Razorページで別々のBindPropertiesを持つ2つのフォームを実装するにはどうすればよいですか?

RazorPagesでASP.NETCore 2を使用していて、1つのページに別々のプロパティ(BindProperty)を持つ2つのフォームを作成しようとしています。

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

2つの送信ボタンのいずれかを押すと、対応する投稿ハンドラーが表示されます。 「ProdutName」と「MakerName」の両方に、対応する入力フィールドに入力した内容が正しく入力されます。ここまでは順調ですね。

しかし:ModelState.IsValid()常に trueを返します-対応するプロパティの値に値があるかどうかは関係ありません。 ModelState.IsValid()は、両方のプロパティがnullの場合でもtrueです。

また、OnPostProductAsync()は「ProductName」のみを検証する必要があるため、OnPostMakerAsync()は「MakerName」のみを検証する必要があります。

これはまったくできますか?それとも、Razor Pagesに質問しすぎていますか? 1ページに2つのフォームを表示する方法を示すブログやチュートリアルはたくさんありますが、それらはすべて同じモデルを使用しています。別のモデルが必要です!

9
Ingmar

検証を適切に機能させるには、2つのプロパティを含むビューモデルを作成し、チェックするプロパティごとに[必須]を定義する必要がありますが、検証が異なる2つの異なるフォームがあるためです。両方の値が必要に応じて定義されている場合、製品を検証しようとすると、値を持たないメーカーも検証されるため、機能しません。

あなたができることはあなた自身でチェックをすることです。たとえば、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();
}
5
pitaridis

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

2
Michael Dzuba

私のソリューションはあまりエレガントではありませんが、手動で検証を行う必要はありません。 [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
AvnerSo