2009年4月4日土曜日

> UpdateModel で、コレクションであるプロパティの各要素の、一部のプロパティだけ更新する

Developer @ ADJUST : ASP.NET MVC - UpdateModel で、コレクションであるプロパティの各要素の、一部のプロパティだけ更新する

コメント欄に書こうと思ったんですが、サンプルが長すぎたのでこっちに書きます。

おっしゃるとおり、復元したいクラスがリストじゃない場合は簡単に制限をかけられるんですが、コレクションのインスタンスに対しての制限は少し面倒ですね。

復元したいインスタンスの項目を制限する場合、UpdateModelを使うなら

  • IncludeやExcludeを指定
  • インターフェースを指定
  • Form復元用のベースクラスを指定

の3パターンあると思います。

が、Listの場合、Listそのもののインスタンスを生成するところから始まるのでUpdateModel<List<interface>>(…)とは書けないですね。インスタンスが作れないので。かといってUpdateModel<List<FormBase>>(…)と書くと、すべてが復元されないので一発で欲しいインスタンスが生成できません。

Listに関する部分に限りこういう使い勝手になってしまうので、アイテム毎にUpdateModelを実行するのが簡単な解決策になります(たぶん)。

ところで、一般的にこういう形式のデータをどのように復元させているのか気になる所ですね。いろいろなコードを眺めるとLINQ to SQLなんかのオブジェクトを使ってる場合は、必要な項目だけ復元させてあとはオリジナルのインスタンスにAttachという形式が多いと思います。オリジナルのインスタンスに直接復元させないところがミソかなと。

一旦復元用のインスタンスに値を入れて、それをオリジナルにコピー(Attach)してしまうのがもう一つの手段として考えられるのではないかなと思う次第です。

  public class ViewModelBase<T>
 {
   public object Attach<T>(object target)
   {
     foreach (var iprop in typeof(T).GetProperties())
     {
       var srcProp = target.GetType().GetProperty(iprop.Name);
       object val = srcProp.GetValue(target, null);
       if (val!=null)
         this.GetType()
             .GetProperty(srcProp.Name)
             .SetValue(this, val, null);
     }

     return this;
   }
 }

 public interface IPerson
 {
   int? Age { get; set; }
 }

 public class Person : ViewModelBase<IPerson>
 {
   public int? Age { get; set; }
   public string Name { get; set; }
 }

 public class Team
 {
   public List<Person> People { get; set; }
 }
こういうモデルがあるとして(ちょっとダサイですが)。コントローラに↓こういう処理を書くようなイメージです。
    Team LoadTeam()
   {
     return new Team { People = new List<Person>{
         new Person{ Name="Boo",Age=11},
         new Person{ Name="Bar",Age=21},
         new Person{ Name="Foo",Age=43}
       }
     };
   }

   public ActionResult Index()
   {
     var team = LoadTeam();

     return View(team);
   }

   [AcceptVerbs(HttpVerbs.Post)]
   public ActionResult Index(List<Person> people)
   {
     var team = LoadTeam();
     for (int idx = 0; idx < team.People.Count; idx++)
       team.People[idx].Attach<IPerson>(people[idx]);

     return View(team);
   }
オリジナルのTeamにpeopleのアイテムを埋め込んでいく感じです。実際にはIDかなにかでAttachしたいインスタンスとされるインスタンスを結びつける(この例だと単純にインデックスで結びつけてます)ことにナルト思います。

いかがでしょうか?