フォームの Post 後にアクションで変更した値をビューに反映させる
環境
- Visual Studio
-
- Visual Studio 2022
- ASP.NET Core
-
- 6.0 MVC
上記以外の環境でも動作しますが、公開しているコードは上記のフレームワークで作成したものです。
作るもの
「画面で入力したテキストを POST し、受け取ったテキストを加工したものを新しい結果として同じ画面に返す」という簡単なプログラムを作成してみます。
コード
ベースは新規の MVC プロジェクトを作成したものとし、そこからコードを追加したものを記載しています。 全体の構成についてはコードを公開していますのでそちらを参照してください。
モデル (ビューモデル)
ビューとアクションのやり取りの為に以下のモデルを作成します。ResultValue
が2つありますが、ビューに結果を2つ出したかったので2つ用意しました。
SampleViewModel
namespace PostValueChange.Models
{
public class SampleViewModel
{
<summary>入力値を受け取るプロパティ。</summary>
public string? InputValue { get; set; }
<summary>結果を出力するプロパティ。</summary>
public string? ResultValue1 { get; set; }
<summary>結果を出力するプロパティ。</summary>
public string? ResultValue2 { get; set; }
}
}
アクション
GET でビューを表示し、POST で入力されたテキストを受けとって加工し結果を返す、という簡単な処理です。
HomeController
// 省略
namespace PostValueChange.Controllers
{
public class HomeController : Controller
{
// 省略
[HttpGet]
public IActionResult Sample() => View();
[HttpPost]
public IActionResult Sample(SampleViewModel model)
{
if (ModelState.IsValid == false) View(model);
model.ResultValue1 = model.InputValue + " テキストを追加1";
model.ResultValue2 = model.InputValue + " テキストを追加2";
return View(model);
}
}
}
ビュー
アクションとモデルを基にビューを作成します。
更新ボタンをクリックしたら ResultValue1
と ResultValue2
を表示するようにしていますが、それぞれ「input テキスト」「div タグ内」に表示するようにしています。
Sample.cshtml
@model PostValueChange.Models.SampleViewModel
@{
ViewData["Title"] = "Sample";
}
<h1>Sample</h1>
<h4>SampleViewModel</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Sample" >
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="InputValue" class="control-label"></label>
<input asp-for="InputValue" class="form-control" />
<span asp-validation-for="InputValue" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="更新" class="btn btn-primary" />
</div>
<div class="form-group">
<label asp-for="ResultValue1" class="control-label"></label>
<input asp-for="ResultValue1" class="form-control" />
<span asp-validation-for="ResultValue1" class="text-danger"></span>
</div>
<div>@Model?.ResultValue2</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">前の画面に戻る</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Index.cshtml
から Sample
に遷移できるようにリンクを追加します。
Index.cshtml
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<a asp-action="Sample">Sample</a> @* 追加 *@
POST 処理で受け取ったモデルを更新してもビューの input などには反映されない
上記で作成したコードを実行し Sample 画面から InputValue
を入力し更新してみてください。
アクション側では ResultValue1
と ResultValue2
に値をセットして返しているので、本来両方表示される想定ですが、実際には div
タグ内に設定した2の方しか表示されません。
ModelState に設定されている値が優先
クライアントから入力値を POST された際、値は引数のモデル変数にセットされますが、他に ControllerBase.ModelState
にも値がセットされます。
そして入力された値の検証は ModelState
にセットされた値で行われます。ModelState.IsValid
で判定できているのもそのためです。
デバッグ時にブレークポイントで処理を止めると中身が確認できます。
本来ビューに値を返す場合は return View(model);
のように記載すれば OK なのですが、ModelState
に値が設定されている場合は ModelState
の値が優先されてビューに返却されます。
ModelState
にセットされる値はビューの input 等の入力して送信された値なので、モデルでいうと InputValue
, ResultValue1
がセットされている状態です。
そのため、ResultValue1
については ModelState
の値が優先され、ModelState
にセットされていない ResultValue2
はモデルにセットした値の方がビューに表示されるというわけです。
モデルの値を優先してビューに値を返すには
前述のように ModelState
に設定されている値が優先してビューに返却されるので、逆に ModelState
に設定されている値を消してしまえばモデルの値がビューに返却されることとなります。
以下のように ModelState.Clear()
を呼ぶことによって ModelState
が持っている値を全て消すことができます。
[HttpPost]
public IActionResult Sample(SampleViewModel model)
{
if (ModelState.IsValid == false) View(model);
// ModelState の値を消してモデルの値をビューに返却できるようにする
ModelState.Clear();
// ビューに返す値を設定する
model.ResultValue1 = model.InputValue + " テキストを追加1";
model.ResultValue2 = model.InputValue + " テキストを追加2";
return View(model);
}
ModelState
は検証処理も行っていますので、ModelState.Clear
を呼ぶときは必ず ModelState.IsValid
の後に読んでください。
実行すると以下のように正しく結果が表示されるようになります。
ModelState.Clear
メソッドを呼ぶと全ての値が消えてしまうので、以下のようにモデルのプロパティ名を指定して個別に値を消すことも可能です。
[HttpPost]
public IActionResult Sample(SampleViewModel model)
{
if (ModelState.IsValid == false) View(model);
// ModelState の値を消してモデルの値をビューに返却できるようにする
ModelState.Remove(nameof(model.ResultValue1));
// ビューに返す値を設定する
model.ResultValue1 = model.InputValue + " テキストを追加1";
model.ResultValue2 = model.InputValue + " テキストを追加2";
return View(model);
}