2008年12月22日月曜日

Oxiteのソリューション構成

ちょいちょいソースを見ていこうと思うところで、まずはソリューションの構成を確認して、どんなコードがどのプロジェクトに含まれてるのかを把握しとこうかなと。


ソリューションを開いたところで、表示されるプロジェクトは↓。

img.aspx

それぞれのプロジェクトに何が含まれてるのかを見てみたよ。

1.Oxite


このプロジェクトにOxiteで共通のライブラリコードや、インターフェースが集中。
BackgroundServicesにBackgroundServicesで実装する各種バックグラウンドタスクのベースクラスや、インターフェース。
Configurationにはweb.configのカスタムエレメント用のクラス。クラス自体は大量にあるけど、内容は各設定値を取得するための物だからエレメントの数だけクラスがある感じ。
Dataにはデータベースのモデルクラスのインターフェース。実装はLinqToSqlProviderに入ってる。
HandlersにMetaWeblogAPIとXmlRPCのクラス。コントローラクラスだけじゃなくてこのハンドラもルーティング登録されてるからコントローラを探しても見つからず。
Routingにはそのままルーティングを登録するクラス。ルート登録の詳細がMVCのWebアプリケーションのglobal.asaxじゃなくて、ここにすべてまとめて入ってる。
Searchに検索機能のインターフェース。
あと、ルートに共通関数が入ってる。
で、 StringExtensionsクラスにStringクラスの拡張メソッドが入ってるんだけど、なんとこの中でAntiXssLibraryをラップしてる関数有り。AntiXss.HtmlEncodeとAntiXss.HtmlAttributeEncodeを使って、エンコードするようにしてる。さすが抜け目なし。ただバージョンは先日公開された3.0じゃなくて1.5を使ってる。ちょっと古いのを使ってるのが気になるところだけどリリースの関係でOxiteの先にリリースしてるし、MIXのサイトでも使ってるって事で、そこはやむなしな感じで。

2.Oxite.BackgroundServices

バックグラウンドで動作する機能の実装。
何個かクラスがあるけど、ようはメール送信(SMTP送信)とトラックバック送信(HTTP POST)の2つを処理してる(と思う)。
CreateMessagesFromSubscriptions(BackgroundService)

コメントをメールメッセージとしてMessageRepositoryに登録。

SendMessages(BackgroundService)
MessageRepositoryに登録されてるメッセージをメール送信。

CreateTrackbacks(PostBackgroundServiceAction)
トラックバック要求をBackgroundServiceActionRepositoryに登録。

SendTrackbacks(PostBackgroundServiceAction)
BackgroundServiceActionRepositoryに登録されてる要求を実行。

3.Oxite.LinqToSqlProvider


QxiteプロジェクトのData内に定義されてる、リポジトリの実装。
モデル自体はLINQ to SQL(dbml)で定義されてるのをそのまま。
あとIQueryable<T>の拡張メソッドでページングデータ(PageOfAList<T>)を取得するものや、モデルクラスの拡張機能など。
MembershipRepositoryがoxite_User/oxite_Roleなんかを見るような独自実装なのがちょっと以外。これはGravator対応とかが関係してるんだろうか?関係なさそうだけどなんでASP.NETのMembershipじゃダメなんだろうね~。

4.Oxite.LiveSearchProvider


サイト検索をWindows Live Searchで実行するための実装。
http://soap.search.live.com/webservices.asmx?wsdlへのWeb参照(SOAP)で検索要求を送信して、結果を取得するようになってる~。

5.Oxite.SearchProvider


これも検索用の実装なんだけど、こっちはLINQ to SQLで実行。
Post.SearchPost(TitleとBodyをくっつけたもの)に対してContainsで単純に検索してる。
Like検索だね。

6.Oxite.MVC


