2008年10月22日水曜日

あぁ~、そうか、こうすればいいんだ

昨日の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で取得したり表示したり簡単にできるようになりました。

初回表示は↓。

img.aspx

送信ボタンを押した2回目の表示↓。 ちゃんと"まうり"が消えます。

img.aspx2

1回目の送信で入力エラーがあった場合の表示↓。

img.aspx3

やったね!

あと、Evalが拡張されて↓こう書くことで書式化が簡単になりました。

<% = ViewData.Eval("Age","{0:D4}") %>

※"0034"みたいに4桁0埋めの書き方。