ホッケー2009年11月29日日曜日internalのテストinternalクラスのテストってどう書くのがセオリー?まさかReflectionってことはないよね?今まで知らなくてズルッコしてpublicにしてたんだけど、まさかこんな簡単な方法があったとは。}}普通にこんなclassを作ると、Testクラスで↓こうなるもんね。AssemblyInfo.csに↓この行を追加。InternalsVisibleTo("Samples.Tests")]※テストプロジェクトのアセンブリ名。今回はSamples.Testsっていうのを作ったのでこの名前。コンパイルエラーも出ないし、テストも普通に通る。なんかもう今までのコードを全部書き直したくなってきた...。TDDTimerでいいんすかちょっと試しにCacheDependencyを書いてたんだけど。}}絶対時間でExpireさせるCacheDependency。Timer使っていいのかな。なんかキャッシュに同時に1万とかデータ入れると1万のTimerだよね...。ブックスこっちはSystem.Threading.Timer使ってるな...。C#2009年11月20日金曜日Html.ActionとHtml.RenderActionなんともエントリを書かなすぎでした。シバトラ読みふけったり。Philさんが面白い機能の紹介をしてくれてたので少し確認。確認してタンブラのほうで軽くコメント書いとこうと思ってたけど、内容が面白すぎたので、こっちに書いてみるっす。プロジェクトを作ろうとVS立ち上げたけど、なんかちゃんとできない。なんで~!と思ったらベータインストールしてなかった...。ソースだけダウンロードして見てるだけな楽しみ方もありですよね。Preview2をアンインストールしてからベータ入れて見るものの、V1の時と同じように日本語環境にはちゃんと入ってくれなかった。/installvatemplates。準備も出来たところで、サブジェクトの機能について簡単に説明。Html.Actionはアクションの実行結果をMvcHtmlStringにして返してくれるもので、Html.RenderActionは同じくアクションの実行をしてくれるけど、こっちは文字列としてではなく現在のレスポンスストリームに結果を書き出してくれるものです。Futuresに入ってた機能ですけど、出世してリリースアセンブリに含まれるようになりました。見、Html.ActionLinkと勘違いしそうなヘルパーだけど、中身は全然違う物です。ソースではChildActionExtensionsにまとまってるので興味のある方はぜひ。中では何をしてるのかというと、Server.Executeです(なのでPageの派生クラス使ってる)。IHttpHandlerとしてラッピングしてProcessRequestを実行してます。この辺の仕組みはFuturesの頃から変わってないです。コードはカッコ良くリファクタリングされてますが。ちなみに今回のリリースには非同期アクション実行も含まれてるので、IHttpAsyncHandlerにももちろん対応してます。ここら辺の実装がHttpHandlerUtil.WrapForServerExecuteですね。500エラー以外のHttpExceptionをServer.Executeが伝播してくれないらしく、がんばった感じが見て取れます。}↑こんなアクションメソッドをHomeControllerに定義しておき、Home/Index.aspxで以下のように書いておく。%></div>そうすると出力されるのは↓こんな感じです。もって、このPartialアクションメソッドはそのまま"/home/partial"ってやっても呼び出せてしまいますね。Ajaxでの部分更新に使うならいいけど、そうじゃなくHtml.Action/RenderActionでしか使わないならChildActionOnly属性をアクションメソッドに指定しておきましょう。ブラウザからは直接アクセス出来なくなります。なんで~!と気になったらソースを確認。ChildActionExtensions.CreateRouteDataでServer.Execute対象のRouteDataを生成してるんですが、その時に現在のViewContextをRoute.DataTokensに入れてます。Areaでnamespaceを渡して違うnamespaceのControllerをRoute登録するのと同じやり方ですね。そのDataTokensが存在してるのかどうかをChildActionOnlyAttributeがチェックして存在してなければ、実行出来ないようにする仕組みです。%>で実行してるのがわかるでしょうか?これはつまり文字列をそのままViewの一部に埋め込んで最後にまとめてレスポンスですよね。%>コード実行の書き方です。レスポンスストリームに書き出すものです。Philさんが書いてる通り、部分的なCacheを有効にしたいときはRenderActionを使う必要があります。Response.WriteSubstitutionを使った出力はFuturesに別途用意されてるね。Html.Substitute。2まんまな感じのエントリで申し訳ない気持ちもするんですが、Preview1から全然使い方の変わってる部分でもあるんで、エントリしとこうと思った次第です(もう役にたたない情報ですがPreview1の時のエントリはこちら)。言い訳から書き出すのもすっかり定着してきた感が否めない...。2に関してはMSDNにドキュメント整備も進んでるんで、そちらも参照すると更に理解が進んでいいですね。Project今回からRouteCollection.MapAreaRouteは廃止され、代わりにAreaRegistrationクラスが導入されてます。同じ目的でAreaRegistrationContextも追加されてます。メインはAreaRegistrationですが、このクラスを派生させたクラス(サンプルはRoutesになってるけど名前は何でもいいです)各エリアフォルダのルートに作成しておき、AreaName/RegisterAreaをそれぞれoverrideして、Route登録時にNamespacesが自動登録されるようになる仕組みです。特徴的なのは、プロジェクトを分けなくてもエリア機能が使えるようになってるところです。これまでわざわざ別プロジェクトを作成しないとエリア機能を使えなかったんだけど(規模が大きくなっても開発効率が悪くならなくても済むようにプロジェクト分割は常套手段ですよね?)、あえて分割を強制しなくなりました。文章だと全然意味わからないですね。MvcApplication1という名前のプロジェクトの中にAreasフォルダ(これも名前は何でもいんですが、慣例としてあえてAreas)を作成。その中にエリア分割したいフォルダを更に作成。今回ならSub1とSub2。その中にControllersとViewsをそれぞれ作成。仕組みをわかりやすくするためにController名はHome、Action/Viewの名前はIndexとして作成しておきます。これで、3つのHome/Indexの組が出来たことになります。各Indexアクションは以下の通り。}ちょいちょいViewに渡すモデルを変えてるのは実験のためです。Sub1とSub2にそれぞれAreaRegistrationクラスを作成しておいときます。}最後にGlobal.asaxのルート登録部分に以下のコードを追加。}前回のエントリにも書いたように、namespacesを指定しないと各エリアのHome/Indexと区別出来なくてエラーになってしまうので、標準のHome/Indexに対してきちんと指定するようにします。※ちゃんとnamespacesを指定しないと↑こんな感じでエラーになるっす。えと、眠くなってきた。あと少し!すべてのIndex.aspxは以下で統一。null)%></asp:Content>で、Sub1の時だけViewPage<ModelsLibrary.Class1>を指定してModelの型を変えておきます。なんでこうするかというと、単に以下のエラーが起きるのを確認したかったから。Overflowこの現象を起こしてみたかったんデス。これを回避するには~/Areas/Sub1/Viewsフォルダに~/Viewsフォルダにあるweb.configをコピーしておく事。これを忘れるとジェネリックで他のアセンブリ(じゃ無かったとしても?試してみてね!)に含まれるモデルクラスを指定すると上記エラーが発生。pageParserFilterTypeが処理してくれないって事です。ここまでやって実行したのが↓この画面。標準/Sub1/Sub2それぞれのHome/Indexがちゃんと判別できて実行されてるのが確認出来ます。ちなみにAreaRegistrationクラスの派生クラスの名前と、~/Areasフォルダが何でもいい理由というのが、AreaRegistration.RegisterAllAreasのソースに書かれてる内容から判断出来ます。何をしてるかというとBuildManagerWrapper.GetReferencedAssembliesでアセンブリの参照出来るすべてのTypeの中からAreaRegistrationの派生クラスを抽出(IsAreaRegistrationTypeをPredicateとして)してるからですね。後はCreateContextAndRegisterでNamespacesを追加した上でoverrideされてるRegisterAreaを呼び出して、ルートを登録するだけ。AreaRegistration派生(ちなみにAreaRegistration自体はabstractなので除外されます)をすべて抽出するので名前は自動でわかるようになってるというオシャレ実装。素敵だね!DataAnnotationsのカスタム属性実装とセットになったソースは以下からどうぞ。最初の目標達成!さっきブログに書いて気がついたんだけど、Feedの購読数がなんかキリ番。目標はそこじゃなくてですね、最近始めたstackoverflow.comへの書き込みでReputationが最初の目標の100に今朝やっと達しました。次は200?いやいや500としよう。何気に英語の勉強もかねて翻訳サイトなんかも駆使しつつ書き込んでいこうと決めて3週間。MVCのフォーラムなんかがあるなら、分かる範囲で書き込みして行きたいところデス。どこかにあるならだけど。話は変わりますが、うちのボスが書いてるのでこっちでも書いておきます。Fireされたわけじゃなくて、普通に人員増強です。募集要項なんかの細かい話はボスのエントリか募集サイトを見てもらえればいいかと思います。自分新人なんでその辺の話はよくわかんないです。株式会社クロスワープ/システムエンジニアの転職情報ただ、これだけは言っておきたい。たけはらのファンかどうかは採用に関係しないので明記する必要はありません!こんな感じの職場で一緒に開発してもいいというドMな方はぜひお気軽に応募していただければと思います。assemby小野さんに振られたので↑こちらのエントリに書かれてるViewStateについての調べてみました。いうきっかけが無いとソースを追いかけない自分に少し反省。MVCにViewStateを持ち込むのか?と、疑いたくなるようなエントリだけど何となくサンプルとして提示されてるコードが怪しい。そのまま転載させてもらうと↓こうですよ。%>Html.Serialize(“person”,Model)ってなんか怪しいですよね。おまえ、ホントにViewStateを吐いてくれるのか?と。1つのViewに何個も書いたらどうなるんだよ、とか、ポストバックしたControllerでコントロールツリーを構築するのか、とかなんやかんやデス。考えててもラチがあかないので、サンプル書いて試してみました。}まずは、前回も使ったDrinkクラスにSerializable属性を追加。これつけないとそもそもシリアライズ出来ないです。どこでシリアライズしてるのかFuturesのソースを追いかけると、MvcSerializerクラスで実装してます。ちなみにシリアライズの方式としてPlaintext、Encrypted、Signed、EncryptedAndSignedの4種類があり、初期値はPlaintext。これはSystem.Web.UI.ObjectStateFormatterを使ってシリアライズしてて。おや?マジViewStateなのか?まぁ、いいや。Serialize属性をつけるわけですが、上記クラスに値を入れてサンプル通りにView書いて実行しても何も出力されない...。%>サンプルだからなんかおかしいのかな~。気になるので更にソースを読み進めると、Html.SerializeはそもそもMvcHtmlStringクラスを返してきます。...%>出力に向けた実装ですよ。IHtmlStringってヤツですよ。3.5にはそんなもの無いので、Futuresの実装はインターフェースは無しバージョン。ことは、単純に実行したらレンダリングされるわけじゃ無くて、ToString()かToHtmlString()で取得して、それをレンダリングするようにしないとちゃんと出力されないわけですね。わかれば、以下のように変更。%>これでちゃんと出力されました。以下のようなModelを渡して結果を見てみます。}↑これが↓こうなります。/>バリバリBase64エンコードされてViewStateっぽいです。これを復元させるコードがどうなるかと言うと、↓こうなります。}このDeserialize属性クラスが何をしてるか、ってことデスよね。してもソースを確認すると、そこには...。}単なるModelBinder...。Page.ViewStateなわけじゃないですね。シリアライズするためにViewStateと同じものを利用してるだけです。最初の方にも書いたけど、ViewStateと同じようにシリアライズ出来るようにするためにEncryptedやSignedが指定出来るようになってるって事です。PageStatePersisterっていうクラスを定義してて、その中でPageクラスのインスタンスを生成し処理させてます。AntiForgeryDataSerializerでも同じ事してたりしてちょいビックリ。そんなことはいいとして、これが完全に独自のModelBinderになってしまってるので、これを使う場合にはDataAnnotationsが効かない。MVCにViewStateを持ち込むんだ~、と怒り心頭な方!ご心配なく。WebFormsでいうところのViewStateでは無かったです。Futuresのソースみてて気がついたんだけど、AsyncControllerがFuturesに戻されてる。Expression系のユーティリティクラスがたんまり入ってて何やら面白そうな予感がしますが、それはまた今度ってことで。入力検証がDataAnnotationsを前提にした設計(IDataErrorInfoじゃ物足りないしね)になってるのと、Templateベースのモデル描画がV2での重要ポイントですよね。入力検証にxValと同じようにModelValidatorProviderとクライアントサイドバリデーションにjQuery.validateが導入されてのが興味深い。そもそもV1にも追加コードとして公開されてるDataAnnotationsModelBinderがあって、System.ComponentModel.DataAnnotationsのValidationAttrbiuteを使えるようになてます。残念ながら標準アセンブリじゃなく別途配布されてるDLLを参照設定して使うようにしないと、全く機能しないというのがあります。何でかというとValidationAttributeクラスの実装が違うのと、その検証属性の実行方法が違うから。validationResult)があります。入力値を取得して検証するだけなら対象となる、プロパティ値があればそれで事足りるかも知れないけど、たとえばCompareValidatorは実装できないですよね。同じモデルインスタンス内の他のプロパティを見れないと比較できないじゃないですか。V1の場合は、特殊なアセンブリだからいいかもしれないけど、V2は標準アセンブリだからVaridationAttribute実装にValidationContextなんて無い(実はある?)。先日1.0がリリースされたxValが使用するアセンブリも標準アセンブリ参照だから当然インスタンス参照なんてない。言うわけで、V1でxValを使うときの入力検証でモデルの他プロパティを参照する際のCompareAttributeクラスを書いて遊んでみました。何に使うのかは後で考える。モデルインスタンスを渡せるIsValidを実装するためのインターフェイス定義。ValidationAttributeとそのインターフェースを実装するCompareAttributeクラスを定義。}CompareAttributeを使うモデルクラスの定義。他にも必須チェックやら付けてみる。}続いて、xValのサンプルを参考に、入力検証を実行するためのDataAnnotationsValidationRunnerクラスを実装。}なんかちょっとダサいけど、まぁ、その辺はセンスが無いってことで勘弁してください。最後にControllerにアクションを追加してコードは完成。}実行したのが↓これ。P2で同じことをしてみようと思います。こんなに違うのかと思えるほどDataAnnotationsの組み込みと、ModelValidatorProviderに感動です。CompareAttributeクラスとモデルクラス(Drinkクラス)は一切いじりません。追加で作成の必要なクラスは以下の1つのみ。