昨日のASP.NET MVC ベータの悩みのシンプルな解決方法はこれかな~。
UpdateModel(TryUpdateModel)で取得したForm値は、ModelStateDictionaryの値が優先されて、2回目以降のViewでInputExtentionsの引数の値を無視する、のこと。 昨日と同じようにまずはモデルクラス。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication.B1.Models
{
public class Person
{
public string Name { get; set; }
public int? Age { get; set; }
}
public class MyViewData
{
public string TeamName { get; set; }
public int Level { get; set; }
public List People { get; set; }
public MyViewData()
{
People = new List();
}
}
}
変更する必要も無かったけど、配列の時の使い方とそうじゃない場合を分かりやすくMyViewDataクラスのプロパティを追加。 今回もコントローラはHomeControllerってことで、以下のコードを追加。
public ActionResult People()
{
var viewData = new MyViewData() {
TeamName = "チーム座布団",
Level = 11,
People = new List() {
new Person() { Name = "たけはら", Age = 33 },
new Person() { Name = "まうり", Age = 26 },
new Person() { Name = "しんたろ", Age = 21 }
}
};
return View(viewData);
}
[ActionName("People"), AcceptVerbs(HttpVerbs.Post)]
public ActionResult PeoplePost()
{
var formData = new MyViewData();
if(TryUpdateModel(formData))
formData.People.RemoveAt(1);
ViewData.ModelState.Remove("People.index");
if (ViewData.ModelState.IsValid)
foreach (var ms in ViewData.ModelState.Where(ms => ms.Key.StartsWith("People")))
ms.Value.AttemptedValue = null;
return View(formData);
}
最後に、Views/Homeの下にPeople.aspxを追加。
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="People.aspx.cs" Inherits="MvcApplication.B1.Views.Home.People" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <% using (Html.BeginForm()) { %> <label>チーム名</label> <% = Html.TextBox("TeamName") %> <label>レベル</label> <% = Html.TextBox("Level") %> <table> <% foreach(var person in ViewData.Model.People) { var row = ViewData.Model.People.IndexOf(person); %> <tr> <th>なまえ</th> <td> <% = Html.Hidden("People.index", row)%> <% = Html.TextBox("People[" + row + "].Name", person.Name)%> </td> </tr> <tr> <th>年齢</th> <td><% = Html.TextBox("People[" + row + "].Age", person.Age)%></td> </tr> <% } %> </table> <input type="submit" value="送信" /> <% } %> </asp:Content>
Person クラスの配列になるPeopleプロパティの表示と取得をメインにしてるんで、hiddenでPeople.indexという名前で配列のインデックスを入れるのは昨日と同じだけど、Html.Hiddenヘルパーを使うのが違うところ。これはPostしたときに、必ずModelStateから削除するようにすることで値がCSVになっちゃわないように対応。これに関しては、UpdateModelでエラーが起きても関係なく削除しちゃって問題ないもんね。 で、Post時のコントローラでUpdateModelのエラーが無ければ、Peopleで始まるキーを持つModelStateの AtteptedValueにnullを入れることで、2回目以降のViewでも値がそのまま表示されるようにしてます。エラーの時には入力値がそのまま表示されるようにして欲しいから、エラー時にはnullを入れない。 これで、配列(List<T>だけど)のプロパティを持つクラスでも、UpdateModelで取得したり表示したり簡単にできるようになりました。
初回表示は↓。
送信ボタンを押した2回目の表示↓。 ちゃんと"まうり"が消えます。
1回目の送信で入力エラーがあった場合の表示↓。
やったね!
あと、Evalが拡張されて↓こう書くことで書式化が簡単になりました。
<% = ViewData.Eval("Age","{0:D4}") %>
※"0034"みたいに4桁0埋めの書き方。