このプロジェクトに全部のコントローラクラスが入ってる。
OxiteApplication クラスがHttpApplicationを派生したクラスとして、定義されててこの中のOnStartでバックグラウンドサービスの開始や、ルーティング登録の実行。BeginRequestでリダイレクトがどうのこうのって処理があるけど、なんのタメの機能なのかよくわかんないかも。
ControllerもBaseControllerっていうクラスをベースにして実装。
ルーティングとコントローラのクラス名を見比べると、URLの設計はそれはそれだろ的な思いもちらほらと。


AccountController(Users/{userName}/~,SignIn,SignOut)
AdminController(Admin/~)
ArchiveController(Archive/{*archiveData})
AreaController({areaName}/~)
FileController
HomeController
PageController({*pagePath})
PingbackController({id}/Pingback)
SearchController(OpenSearch.xml,Search/~)
SEOController(Robots.txt,SiteMap/~)
TagController(Tags/{tagName}/~)
TrackbackController({id}/Trackback)


Viewで使うViewPageクラスの代わりにBaseViewPage/BaseViewUserControlなんかを定義。
OxiteSite(Webアプリケーション)で使う共通関数なんかもここで定義。Oxiteでも共通関数があったりするけど、Webアプリケーションでしか使わないのはたぶんここに集中してるんだろね。
ヘルパーの拡張なんかもここ。
FeedResultっていうのがなぜかViewResultから派生させて、Viewとして定義してるのはちょっとダサイ気がしなくもない。
Trackback/Pingbackを属性クラスで定義して、ViewDataにディスカバリー情報を入れるっていうのはカッコイイ。

7.OxiteSite


これがサイトの実体プロジェクト。
でも、実質コントローラもロジックも別のプロジェクトにごっそり入ってて、ここではもっぱらViewに関する物だけがある。
なので、Global.asaxのコードビハインドではOxiteApplicationクラスから派生。
ほとんどのViewにAtom/Rssそれぞれのフィード専用Viewが入ってて冗長な気が...。もう少し綺麗にできるんじゃないのかな~。ダメなのかな~。
特徴的なのはViewDataがすべてDictionaryのままで、View内キャストするようになってるとこ。
ViewPage<T>なんて定義はなく、すべてキャスト。しかもViewに結構なコードが含まれてる気がする。
あと、ルートにLiveWriterManifest.xml(Windows Live Writer Provider Customization API) があって、MetaWeblogAPIに関する設定が書かれてた。こんなファイルがあるのを初めて知りました。


とりあえず、含まれてるプロジェクトはこんな感じってことで。
中身・機能についてもちょいちょい見ていきます。

2008年12月19日金曜日

RCなんですね

ASP.NET MVC Design Gallery and Upcoming View Improvements with the ASP.NET MVC Release Candidate - ScottGu's Blog


デザインギャラリーについての部分も面白そうだけど、それより何よりASP.NET MVCの次のリリースが1月にRCを出すってところ。
IDataErrorInfoっていうのが、どういうインターフェースなのか気になって年越しが落ち着かないじゃないですか!

View生成の時にScaffold出来るとかは、正直あんまり興味なかったりするけど、管理機能を作るのがどれだけ簡単かが、重要なエンタープライズ(社内システム)開発には有効なんだろうね。
でも、Viewのコードビハインドが全く生成されなくなるっていうのはすばらしいかも。

確かに@PageディレクティブのInheritsに指定するならViewDataの型指定のタメだけのコードビハインドファイルはいらない(Strongly-Typed ViewData Without A Codebehind - Tim Barcz)。
ひょっとして、そうすることで、コンパイルしなくてもViewData.Modelにアクセス出来るようになったりするんだろうか。

