フォームの Post 後にアクションで変更した値をビューに反映させる

Page creation date :

The page you are currently viewing does not support the selected display language.

環境

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

ビュー

アクションとモデルを基にビューを作成します。

更新ボタンをクリックしたら ResultValue1ResultValue2 を表示するようにしていますが、それぞれ「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 を入力し更新してみてください。

アクション側では ResultValue1ResultValue2 に値をセットして返しているので、本来両方表示される想定ですが、実際には 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);
}