Home)がどこまで最適化できるのか試してみようと思います(ビデオ見疲れたし)。現状↓こんな感じです。見ての通り、あまり最適化の余地がない...。localhostからの取得自体が少なすぎ。Site.cssとHome/IndexのHTMLと、jQueryとMap.js。あとはVirtualEarthのサーバーから取得。トータルGradeは60ポイントでD判定。マッシュアップ恐るべし。そのものはどんなサイトでも通用する物なので。今回どこを最適化するかというとCSSとJavaScript。画像もないし、ETagもVirtualEarthがらみだし、CDNも勘弁。NerdDinnerサンプルではCSSは1個しかないけど圧縮して、キャッシュが効くように。JavaScriptも圧縮してキャッシュが効くようにするけど、さらに動的に1つのファイルにしてしまう。今回はHome/Indexしかいじらないですが、Viewに埋め込まれてるJavaScriptも外だしにしてしまいます。ようするに以前の投稿の続きです。スタティックハンドラはそのまま使います。JSONPが無いのでそこもスルー。圧縮と縮小化、連結、キャッシュヘッダのコントロールを行うのに新しいコントローラを作成します。コントローラのアクションの結果が圧縮されたCSSかJavaScriptになるようにします。StaticFileHandlerじゃないのは複数ファイルを連結させたいからです。圧縮させるためのActionFilterを定義。}}}}なんてことないです。Response.Filterに圧縮用のストリームを指定するだけ。後はよしなにはからってくれます。使い方はこれをコントローラのアクションに指定するだけです。↑こんな感じのUrlで使用します。ルーティングを登録すればもう少し綺麗になりますね。気になる方はチャレンジしてみてください。ここでJavaScriptの縮小化をするのにJavaScriotMinifierと言うのを使ってます。Tracから取得出来ます。自分で書いてもいいです。単純にコメントと空白を削除するだけなので自分でも書けるところですが、ここでは楽します。変数名やファンクション名を最適化とかは無しです。自分でそこまでするならハフマン的なことをすればいいかも。ファイルを1つに連結して縮小化のステップを毎回処理するのは大変(CPUとIOが)なので、HttpRuntimeのCacheに入れてしまいましょう。キャッシュのクリアは考えない富豪プログラミングで。パラメータで指定しているverには指定したファイル群の中で最大の最終更新日時を整数化したものを渡すように作れば上手く行く寸法です。OutputCache属性はMVCにそもそも用意されてるものなので、そのまま利用しましょう。ここでは有効期限1ヶ月を指定してます。verの値が変われば再生成なのでお構いなし。サーバー上でメモリに実体をキャッシュし、クライアントでも有効期限を指定してファイルキャッシュさせるのは無駄なように見えますが、違うUAからのアクセスならクライアントキャッシュは入って無いので、その時サーバー上のメモリキャッシュが賢く機能してくれます。その為には両方のキャッシュを上手く使う事が大事。中間にリバースプロキシを入れてそこでキャッシュさせたりすると、アプリケーションサーバーの負荷を下げるのに役立つよね。やったこと無いですが。後は、Viewで使うヘルパーを。ModelDataの場合は同一キーで値を入れようとしてもダメよ。常に最初に入れた値だけが取り出せます。無名クラスをJSONで展開の4パターンを上手いこと処理するヘルパーです。それぞれ固定のパスにファイルがあるという前提でタグを出力します。CSSは/Content、サイト共有のJavaScriptは/Scripts、ページ固有のJavaScriptはViewと同じフォルダにViewと同じ名前で作成(/ViewScripts/Home/Index.jsの形式でアクセス)。Compressアクションに指定するverを生成するために、GetLastModifyがファイルに直接アクセスして最終更新日時を取得してるけど、ここでもHttpRuntimeのCacheをCacheDependencyを上手く利用してしまえば、実体へのアクセス(IO)はなくせるので、さらに改良の余地あり。Viewも変更します。Site.Master。%></body></html>ヘッダでCSSをlinkタグで書いていたのをHtml.RenderCssTagsに変更。引数は文字列配列でファイル名を並べてください。いくつ書いてもOKです。すべてを1つのファイルに連結して、圧縮したレスポンスを返すようにUtility/Compressを参照するlinkタグを生成します。jQueryのscriptタグをHtml.RegisterScriptTagsに変更。最初の引数"site"はサイト共有を表します。閉じbodyの直前にあるHtml.RenderScriptTagsでRegisterしているすべてのスクリプト(サイト共有、ページ固有、無名クラス3つとも)を展開します。すればページの最後ですべてのスクリプトを展開出来るようになって、YSlow的にも高評価。今回はHome/Indexしか最適化しない(全部は面倒)ので、Home/IndexのViewを少し変更します。Html.RegisterViewScripts();%></asp:Content>ここでマッシュアップするためにVirtualEarthのサーバーからスクリプトを取得するようになってますね。これに関してもちゃんとページの最後にタグ出力しなきゃいけないところですが、そこまでは作ってないので、これまた興味のある人はRegisterOuterScripts的な関数を作ってみてSite.MasterのRenderで一括出力できるようにしてみてはどうでしょう。今回そこは作ってないので、直接ここに残したままにしときます。サイトで使うVirtualEarth用の関数群を保持してるMap.jsは、本来サイト共有のスクリプトなのでSite.Masterで一括して指定しておきたいところですが、無駄にすべてのページに反映されるのも良くないので、ViewでRegisterします。すると、サイト共有のスクリプトキャッシュが2種類作成されることになります。1つはjQuery+Mapと、もう一つはjQueryのみのキャッシュ。それほど迷惑な話でもないので、これでもいいんじゃないかなと思いますがどうですかね。最後のRegisterViewScriptsがページ固有の外部JSを登録してる部分です。Viewのパスから勝手にスクリプト名を判断してます。複数のViewで共有っていうこともあり得る(例えば、データの新規登録と編集では共通のスクリプトを使いたいけど、それはサイト共有じゃない)ので、スクリプト名を指定するオーバーロードもあります。細かいところはソースを見てね。最後にViewScripts用のルートを登録すれば完成です。前投稿と同じですが再掲(Global.asax)。ClientTagHelpers.ViewScriptRouteHandler();これで、再度YSlow!※クリックで拡大。CSSとJavaScriptは圧縮されてファイルサイズも小さく(トータルが425KBから325KBに縮小)なってるけど、全体的には全く効果が無いに等しい...。7ポイントアップはしたけど。VirtualEarthが有効期限の無いレスポンスを返してるのと、Etagがちゃんと制御されてないのが大きなところ。これ以上はどうしようもないかな~。本格的なアプリケーションだとこれでずいぶん最適化される(CompressをHome/IndexアクションにつけるとかもOKだけど認証かかってる物のキャッシュは気をつけてね)し、画像なんかが多い場合も最適化の余地が残されてる可能性大なので。興味ある人はお試しアレ。今まで、プロジェクトのソースを直接書いてたけど、ダウンロード出来る方が試しやすいと気がついたので、貼り付けておきます。InというサイトでVirtualEarthのMVC用ヘルパーを公開中です。Ajax.MapでJavaScript(NerdDinnerのMap.jsの部分)を出力してくれます。外部ファイルにならないけど、パラメータ指定がチェインしててなんかオシャレな感じ。これなら無名クラスでしていして、内部でデフォルト値とマージするスタイルの方がいい気がするね。%>この辺は好みだから、自分で作っちゃえばいいんだけどね!ちなみにソースを確認してて、面白いEnumヘルパー発見。EnumExtensionsクラスでEnumに拡張メソッドを指定してるんだけど、Enumに属性でJavaScriptに出力するときの文字列を指定しておくというもの。}こうやって、Enumの属性に指定しておいて、出力するときにToJavaScriptValueの結果をレンダリング。ない?その中でも特に気になるのがデータ操作をラッピングしてサーバーサイドとのプロキシ動作を司るDomainServiceからはしばらく目が離せそうに無いですよね。得意のなんの脈絡もない展開ですいませんね。Videosあれですよ、とにかく上記2つのMIX09セッションを見るべし、ですよ。英語だけど気にすんなよな!ビデオ見た感じSilverlight3が目立ってる感じするかもしれないけど、そこじゃねっす。気になるのはデータアクセス部分っす。Silverlight3からもASP.NETからも、はたまたJavaScript(これはちょっと特殊だけど)からも同一のサーバーサービスを呼び出してるよね。サーバーサービスを呼び出してるというか、サーバーサービス定義を元にジェネレートしたクライアント用クラスを使ってるよね。中身はサーバーサービスの呼び出しと、データ操作のトラッキングだろうと。サーバーサイド実装が何を使ってるのか気になる。ヒントはJavaScriptから呼び出してるデモの所で、DataService.axdがURIに含まれてるから、基本HttpHandler内に組み込まれてる機能なんだろうとは思うけど、それがWCFなのか独自実装(だとしたらパネーっす)なのか。9ってことはないでしょうが。そんなこんなで"絶対読めよ"と↓こんな資料も。Overviewしょうがないからダウンロードして読んでみる。読むというか見る...。前半40ページくらいまで、こういう感じよ、こうやって書くよ、こんな感じで作るのよ、な説明が続くので、ビデオ見たのでそこはサラッと流す。EnableClientAccess属性が付いてるとクライアントコードをジェネレートするって書いてるけど、ジェネレートしなくてもサーバー上でだけでも使うならそれもよし。こっからが、少し気になる部分の説明に入っていく。CRUD操作のR部分。デモでは何気なくGet~で書いたものをプロキシクラス呼び出すクライアントではLoad~って書いてロードしてるよね。ItemsSourceのところはコレクションプロパティ(this.Entities.GetEntityList<T>()って何が返るのかな~)を指定してるし。いう風な関連付けがされてるのか気になるっす。空のコレクションを返しといて、あとはObserveに任せるのかな。“Select”」なら何でもいいみたいね。いうか、Query属性をつけるなら、それすら関係無い。読み込みクエリーはLoad~が非同期で実行。更新系もデモだとInsert/Update/Deleteがプレフィックスに付いてるけど、ここも何種類か規約でプレフィックスが用意されてる。データ取得と同じく、属性ベースでも指定可(Update/Insert/Delete属性)。クライアントでの更新結果はEntities.GetChanges()でEntityChangeSetを取得することですべて抽出できる。SQLのDataContext.GetChangeSet()と同じようなものなんだろう。SubmitChanges()で更新情報をサーバーに送信して確定させるか、RejectChanges()で破棄。エラー)に更新セットを流す(?)。トランザクション制御はやらないので、必要なら自分でオーバーライドしてTransactionScope使うべし。Custom属性をつけたメソッド内での更新はクライアントのコンテキスト上で実行されるみたいで、ちゃんとクライアントてSubmitChangesしないとサーバーには反映されない。プロキシクラス内にそのまんまメソッドが展開されるんでしょうね。更新を伴わない場合はCustom属性じゃなくてServiceOperation属性。どっちも非同期みたい。フィールドレベルの検証はDataAnnotationsを使ってMetadataクラスに定義。この辺はだいたいみんな同じような作り方になるのね。独自検証の場合はShared属性をつけたクラスで実装しとけば、それも自動生成に含めてくれるから、単純な検証以上の事をしたい場合はこれで。ビジネスロジックとしての検証はどこに書くのかな~?モデルクラスなのかな~?後は認証や、認可の属性と続いてSilverlight、ASP.NET内で使うDomainDataSourceコントロールの説明と続く。ページングやらフィルタの指定もXAML内でできるのがカッコイイ。後は、サンプルコードみたりしないとちょっとよく分からないけど、ビデオでは紹介されてない実装の細かいところはこのドキュメントにある程度書かれてる感じです。Services(Silverlight3環境必須)を入れなくてもDomainServiceだけでも試したいな~。3にあるので、そっちを眺めてみようと思います。StorageそういえばAzureのInviteが来てから、なんにも遊んでないことを思い出した。Videosちょうどいい具合のビデオ。Azureがどういうものかは分かってるっていうことでそこは省略。ビデオの通りのデモを作るってだけの単純なエントリ。どこで何をしなきゃいけないのかは分かるようになると思われる。StudioでWebRoleプロジェクトを作成。最新のSDK&Toolsを入れて無いとダメっす。StorageとSDSと2種類のストレージサービスがあるけど、SDSは今回関係無いのであしからず。Storage(ローカルストレージ)がインストールされます。Storageは停止状態でOKです。PortalAzureのポータルでまずはストレージを作成。"FirstWeb"という名前にしてみた。ここでエントリが終わってもつまんないから、とりあえずは手順を。csdefにConfigurationSettingsを追加。</ConfigurationSettings></WebRole></ServiceDefinition>これに合わせるように、cscfgにはvalueをセットしたものを書き込みます。次にモデルクラスを作成。keyさえあれば、あとはどんなスキームでもいいのでサンプル通りのメッセージクラスを作成。}}}テーブルの行に相当するエンティティはTableStorageEntityクラスから派生させる。Key書けばいらないけど、この実装が一番簡単。ストレージで管理したいテーブルのコンテキストをTableStorageDataServiceContextから派生。ここまではもう、こうする物だと覚えておこう。自分で全部実装してもいいけど、せっかくStorageClientのサンプルライブラリが提供されてるんだからそれを使う。Servicesに付いてくるサンプルはまた違う実装を使ってるので、なんならそっちを使う(Microsoft.Azure.StorageClient)っていうのも面白いかもね。中身はRESTfulな実装になってるから好みの実装で。Storageを使う場合、クラウド内にちゃんとテーブル定義を作らないとダメ。ビデオではその部分をPythonのスクリプトを書いて実行してるけど、なんせマイマシンにはPythonの実行環境もないので、そこは他のサンプル同様Global.asaxに書いてしまうことにします。}一回作ってしまえば、クラス定義が変わるまで、存続するはずだからわざわざBeginRequestに書かなくてもいいんじゃないの?と常々思ってたので今回はApplication_Startに書いてます。コレでもちゃんと動くし。最後にdefault.aspxを書き換えて終わり。</form></body></html>コードビハインドはこっち。}}}Page_LoadじゃなくてPreRenderにデータバインドを書くのを忘れずに。コレを動かす!見にくいけど、ローカルストレージは全部Stopped。WebRoleはローカルのFabricで動いてブラウザ内にはメッセージリストがちゃんと表示されてる。一応、Fiddlerで確認も。見にくいけど、2行目が最初の表示のバインドのためのリクエスト。3行目がメッセージのポスト時に実行されるPOST。4行目が、ポストバック後のバインドで取得するリクエスト。MeshMesh使ってファイル同期なんていうのは、目に見えるアプリケーションとして日常のなかで使う事もあると思うけど、Meshの凄さはそこじゃない、とみんな感じてる模様。クラウドで保持してるエンティティをMeshデバイス間で自動で同期することの一つの実装としてのファイル共有(“P2P=ファイル共有=悪”と同じ発想にいっちゃうと何が起きてるのか分からなくなるから)。データの持ち方が階層構造になってて、アクセスはすべてAtomPubのRESTful。認証すらRESTful。ベータとして一般に使えるMeshクライアントは、ファイル同期とリモートデスクトップだけしか遊べないけど、開発者向けのCTPを使うとそこでアプリケーションが開発出来て、カスタム同期アプリケーションを構築出来る。こっちはファイル同期とリモートデスクトップは使えないから、両方のバージョンを入れないとちょっと不便。この辺についてはいろんなサイトで取り上げてるから割愛。デベロッパーとしては、開発者向けで少し遊んでみたいと思っちゃうよね。利用申請(Connectで)もして、プロジェクトが作れるようになってるところまでは準備が出来てるものとします。Studioにインストール出来ない。英語版じゃないとダメ!って言われてインストーラーが停止する。これは選別トラップと見た。これをクリアしてこそMeshで開発するにふさわしい開発者だというメッセージなんじゃ。んな、わけないっつの。ダウンロードしたLiveFrameworkTools.exeは自己解凍形式のCABファイルなので、CABが解凍できるツールを使えば簡単に解凍できます。解凍後にLiveFrameworkSDK.msiとLiveFrameworkTools.msiが出てくるから、それを直接インストールすれば、上記メッセージ(言語環境チェックされない)も出てこないので日本語版でも開発することが出来るようになります。自己責任でってことにはなるけど。CTPだからそもそも自己責任だし(MSがダメだと言うなら諦めよう)。これで、開発出来る状態ができあがったね。Videosそしたら↑コレ見ましょう。前半デモがグダグダだけど、そこは無視。内容自体はグッとくる。面白そうでたまらない。Mesh」アプリケーション開発をしてみる。Silverlightも初めてだし、小粋な物は作れないので、ビデオで最初にデモしてる、ログインしてるユーザー名を表示する部分だけ作ってみることに。Application"を選択して作成。次に、XAMLにテキストブロックが最初から張り付いてるので、そこにx:Nameで"HelloMessage"と命名。</Grid></UserControl>コードビハインドのmeshAppLoadedに以下のコードを書く。あとは、実行させるのみ。何も開発してないじゃないか!なんてことは言わないで...。あとはビデオのやり方通りに淡々と決まり事を実施。Frameworkのプロジェクトを作成(Mesh用)。そこでzipファイルをアップロード。開発したもののアップロードはVSが自動でやってくれる(ボーッと待つ)。Desktopが表示されてアプリケーションが実行される。デスクトップにも自動でアイコンが表示されて、同じものがローカルマシンでも動かせます。これで開発出来る環境が出来てることは分かったし、ここからデータを追加したりして同期するのを楽しめるコードを追加していく。...、ビデオの通りやれば出来るって分かってるんだけど、なんか面倒くさくなってきたので、今日はここまで。ID)と認可(OAuthじゃないけどそんな感じのもの)するアプリケーションも作れたり、興味が尽きない。Servicesが強烈過ぎるのでそっちでも遊んでみたい。3はまだ入れたくない気がしなくもなく。2と3のどっちも同じマシン内に入れて、それぞれ(MeshとRIA)で開発できるのかな。Overflowこの質問の回答としては、ページにscriptタグを書き、その中でJavaScriptを書きつつサーバーサイドのコード(foreachやViewDataの参照)も埋め込んでしまえばいいじゃないか、というもの。コレまでもさんざん悩んで、ViewEngineの実装に寄り道したり右往左往したけど、結局はグローバルな変数にJavaScriptで使用したいデータをViewDataから取り出して登録しておき、ロジック(JavaScriptで書くクライアントでのっていう意味で)は外部JSファイルに。外部JSファイルからグローバル変数にアクセスすることで、Ajaxによるサーバーからのデータ取得や、サーバーサイドコードの埋め込みを不要にして、純粋なJavaScriptコードのみの外部JSファイルを作成する感じです。JavaScriptの用途は、サイト全体で共通のコード(jQuery.jsやprototype.jsなどのフレームワーク、またはアプリケーション共通機能)と、そのページでしか必要のない機能に分けられる。データとしてはJSONPでサーバーサイドのデータをクライアントサイドに渡したり、サーバーサイドのデータをクライアントサイドにJSONにシリアライズしてページに直接埋め込む方法がある。XHRで取得や、DOMそのものをデータとして使う方法など他にもあるけど、今回はその辺はとりあげません。整理すると処理コードとしてサイト共通ページ固有データコードとしてJSONPJSONシリアライズのページ埋め込みというのを今回のテーマとします。サイト共通コードは、通常~/Scriptsフォルダに入れておくのがRC以降のテンプレートになってます。ページ固有コードは通常View内に直接scriptタグを書いて、その中に直接書くのがどこのチュートリアルにも書かれてるやり方になってます。View内に直接は書かないでViewと同じフォルダに外部JSファイルとして作成する方法をとります。なんでViewと同じフォルダに入れるかというと、スクリプトファイルを管理しやすくしたいからです。~/Scriptsに入れてもいいんだけど、そうするとViewと同じようなフォルダ構成(~/Content/Home/Index.jsとか)を作ってその中に入れるか、ルール通りのファイル名(Home.Index.jsとか)にして1つのフォルダ内にすべて保持するか。ルール無用でViewとJSとを管理してもいいとは思うけど、簡単に管理しつつ、極力簡単なヘルパーでscriptタグを出力できるようにしたいので、Viewと同じフォルダ構成にしておき、アクション名.jsと命名するのがいいんじゃないかと。すると全く同じフォルダ構成を用意するというちょっとかっこ悪いことになり、スッキリしない!という理由でページ固有コードはViewと同じフォルダに保持するのがいいんじゃないかと思う次第です。~/Viewsフォルダには通常aspx/ascxのいずれかを入れておく(WebFormViewEngine)んですが、そこに.jsファイルを置いておき、scriptタグでsrc指定するとどうなるかと言うと、そんなアクションありませんと怒られます。src=”/Home/Index.js”></script>と書いてもきちんとダウンロードされないということです。規約でそうなってるから。独自のMasterLocationFormatsやViewLocationFormatsを定義したとしても、BuildManager.CreateInstanceFromVirtualPathでJScriptのコンパイラが走ってあえなくエラー(クライアントサイドのコードなのにサーバーサイドのコードとしてコンパイルしようとする)。ViewEngineがらみをどうのこうのするのは、ちょっと面倒。簡単な解決方法は、ページ固有コードをscriptタグのsrcとして指定する場合にはパスのルートに何かしら固定のフォルダ名を指定するようにして、そういうルーティングを行うようにしてしまう。ルーティングが出来てもMvcRouteHandlerを使ったんじゃ結局コンパイルしようとするので、そこはファイルを直接返すだけの簡単なRouteHandlerを書いてしまいましょう。ようするにStaticFileHandlerなんだけど...。IRouteHandlerの実装と、IHttpHandlerの実装。}これをRouteCollectionに登録します。ページ固有コードなので/ViewScripts/コントローラ名/アクション名.jsというURLでアクセスさせたいと思います。MapRoute拡張メソッドを使った場合、登録したRouteが返ってくるので、それに直接ハンドラをセットするやり方です。ViewScriptRouteHandler();たったこれだけ。src=”/ViewScripts/Home/Index.js”></script>として取得できます。処理コードはとりあえずこれで組み込めるようになったので、こんどはJSONP。これはもう特に難しいことを考えずにコントローラのアクションとして実装してしまい、JavaScriptResultで返すのが一番簡単。src=”/Home/JsonpDate”></script>と、書く事でjsonp変数に値が入ります。↓こう書いたのと同じ意味になります。'2009/03/17'};</script>ちなみにUtility.ToJSONの中身は単純。}System.Web.Script.SerializationのJavaScriptSerializerを使います。MVCの中でも同じように使ってるから問題無いでしょう。最後にページ固有のデータをViewDataに入れておき、View内でヘルパーを使ってscriptタグに展開してしまうコードを作成します。使い方のイメージを先に書いてしまうと↓こんな感じです。%>ViewData[“Url”]にアクション内で値を入れてるという前提で。}こんな感じでしょうか。↓ViewData["Url"]に"どこそこ"とアクションで入れてればこう展開されます。'どこそこ'};</script>これで、共通コードは~/Contentなり~/Scriptsから取得、ページ固有はViewと同じフォルダ内にjsファイルとして作成したものを~/ViewScriptsから取得。Jsonpは通常のアクションとして実装し、ページ固有のデータはViewData経由でActionからViewに渡した物をJsonで展開。クライアントサイドでちゃんとevalするべきところではあるけどその辺はご愛敬ってことで。これだけでも、コードとデータをきちんと分離は出来てるけど、もっと便利に簡単に使うために、スクリプトとデータのRegisterヘルパーと、それらをページの最後でまとめて書き出す、Renderヘルパーなんかを作っておいて、さらに圧縮(Compress)/縮小化(Minify)/連結(Merge)なんかも実装してたりするんですが、それはまた今度。Tutorialを読んでいて、おやや?と思ったわけです。彼らはコレを使ってないんだろうかと。Blogで紹介されてるRuleViolationクラスを使う方法なんですよね。いう検証ロジックになってるのかを公開されてるソースとPDFに書かれてる内容から整理してみます。SQLを使ってモデルを作成してるので、モデルクラスのOnValidateパーシャルメソッドを使って、データ更新時に検証ロジックが実行されるようにしてます。Blog実行した検証結果はRuleViolationクラスのインスタンスとして、プロパティ名とエラーメッセージを保持。GetRuleViolations()で、プロパティ毎の検証を実施。RuleViolation(~)の形式で各プロパティの検証結果を返すようにすることでLINQのAny()でエラーの有無をすべてチェックしなくても判定できるってことですね(実際はCount()==0でエラー無しっていうコードになってるけど)。OnValidate()内ではIsValid()を呼んで、エラーがあるなら例外(ApplicationException)を発生させてるんですね。更新処理自体はRepositoryの中で実装してますが、実質InsertOnSubmit()してるだけ。それをAction内で実行するわけですが、入力エラーがある場合Repository.Save()を呼び出した段階で例外が発生するので、catch内でGetRuleViolations()を呼び出して、エラー詳細を取得。ヘルパーを用意しておき、RuleViolationをModelState.AddModelError経由でModelErrorとして展開。これでViewの処理時に自動でエラークラスもセットされるし、ValidationSummaryの表示なんかもされる。ここまでで、検証ロジックについて触れてなかったですけど、そこはもうベタな感じで作ってる。String.IsNullOrEmpty()で必須チェックや、正規表現で形式チェック。どこにもDataAnnotationsなんて使ってないです。検証処理の実行をDefaultModelBinderに任せないのでIDataErrorInfoの実装もしていない。長くなったけど、こんな感じです。昨日のエントリでViewData.Eval()が便利だよ、って話をしました。ここでやっと今回の本題。MVCに用意されてるHtmlHelperのInputExtensions(Html.TextBoxとかの入力用ヘルパー)は、内部でこのViewData.Evalを実行してるんですね。そのEvalの中で呼び出されてるのが以下の関数。}ViewDataDictionary.csの200行から抜粋。プロパティが別のクラスのインスタンスになってても取り出せるように再帰ですね(例えばModel.Address.ZipCodeとか)。ここでGetRightToLeftExpressions()で上手いこと取得したいプロパティをすべて取り出せるんですが、問題はそのプロパティを取り出す関数であるGetPropertyValue()。してもソースを引用しておきます。}GetIndexedPropertyValue()ですよ。これが最初に値を取得するために呼び出されるんだけど、これが内部でGetProperty("Item",~)を呼び出すんですよね。}しつこくソースを引用しますが、ViewDataDicstionaryそのものであれば、直接ディクショナリの値を返そうとしてます。内部ではTryGetValueで一致するものがないからとnullを返します。なると、Modelを参照して、プロパティを取得するので、ViewData.Modelにセットされてるインスタンスのプロパティを取得するときはそのままインスタンスのプロパティを返してくることになるので、問題にはなりません。このModelにセットしてるインスタンスが別のクラスのインスタンスを保持してて、それをプロパティとして公開してる場合にはですね、GetIndexedPropertyValue()が"Item"プロパティを参照するようになるんです。この”Item"っていうプロパティはデフォルトインデクサのプロパティ名みたいで、IDataErrorInfoを実装するっていうことはコレを実装することになるので、なんとまぁエラーメッセージが返ってきてInput要素に出力されてしまうんですね。最高に分かりにくい文章ですいません。サンプル書くと長くなりそうなんです...。適当なコードで表現してみます。}}こんな感じのクラスがあったとします(中途半端ですいません)。これをViewData.ModelにセットしてViewを表示するとしましょう。View(person);}こんな感じでしょうかね。これを表示するとしましょう。するとPersonクラスのFatherとMotherプロパティは空のPersonインスタンスが入ってます。PersonクラスはIDataErrorInfoを実装しててプロパティの検証コードが入ってます(DataAnnotationsの検証実行が入ってるとしてください)。%>なんて書いてると、ViewData.Eval経由で値を取得しようとするけど、上記理由のためIDataErrorInfoの実装のつもりで書いてるデフォルトインデクサにアクセスされて、エラーメッセージとしての"名前は?"がテキストボックスのvalueに設定されてしまうという罠。コレはテキストボックスのinput要素を作成してるInputHelperの引数useViewDataにtrueをセットして呼び出した時にEvalが実行されるんだけど、プロパティの値がnullならtrueとなる実装。そもそもstringの初期値はnullだから、今回のような場合必ずDataAnnotationsの検証が実行されて、エラーメッセージが表示されてしまいます。はてさて、どうしたものか。初期値として空文字("")をセットするとかしておけばnullじゃないから、デフォルトインデクサにアクセスされないんだけど、なんか気持ち悪し。IDataErrorInfo使わない方がいいんですかね~。