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つのフォームを表示する方法を示すブログやチュートリアルはたくさんありますが、それらはすべて同じモデルを使用しています。別のモデルが必要です!
検証を適切に機能させるには、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();
}
もう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();
}
// ...
}
}
私のソリューションはあまりエレガントではありませんが、手動で検証を行う必要はありません。 [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
を使用しました。フィールド名が異なる小さなフォームがあったので、私には十分でした。