Html Helper の EditorFor と DisplayFor が、POST 要求に対する応答で、異なった値を表示することがあります。その理由と解決策を備忘録として書いておきます。
上の画像を表示した Model, View, Controller のコードは以下の通りです(説明に関係ない部分は省略してあります)。
Model
public class FileModel
{
public string FileName { get; set; }
}
View
@model MvcApplication1.Models.FileModel
@using (Html.BeginForm())
{
@Html.EditorFor(m => m.FileName)
<input type="submit" value="POST" />
@Html.DisplayFor(m => m.FileName)
}
Controller
public class FileController : Controller
{
[HttpPost]
public ActionResult Index(FileModel m)
{
m.FileName += ".ext";
return View(m);
}
}
ユーザーがテキストボックス(EditorFor 相当)にファイル名を入力し、form を submit(POST 要求)すると、Controller で model の FileName プロパティに拡張子 ".ext" を追加します。
この場合、応答画面では、DisplayFor には拡張子 ".ext" が追加されて表示されるものの、EditorFor には ".ext" は追加されません。 上の画像の例を見てください。
つまり、EditorFor には POST された値がそのまま使われています。
何故そうなるかと言うと、例えば、EditorFor の入力にユーザーが間違った値を入力した場合、サーバーに POST された時に検証 NG とし、エラーメッセージ(例:入力が間違っています)を表示するとともに、EditorFor にはユーザー入力をそのまま表示したいという理由だそうです。
具体的には、POST された値は ModelState ディクショナリ(model ではない)に格納されていて、Html Helper はまず ModelState ディクショナリを調べて、そこに値があればそれを表示するようになっています。
詳しくは以下のページを見てください。
ASP.NET MVC’s Html Helpers Render the Wrong Value!
value が POST されるか否かが問題になります。例えば、EditorFor に限らず、HiddenFor もその value は POST されるので、結果は EditorFor と同じになります。
一方、DisplayFor には、POST される value などというものはないので、model の値が使われます。
EditorFor にも DisplayFor と同様に model の値(拡張子 .ext が追加されたもの)を表示するには、ModelState ディクショナリを Clear することです。
そうすれば model から値を取ってくるので、期待した結果になります。具体的には以下のコードを追加します。
if (ModelState.IsValid)
{
ModelState.Clear();
}
ただしこのようにして、検証結果 OK で POST 要求への応答をそのまま返すのは、二重 POST の問題が起こりうるので、好ましくないようです。
MVC に限らず、Web アプリケーション開発の基本として、Post/Redirect/Get (PRG) パターンを使う・・・即ち、POST 要求への応答をそのまま返すのは検証結果 NG の場合のみとし、検証結果 OK の場合は、例え同じページを表示するにしても、リダイレクトしてブラウザに GET 要求させるのがよいそうです。(詳しくはリンク先を見てください)
Post/Redirect/Get パターンを使うように、上記のコードを書き直すと、以下のようになると思います。
public class FileController : Controller
{
[HttpGet]
public ActionResult Index()
{
FileModel model;
object obj = TempData["ValidationResult"];
if (obj is FileModel)
{
// 検証 OK ⇒ 再確認
model = (FileModel)obj;
}
else
{
// 初期画面
model = new FileModel();
}
return View(model);
}
[HttpPost]
public ActionResult Index(FileModel m)
{
if (ModelState.IsValid)
{
// 検証 OK ⇒ 再確認
m.FileName =
ModelState["FileName"].Value.AttemptedValue
+ ".ext";
TempData["ValidationResult"] = m;
return RedirectToAction("Index");
}
else
{
// 検証失敗
return View(m);
}
}
}