コレでRouteData.DataTokensからNamespacesを取得して、Controllerの型を判定してる。}}なので、上記ヘルパー(Philさんのコードです)の太字の部分さえ書いておけば、ちゃんとサブフォルダーのControllerとしてインスタンスを作ってくれる。Namespacesがうんたらかんたらってアナウンスしてたときは、はぁ、そうですか、それ何に使うんですか?ってなもんだったけど、こうやって実装を見せられると納得。Writerのアドインでコードを色つきに出来るみたいだけど、BloggerにポストするとCSSがエンコードされちゃってちゃんと出来ない。使い方としてはCSSを外部に出してlinkタグを書くのが正んでしょうか?その場合、外部CSSはどこに置いておくのが正しいんでしょうか?いまいち使い方が分からずだけど、徐々に慣れていこうと思うところです。MVC2009年2月18日水曜日DataAnnotationsのValidationAttributeを作る何となくね、n以上の値のみ許可、っていうValidationAttributeが欲しいな、と思ったわけですよ。実際はRangeで最小値とdouble.MaxValue(intでもいいけど)とかで、代用出来るじゃないですか。悩ましいけど、コードを見てコレってようは最小値を制限したいってことなんでしょ?っていうのをすぐ分かるようにしとくなら専用のValidationAttributeかなって思ってね。そもそもなんて言う名前のクラスにすれば適切なんですか、って。moreとか、aboveとか出てくるじゃない。なんかね~、クラス名にするにはしっくり来ない。みんなどうしてるんだろ。結局はさ古典(昔、なんかで勉強したときに確かGTEとかって書いた気がするし)に則って、GreaterThanOrEqualAttributeって名前にしたんだけどさ。(System.Linq.Expressions))、ExpressionType.GreaterThanOrEqualだね。}って、書いて動くんだけど、まぁ、ちょっと、ね。最初から用意されてないってことはRange使えよっていう意図があるような気がするから、Range使いましょう。残念なことが続いてさこないだノートパソコンのメモリを2GBから4GBに変えたのよ。Ramdisk)にしてさ。1GBくらいできるじゃないですか。boostにすると、結局メモリとしてすべて使えて早くなるんじゃないかって。それで動かしてたんだけど、あれだね、関係ないね。Boostとして使える領域が減る分HDDが回転しちゃうからか、バッテリの持ちも悪くなったし、パフォーマンスも上がらないし。BoostはSDカードに戻したよ。いいアイデアだと思ったんだけどな~。これもかなり残念なことだったんだけど、それ以上に残念だったのがこれ。最近ホッケーよりも水泳の方が楽しくてね、週3で泳いでるんだけどね、いつも行ってるプールがね、休みだったの...。メモリ上手く使えなかったことなんかよりよっぽど泣ける。定休日調べてなかったのが悪いんだけどさ。この休館が実は10日間も続くなんて思ってもなくてさ。泳ぎたいじゃない?だから違うプールに行ったわけですよ。したらね、完全アウェーな空気に心が折れそうになっちゃってさ。プールってコースの泳ぎ方とか場所によってローカルルールがあるわけじゃないですか。なんか、いつもと違うってだけで、心折れる。泳ぎはしたけど、なんかね。今はもうホームプールが復活してるから、我が物顔で泳ぎまくってるんだけど、アウェー怖しだね。遊びURLは自動生成になっちゃうの?今気がついたというか、確認してみたら"blog-post_n"みたいなURL(Slug)が生成されてるけど、コレを変えるにはどうするのかな~?WLWだと出来なかったりするの?あ、スラッグが"いいえ"になってる...。自分で指定は出来ないけど、アルファベットで始まるタイトルなら、それを利用するようには出来てるんだね。このエントリのアドレス中途半端に"url"とかってなってるし。使いたいシステムが他にあるわけでもないし、自分でサーバー用意するのも面倒だから、これからは徐々にこっちに移行してしまおう。Connectでしかコメントを受け付けないようにしとけば、へんなスパムコメントもないだろうし(昨日の今日ですでにスパムコメントがあったのに驚いたけど即削除)。Writerで書いてみる。フォントとかどうなっちゃうのかちょっと不安なんだよね。テスト2009年2月16日月曜日と、言うわけでどういう形にすれば、引っ越しできるのかを試す。画像をどうすればいいのかを確認。テストブログの引っ越しをようようかといろいろ思うところがあって。趣味や技術的なこともすべてこっちに引っ越そうかな。RCの入力検証とにかく、簡単に検証したい。RCになってからIDataErrorInfoをモデルクラスに実装することで、DefaultModelBinderがプロパティ毎にValidationを呼び出すようになったりました。コレを上手く生かしたい。Dataで導入された、DataAnnotationsを使うようにしたい。結果的にかなりシンプルに実装出来ることが判明。Dataアプリケーション:CodeZine」に分かりやすい説明があるのでそちらを見てね。簡単に言うとクラスのプロパティに属性ベースで検証ルールとエラーメッセージを指定するもの。データベースのモデルクラス(DBO)に、検証ルールを入れたいときなんかにはMetadataType属性をクラスに指定しとけば、別のクラスで検証ルール属性を定義できたり、そりゃ~もう、便利そうでたまらない機能ですよ。↑ここでビデオでもどんなものか確認できます。古いしUIに関することにも触れてるけど、DataAnnotationsの簡単な使い方が分かると思います(動的検証はまた別の話なので今回は触れてません)。DataAnnotationsそのものは検証を実行してくれないので、検証の実行は自分で書く。書くと行っても属性指定しているValidationAttributeクラス(各検証属性の基底クラス)のIsValid()を呼ぶだけなんだけどね。この呼び出し部分をIDataErrorInfoのItemプロパティ(インデクサ)に書く。入力検証はDBOじゃなくViewModelに属性を定義して、そこでコードを書くようにしたい。イメージ的には↓こんな感じ(図にしたほうが分かりやすいんだけど、面倒くさい)。RCでIDataErrorInfoの使い方)をベースに進めることにします。ここでちょっと訂正です。↑このエントリーでList<WeaponViewModel>の復元がDefaultModelBinderでは出来ないと書いちゃってて、個別にUpdateModelを呼び出さないきゃいけないよね~、なんて思いっきり間違いを書いてました。コード見て分かるとおりPersonViewModelクラスのWeaponsがプロパティじゃなくてパブリックメンバ変数になってるから復元出来ないだけでした。プロパティにすればちゃんと復元されます。GetModelPropertiesのコード参照)。名前の通りメンバ変数じゃなくてプロパティを抽出する関数なんだよね。そりゃ復元されないわ。言うよりコード見た方がたぶん早いと思うんでコードを載せときます。}BaseViewModelクラスがIDataErrorInfoを実装。ViewModelは基本的にこのクラスを派生させる。何でかというとItemインデクサの処理はどのクラスでも全く一緒だからっていうのと、Errorはシンプルなモデルクラスなら空(null)でイイから。太字の部分がValidationAttributeの呼び出し。1つのプロパティには複数の属性をセットしてもいいので、全部の検証がOKかどうかチェック。プロパティにしときます。}※太字がDataAnnotationsのValiudationAttribute。PersonViewModelではモデルのエラーチェックをしたい(Weaponsプロパティのアイテム数チェック)から、Errorをオーバーライド。ホントはRequire属性で判定出来るんじゃないかと思ってたんだけど、Listに1件もポストされない時に呼び出されないってことが判明(復元対象の値が存在しないから)。試しにValidationAttributeクラスを派生させて、ListRequireAttributeクラスなんてものを書いてみたけど、そもそも呼び出してくれないから意味なかった。}※全く無意味なクラス。こんなクラスを書いても、List<T>の検証が呼び出されない(Listに1つでもアイテムが追加されるなら呼び出されるけど、何も追加されない場合はスルー)ので、IDataErrorInfo.Errorをオーバーライドしてモデル検証としてチェックするようにしました。AgeプロパティがintじゃなくてNullable<Int32>なのは、何も入力しなかった場合にintだと0になっちゃうのと、整数じゃなくて実数を入れたときにも0になっちゃう(intの初期値)のが都合悪いからです。}ココまででコントローラでは普通に復元されるようになります。入力エラーが分かりやすくなるように、Viewも少しいじったのでEdit.aspxのソースも貼り付けときます(と、言ってもValidationMessageを入れただけ)。Editでルフィーを選択。名前とタイプのところが、List<WeaponsViewModel>の入力フォーム。この状態のまま、Saveボタンを押してポストする。これだとDataAnnotations関係ないので、あえて入力エラーになるように、フォームの内容を変更。すると、DataAnnotationsがちゃんと動作してるのが確認できます。コレだとちょっとエラーメッセージが分かりにくいと思う。メッセージそのものの文章が変とかっていう話じゃなくて、List<WeaponsViewModel>のどのアイテムがエラーなのかが分かりにくいんじゃないかと。エラーメッセージにアイテムのインデックスを含めたい。ValidationAttributeクラスのErrorMessageって固定文字列。動的に生成出来ないですよね。思ったけど、ちょっと待て。エラー情報は、どのフォームフィールドなのかと入力値(ValueProviderResult)、それとエラーメッセージ(ModelError)は自動でModelStateDictionaryに入るので、これを強制的に書き換えればいいんじゃないかななんて思ったわけです。エラーメッセージにインデックス番号を含めるように置き換える拡張メソッドをModelStateDictionaryに追加することにしました。ErrorMessageが直接書き換えできるならもっと簡単に書けるけど、残念ながらこのプロパティはリードオンリー(getterしかない)。ModelErrorを削除してErrorMessageを書き換えたModelErrorを追加することで、この機能を実現。エラーメッセージを置き換えたいプロパティ名と、どういう書式でエラーメッセージを書き換えるのかのフォーマット文字列を引数に指定する。ここで先日作成したNamedFormat(いまだ最速?)を使ってみました(ソース中太字のところ)。これらを利用するようにアクションを少し変更。}※これまた1行追加するだけ。この状態でさっきと同じ入力エラーを発生させる。5.DefaultModelBinderのイベントちょっと、長くなってきたけど最後にRCになって変更されたModelBinderのイベントについて少し確認してみました。言っても、DefaultModelBinderを派生させて各タイミングでログを出すだけなんですけどね。Changes」にまるっと全部書いてることですけど、以下のメソッドをオーバーライドすることで、各イベントに合わせて処理を実行出来るようになってます。まんまですが、以下のような単純なクラスを書いて、PersonViewModelとWeaponsViewModelのModelBinderとして宣言してみました。}※BindingContextに復元に必要なすべての情報が入ってます。ViewModelの先頭で以下の属性を追加。{...}これを試すために、ルフィーのデータをそのままポストしてみました。出力された内容が↓こちら(インデントは自分で入れてます)。OnModelUpdated無駄に長い貼り付けになっちゃったけど、BindModelでインスタンス化する変数名(またはプロパティ名)を決定し、CreateModelでインスタンス作成、OnModelUpdating~OnModelUpdatedの中でプロパティ毎の処理。BindPropertyで対象を決めて、SetPropertyでセット。OnPropertyUpdatedが呼ばれる。ModelBinderの中で直接検証したりするときには、OnModelUpdatedでモデル検証、OnPropertyUpdated(セット前に検証するならOnPropertyValidating)でプロパティの検証でいいのかな。挙動の確認に手間取ったけど、リリース版でもModelBinderはこの流れだろうから、DataAnnotationsを使った検証はかなり有効な手段だと思います。強力になったDefaultModelBinder配列を保持するときに、コレまでHiddenにプレフィックス+”.Index”の名前でインデックス番号を保持しておかないと、きちんと復元してくれなかったのが、Index無しでもちゃんと復元出来るようになってる!DefaultModelBinderクラスのUpdateCollectionのコードのリファクタリングを進めて、Index値を内部でループで回すように変更した結果だね。0から始まる連番じゃないのは困っちゃう(-1から始めるとか1,3,5とか)けど、基本的に連番にするだろうから問題ないと思われる。そもそも連番じゃないなら、違うフィールド(Hidden)に持つなりするはずだし。おかないときちんと復元出来なかったのが、Indexそのものを使用しなくなったおかげで、Indexの出力も削除も不要に。ViewDataに入れとくかという事はやらないとね。ベータになって↑こういう問題があったのを↓解決させてた。あぁ~、そうか、こうすればいいんだ※AttemptedValueに直接null入れてるけど。(ValueProvider経由のValueProviderResultで取得)、代わりにModelStates.SetModelValueでキー名とValueProviderResultを渡すようになったので、この問題に気がついた次第です。}DefaultModelBinderがらみでもう一つ。フォームポストされるデータを、アクションの引数にクラスを使って復元させるとき、クラスにHttpPostedFileBase(ファイルアップロード)を含んでいると、そのままじゃ復元してくれない罠。あと、デフォルト動作としてValidateRequestが有効になるようになってる。以下のようなアクションをデフォルトで作成されるHomeControllerに定義。}んで、Indexページに以下のコードを書く。%>こんな感じの単純な物なんだけど。/>"なんて入れて送信すると...。見慣れたエラーが出るね。アクションにRCで導入されたValidateInputAttributeを指定して以下のような定義に書き換えると、ASP.NETの入力チェックがスルーされてコレまでと同じ動きをしてくれます。}Viewsフォルダのweb.configやaspxのPageディレクティブ指定のValidateRequestはページに対しての指定で、アクションに対する指定じゃないので、気をつけましょう。WebFormsの時はPageにPostBackされてたけどMVCだとControllerにPostするので、その違いがこんな所に出てきてます。デフォルト安全動作っていうのはいいことだね。ベータ以前から移行の場合は修正箇所は増えるけど。肝心のファイルアップロードといえば、以下の通り。普通に入ってるね(Vistaにサンプルで入ってる写真をポスト)。今度はアクションの引数に自作クラス(ViewModel)を用意して、DefaultModelBinderに復元してもらうようにする場合。以下のようなクラスを用意。}んで、アクションを以下のように書き換え。}そうすると今度はどうなるかと言うと...。※Viewは書き換えてないです。post.uploadFileはnullになってますね。これは、以下のようにViewを書き換えることでちゃんととれるようになります。%>input=fileのフォーム要素と同じ名前+".exists"のhiddenを作成して、valueに"true"を入れる。これだけなんだけど、なかなか気がつかないよね。今度はuploadFileがnullじゃな~い。MVCやってることは、Actionデリゲートを受け取るRepeaterヘルパーを書いて、その中でイテレータでデータを取り出して、Actionデリゲート(アイテム描画と区切り描画)を実行するものだけど、初回実行と2回目以降の描画を判定するイテレータの書き方がオシャレ。1.itemsをSelectでアイテム描画FuncのIEnumerableに変換。returnで新たなIEnumerableに変換。3.最後に、2のFuncのIEnumerableをSelectして実行させる。元はPhilさんの↓このエントリー。MVCちょっと、古いじゃないですか!と、思うところですが、気になったんですよね。いえば、このLambda(Actionデリゲート)には何が渡されてるんだ?と。WebFormViewEngineなんだから、aspxをc#(VB.NETかもしれないけど)に変換してるのは分かるけど、そういえば最近変換内容を確認してないな~、なんて。</p>↑こんな感じで書いたときに、どういうcsが出力されてるのか。WebDev.WebServerで動かしてる時って、どこにいるんだべか。悩む必要なんか無いことに気がついた。VSの出力ウィンドウに出てるじゃん。小さすぎて見えないね。シンボルが読み込まれました。ことで、上記フォルダを見てみる。それっぽい拡張子csのファイルを開く。コメントが多いから消したのが↓これ。</p>\r\n\r\n");※@__writeはHtmlTextWriter。%>はそのままのコードだもんね。ルートの登録でもアクションの定義でも"url"って書いてるのに、使ってないじゃないか!と。非同期サンプル書くのにURLを渡して、そのURLに対してWebRequest(WebClientでも)で取得するのを書こうとしてたんだけど、エラーになるのでとりあえずは固定で対応という逃げの一手。url)という感じでアクションを定義(前回のサンプルだとSync2とAsync3)する感じ。※WebRequestControllerっていうのがいたとして。リクエストするときのURLが昨日の例だとhttp://localhost/Proxy/http://farm1.static.flickr.com/131/353753310_1ed04f694c_m.jpgみたいな。そこはURLエンコードしとかないとさ、っていうんでhttp://localhost/Proxy/http%3A%2F%2Ffarm1.static.flickr.com%2F131%2F353753310_1ed04f694c_m.jpgみたいな。ゴルァ~!っと怒られるわけっす。ことで、調べてたんだけど、たぶんRouting(コレだ!っていう資料を見つけられなかった)。WebServerじゃないと思うんだけどどうでしょう。WebFormで以下のように書くとこれはちゃんととれるんだもん。}何がまずいのかな~、と。Encodingに書かれてる、使っちゃいけない文字も含まれて無いし。MVCのRoutingってcontrollerとかactionとかっていうデータを構築してくれて、GetVirtualPathで勝手にURLエンコードした文字列返してくれるじゃないっすか。(System.Web.Routing)その辺については特に書かれてないんだけど。Routeを登録するときにワイルドカード指定できるでしょ。ワイルドカード内のスラッシュ'/'はエンコードしないじゃないですか。思って、UrlEncodeしないで試してみたんですよね。':'は使えないんで除外します。したらちゃんとリクエスト受け付けてくれるんですよ('/'を2個連続はダメだけど)。このままだとアックションで取得出来るurlが"http/"で始まるんでちょっと感じ悪い。ルートの定義をちょっと変更。