試してみたら、今のバージョン(ベータ)でも上記サイトのようにInherits="System.Web.Mvc.ViewPage`1[[モデルクラス名,アセンブリ名]]"を書いておけば、コンパイル無しでもViewData.Modelにアクセスできるね(`1ってついたクラスしてだったり面倒だけど、ジェネリックバージョンはこっちなんだもんな~)。
ViewData.ModelじゃなくてModelから書けるようにもなるっていう。

んで、すっごく気になるのがJavaScriptResult。
どういう風に使えるんだろ。

とにかくJavaScript内でViewDataを参照したいときとか、単純に外部jsに出来ないから、aspxにしてるけど、それをscriptタグで読み込むような使い方にしちゃうと、コードの色分けもIntelliSenceも効かなくてちょっと切ない。かといって、ページに直接 JavaScript埋め込みたくないし。
全然JavaScriptResult関係ない気がしてきた...。
1月にRCだと、その次はいつになるやらデス。

Oxite

Oxite – Home


いろんなニュースサイトでも取り上げられてるから知ってる人も多いと思います。
このOxite(おくさいと)、ASP.NET MVCで作られてるってことで楽しそうじゃないですか。

Oxite - Lab - MIX Online

↑ここで紹介ビデオが見れます。

何にせよ、どんなものかを知るには動かしてみるのが一番。
ということで、まずはソースのダウンロード
ファイルを解凍してソリューション(Oxite.sln)を開いて見よう。

img.aspx

あれれ~?怒られちゃった。dbprojが開けないってさ。
まぁいいや。そんな人のタメにもう一つソリューションファイル(Oxite.VWDExpress.sln)が入っててそっちを開きましょう。
データベース関連のプロジェクトはたぶん使う事ないし。
今度は問題なく開けたので早速実行。

img.aspx2

あっさり動いたね。
さて、使い方がサッパリ分からない。
とりあえず、ログインしてみるものの(Admin/pa$$w0rdで)「で?」って感じです。

img.aspx3

"Create Post"がブログエリアの投稿で、"Create Page"がページの投稿みたいだけど、画像ファイルをアップロードするインターフェースもWYSIWYGなエディタがあるわけでもない。素っ気ないな~。
MetaWeblog API対応なOxiteにはWindows Live Writeがピッタリなんじゃん?と、Aboutページにも書いてるしで、早速インストール(使ったことないも~ん)。

img.aspx4

んで、アカウントの設定をして、いざ投稿!

img.aspx5

なるほど。

img.aspx6

おぉ~。ちゃんと投稿できた。スラッグ(ナメクジじゃないよ、URLのページ名だよ)に日本語入れてみるなんていう意地悪してみたら、ちゃんとエンコードされてた。やるな。
※"新しい記事"(ブログ)は投稿出来るけど、"新しいページ"が投稿出来ないのは何でなんすか?

つか、この投稿者の写真の人誰だよ...。替えたいけど替え方が分からない。まぁちょいちょいソース追いかけようかな。

デザインテンプレートも特に気の利いた物もないし、エディタもテキストエリアだしで、普通に使うのには面倒かもしれないけど、バックグラウンドで処理が走るように作られてたり(パッと見た感じではTimerで起動するみたい)、MetaWeblogAPI対応だったり、プロジェクトが細かく分かれてたりで、気になるところ盛りだくさん。

img.aspx7

小さくし過ぎて見にくいけど、コードメトリックス。
OxiteSiteっていうのがASP.NET MVCのWebサイトなんだけど、コード量が異常に少ないのは、Oxite.MVCにすべてのコントローラが入ってるし、Oxiteと Oxite.LinqToSqlDataProviderにRepositoryとロジックが入ってるから。

MVCのバージョンが変わっても、変更箇所をOxiteSiteだけに押さえてしまおうということかな。
にしても、Viewをちらっと見たけど、ViewDataが型無しのディクショナリだったのがちょっと意外。キャストしまくってるし。

しばらくコードを見たり、動かしたりしながら遊んでみようかな~、なんて。
で、ちょいちょい気になった部分をエントリしちゃったりするのも面白そう。

2008年11月22日土曜日

Windows Azureで遊ぼう

とりあえずWebRoleでWebアプリケーションを作ってみたい。
Workerでごにょごにょするのも楽しそうだけどまずはWeb。
で、もちろんWebFormsじゃなくてASP.NET MVCでしょ!


Azure Services Platform

開発に必要なファイル類をダウンロードします。
Resources - Developer SDKs | Azure Services Platform
↑ここからね。

1.Windows Azure SDK。
2.Windows Azure Tools for Visual Studio

いろいろあるけど上記2つがあれば先に進めるよ。
.NET Services SDK(Account Control/Workflow/Service Busを試すときに使う)とSQL Data Services SDK(SDSのみ試したい時に使う)は使わないからほっとこう。
もちろんVisual Studio 2008とASP.NET MVC ベータ/SQL Server Expressは入れてあるって前提でね。
Azure SDKに開発に必要なAzure仮想環境がローカルに構築されます。なので、これが無いと開発できましぇん。Tools for VSが無いとテンプレートとか入らないし、たぶんファブリックとストレージを起動してくれないんだと思われる。入れてない環境で試さない安全主義!


img.aspx

↑これが仮想実行環境。Development Fabric。

img.aspx2

で、↑これが仮想ストレージ環境。Development Storage。
もちろんこれだけでもAzure上の開発は出来るんだけど、これじゃMVCにならないのでさらに追加でダウンロードするものが一つ。

Cloudy in Seattle : ASP.Net MVC on Windows Azure with Providers

↑ここで説明があるけど、MVCCloudServiceっていうテンプレートプロジェクトをダウンロード。

ASP.Net MVC Windows Azure Cloud Service – Home

で、このソリューションを開発を始めるタメのテンプレートに使います(簡単だね)。
ただし、Azure SDKに入ってるsamplesへのプロジェクトが2つあるので、その二つは手動でソリューションに追加しましょう。

対象になるプロジェクトはAspProviders/StorageClientの2つ。
AspProvidersがあればMembershipやSession、RolesのストレージにAzureのストレージが使えるようになるんだけど、その中でStorageClientを使うので(自分で書くプログラムでもね)、この2つは必須。
LINQ to SQLを使ってSqlServerにアクセスしないで、このStorageClientライブラリを使ってAzure上のStorageにアクセスするからね。LINQは使えるから安心だべ~!!

img.aspx3

img.aspx4

ほら、ちゃんと動く。
試しに"takehara"っていうユーザーを登録してみて、データベースを確認してみる。

img.aspx5

ServiceHostingSDKSampleデータベースのMembershipテーブルにユーザーが入ってるね~。あ、Profileがないな。まぁ、いいか。
AspProvidersがこのデータベースを使ってくれます。
Blob/Table/Queueはここじゃなくて、DevelopmentStorageDbっていう名前のデータベース。
ワクワクしてくるね~。

あ、Azureって何?ってことになる人もいるかもしれないけど、そこんところは他のサイトで色々説明があるから検索してみてね。簡単に言うとMicrosoft版Google App Engineね。簡単じゃね~よ!
で、データを保存する場所というか機能としてBlob/Table/Queueの3種類があるんだけど、このうちQueueはWorkerとの連携に使ったりするような簡易メッセージング機能なので今回はスルー。
BlobっていうのがファイルストレージでTableっていうのがデータベース。でもリレーショナルじゃないからSQL Serverみたいな感じで使えると思うと大間違いな設計になっちゃうので気をつけましょう(だってそうじゃないとスケールアウトも出来ないっしょ)。


Table にはStorageClientからアクセスするんだけど、なぜならRESTで自分でコード書くのが面倒だからだね。Blobにアクセスするときもそうだけど基本RESTで(CRUDはPOST/GET/PUT/DELETE)。SOAPもあるんだっけ?まぁ、いいや。

あと一つ、これはあった方がいいっていうツールがあります。

CodePlex.SpaceBlock – Home

これ、何する物かっていうとAzure Blobへのファイル操作を行う物。

img.aspx6

これがないとどうなるかっていうと...。PowerShell上でのコマンド処理ですから!
出来ない分けじゃないけど、面倒だしね。
ちなみにこのツールはAmazon S3にも使える優れものです。
ここまでで、だいたい準備完了。

これだけあれば開発出来るよ。
1.Azure SDKでローカルに仮想環境構築。
2.Tools for VSでVS統合。
3.MVCCloudServiceでASP.NET MVCテンプレートを準備。
4.SpaceBlockでBlobストレージへのGUIアクセス。

OK?

で、これだけだとAzure上にデプロイできません。ので、Azureを使えるようにアカウントを登録しないといけないのでレッツトライ。つか、最初にこれしとかないと...。
早速AzureサイトにアクセスしてSign inします。が、ここで悲しいお知らせが。

img.aspx7

Invitation Code(Resource Token ID)ていうのがメールで送られてこないと先に進めない...。
ぬかった。

Inviteをもらわないといけなかったのか。
そうだったのか。
と、いうわけで、現時点では実際にデプロイは出来ないってことになりまして...。
どうも、すいませんね。期待させちゃって。てへ。
これをcscfgファイル(AccountSharedKey)や、SpaceBlock(Access Key)に設定しとかないと、Azureにアクセス出来ないから面白くないんで、続きはInvite来たらってことで。

2008年10月30日木曜日

関連情報も続々登場

ASP.NET MVCベータの気になる関連情報。


その1.ASP.NET - Release: ASP.NET MVC Beta Source Code Release
まだそれほどダウンロードされてない気がするけど、これを見とかないことにはドキュメントが無い状態では効率よく開発するのは難しいよね。

その2.ASP.NET - Release: ASP.NET Dynamic Data 4.0 Preview 1
ASP.NET MVCとDynamic Dataの融合。
サンプルのブログが参考になります。

その3.SubSonic MVC Addin Updated for Beta 1 : Rob Conery
これまたスゴイんだけど、ScaffoldできるSubSonic。
リポジトリパターンのデータアクセスと、コントローラ、ビューまでをLINQ to SQLのモデルから生成。生成されるビューを_Templatesフォルダに作っておくことで、カスタマイズ可能。
Dynamic Dataでのお手軽作成に負けないほどの凄さ。

その4.Cloudy in Seattle : ASP.Net MVC Projects running on Windows Azure
これはまだ試せてないけど、Windows Azure上にASP.NET MVCをデプロイする方法。
衝撃的...。出来るかどうか、調べようと思ってた矢先のエントリでビックリ。
データアクセスがSQL Servicesだろうから、そっちの使い方も調べる必要があるけど、まずは動くっていうのが分かっただけでワクワクする。

2008年10月28日火曜日

UpdateModelのタイプ指定

ASP.NET MVCベータになってUpdateModelで「強く型付けされたホワイトリストフィルター」がありますね。これが悩ましくてですね。結局スマートな使い方はどうした物なのか未だに答えを見つけられないでいます。 そもそもViewDataをViewに渡す時ってどういう内容で渡しますか?

入力される項目と、表示しかしない項目があると思いますが、それってクラスを分けるもの? 単純に考えたら入力も表示も同じクラス内に混在させていいんじゃないの、って感じに実装すると思うんだけど、そうするとUpdateModelでちょっと面倒な事が起きたり起きなかったり。

例えば、表示専用のプロパティとしてLINQ to SQLのモデルクラスを持ってたりした場合なんて、切なくなりますよね。だってね、DefaultModelBinderでインスタンス作られると最悪Webサーバーがダウン。 その為にstring配列で復元対象のプロパティを指定したり、除外したりすることになります。ただ、単純に文字列をコードの中に埋め込むっていうのもシャキっとしない気がするんですよね(リフレクションでプロパティリストを自動取得するっていうやり方がシンプルで簡単だけど)。

だから、スコットさんのエントリに書いてあるように型指定して値を復元しようかな、と思い至る。 で、今度は保持してるプロパティにList<T>とかがあるとどうなるんだ、って話です。 前回までのエントリで配列なりListの取得が出来るのは分かってるんだけど、それを「強い型付け」で取得したいときどうしましょうかと。

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

  public class Person : IPerson
  {
    public string Name { get; set; }
    public int? Age { get; set; }
    public string Nickname { get; set; }
  }

  public interface IMyViewData
  {
    string TeamName { get; set; }
    int Level { get; set; }
  }

  public class MyViewData : IMyViewData
  {
    public string TeamName { get; set; }
    public int Level { get; set; }
    public List People { get; set; }

    public MyViewData()
    {
      People = new List();
    }
  } 

試しに↑こんなクラスを書いて。

var formData = new MyViewData();
UpdateModel(formData); 

と、実行するとですね、Peopleプロパティは復元されませんよね。 インターフェースにPeopleが無いから。 ※インターフェースに含めてプロパティの型をList<IPerson>にしても上手く行かない。 Peopleを強い型付けで復元したいときって、Prefixを指定してループで回して取得とか?

      var index = Request.Form["People.index"].Split(',').Length;
      for (var i = 0; i < index; i++)
      {
        var person = new Person();
        UpdateModel(person, string.Format("People[{0}]", i));
        formData.People.Add(person);
      } 

こうやって書けば、そりゃもちろん取得できるけど...。ダサい気がする。 IPersonじゃなくてPersonBaseクラスを定義して、PersonをインターフェースじゃなくてPersonBaseから派生させて保持する方法を考えてみた。

  public class PersonBase : IPerson
  {
    public string Name { get; set; }
    public int? Age { get; set; }
  }

  public class Person : PersonBase
  {
    public string Nickname { get; set; }
  }

...

UpdateModel>(formData.People, "People"); 

だけど、これってInvalidCastException。 Listの場合ってどうするのさ! コード量は少なくしたいっす。

次にListで取得して、それをListにキャストすればいいのかと。

var people = new List();
UpdateModel>(people, "People");
formData.People.AddRange(people.Cast()); 

これまたInvalidCastException。ダウンキャスト出来ないんだね~。そもそもCast()は何を使ってキャストしてるんですか。Reflectorで見た感じだとキャスト演算子(T)してるだけっぽいけど。 PersonBaseクラスにexplicitでPersonを返すメソッド書いたけど、派生クラスはダメよ!って怒られてダメだし...。 なので、PersonクラスのコンストラクタにPersonBaseを渡すほうほうに変えてみた。


public class Person : IPerson { public string Name { get; set; } public int? Age { get; set; } public string Nickname { get; set; } public Person() { } public Person(PersonBase person) { Name = person.Name; Age = person.Age; } }

Personクラスは↑こう変更。 で、UpdateModelの後のコードを↓こう変更。 formData.People.AddRange(people.Select(p=>new Person(p)); これは上手く動きますね。ViewDataのクラスのコンストラクタを追加する必要はあるけど、Controllerでは短いコードですむし。

Personクラスのコンストラクタはいじらずに、ConvertAllでConverterを渡しても結果同じだけど、コードはちょっと見づらい。Controllerに書くのもどうかと思うよね。

formData.People.AddRange(people.ConvertAll(
  new Converter(s => new Person() {
    Name = s.Name,
    Age = s.Age
  })
)); 

クラスの中に、違うクラスのコレクションや配列、リストをプロパティに持ってる場合の、UpdateModelのベストな使い方ってどう書くんですか...。 CustomMobelBinderを沢山作るのはちょっとヤダ(コード増えすぎ)。 教えて偉い人!

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埋めの書き方。

dotnetConf2015 Japan

https://github.com/takepara/MvcVpl ↑こちらにいろいろ置いときました。 参加してくださった方々の温かい対応に感謝感謝です。