2010年2月24日水曜日

ダイヤモンドは砕けない

ゆりかもめに初めて乗りました。

techdays2010 

わーい、自由の女神だー。あははー。あはははー。はぁ...。

台場でTechDays2010のBoFに出てきたんです。

4 の時代の Web アプリケーションを語ってみよう

小野さんとナオキさんと三人でのおしゃべりはTechEd 2009の時とあわせて2度目です。相変わらず打ち合わせとかしないのにはヒヤヒヤです。

ASP.NET 4になっていろいろすごいじゃないですか。その辺の話をしましょうよ、ということだったので、たけはら担当はもちろんASP.NET MVC 2。一応事前に書いておいたメモをブログにも載せておきます。

メジャーな部分

  • データ検証の方法がDataAnnotationsを基本にしたものになりました。

これまで通りIDataErrorInfoを実装したものも有効ですが、ValidationAttributeをモデルまたはモデルプロパティに指定(ModelMetadataで外部に定義したものでも可能)して、DataAnnotationsModelValidatorを使用するようDefaultModelBinderが変更になっています。
LinqBinaryModelBinderも標準実装に組み込まれたので、System.Data.Linq.Binary(SqlDbType.Timestampなど)をbase64でhiddenからポストしたときも、自動で復元されます!
またLinqBinaryModelBinder、基底クラスのByteArrayModelBinder(byte[]を復元)もFuturesから昇格です。

  • ASP.NET 4で組み込まれるSystem.Web.IHtmlStringを使えるようにMvcHtmlStringクラスが導入されました。

MvcHtmlStringのソースファイルを確認するととても勉強になります。まさに黒魔術。
すべてのヘルパーはstringではなく、このMvcHtmlStringを返すよう変更されています。
<%: expression %>としている場合、自動的にHtmlEncodeした結果がレンダリングされるので、今後はこれが主流になります(きっと)。
MVC2の実装でもそうですが、2.0ベースのSystem.Webにはそんなものないので、なので、ASP.NET 4じゃない場合はこれまで通り、Html.Encode(string)を使いましょう。
IHtmlStringの場合、<%: ihtmlstring %>となっていても、HtmlEncodeをかけずに出力するので、Partial HTMLをレンダリングする場合(ヘルパーのレンダリングとかも)は、IHtmlStringとして渡しておきましょう。

  • AcceptVerbsAttributeがHttpVerbs.Get/Post以外に、Put/Delete/Headにも対応するようになっています。

Get/Post以外は通常、ブラウザからは送信されないですが、HtmlHelper.HttpMethodOverride()をformタグ内で呼んでおくことで、hiddenにX-HTTP-Method-Overrideという名前で、メソッドオーバーライドを保持するようになり、POSTを使ったHttpVerbsの上書きができるようになります。
この辺の実装はHttpRequestExtensionsに用意されてるHttpRequestBaseに拡張メソッドとして実装しているGetHttpMethodOverride()がまるっと処理してくれるようになっています。
もちろんここも、AcceptVerbsAttributeの判定メソッドがoverride可能になっているので、独自のAcceptVerbsAttributeを実装することで、Railsライクに"_method"というオーバーライドを使うように変更することも可能です!
これは今までのバージョンではとても面倒な実装 にしないとできない部分でした。
ちなみに、RESTfulなController実装をするときに、これができないと、ブラウザからのリクエストと、その他のクライアントからのリクエストの処理を簡単に切り分けできなくてとても不便です。

  • JsonResultを返すActionの場合、HttpVerbsがPOSTであることが基本条件になりました。

セキュリティ的にゴニョゴニョらしいです。

  public ActionResult Json()
  {
    //var json = Json(new {result = "json!"});
    //json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
    //return json;
return Json(new {result = "json!"},JsonRequestBehavior.AllowGet); }

Getなら↑これで大丈夫。

修正:2010年3月9日

  • DefaultControllerFactoryからRequestContextプロパティが無くなりました。

依存性をなくすのはいいことです。その代わり、Controllerのインスタンスを生成するためのCreateControllerにRequestContextをパラメータとして渡すようになってます。
Mockを作る時が楽ちんです。

  • AreaをサポートするためにRouteData.Valuesで"area"が予約されるようになりました。

自分仕様でこの名前の値を使ってる場合は変更しておきましょう。

  • Html.Substituteは残念ながら使えなくなりました。
  • クライアントサイド検証が標準で組み込まれてます。

このクライアントサイドコードを生成するために、C#からJavaScriptを吐き出す
ScriptSharpが使われています。
コード生成に興味がある方は、参照してみるといいんじゃないかと思います。

Script#
http://projects.nikhilk.net/ScriptSharp

ボスが最近似たようなのを見つけてはしゃいでました。

SharpKit - Write C# instead of JavaScript
http://sharpkit.net/

  • Templateベース

DynamicDataと同じようにTemplateベースのモデルレンダリングを実装したHtml.EditorForやDisplayFor、プロパティベースのレンダリングを行うTextBoxForやLabelForなんかも目が離せない便利機能です。
System.ComponentModel.DisplayNameAttributeやUIHintAttributeを使ってカスタマイズもしやすくていいですね。

注目機能

  • ChildActionOnly属性

HtmlHelperのRenderAction/Actionからの要求しか受け付けないようにするActionFilter。

  • UrlParameter.Optional

Route登録時にRouteData.Valuesにキーすら含ませないようにするオプション。

Deep Dive!

一番注目したいのはASP.NETらしさをしっかり継承したProviderモデルへのリファクタリングです。マニアックな部分ですが、拡張性を考慮して、より自由どの高い開発を行えるよう沢山のフックポイントを提供するために実装されています。処理の流として入力→検証(Model)→処理(Controller & Model)→出力(View)
というフローになるそれぞれのつなぎ部分で拡張できる感じです。

MVCソースから"Provider"と付いているクラスを検索!

  • ModelMetadataProviders
    • ModelMetadataProvider
      • AssociatedMetadataProvider
        • EmptryModelValidatorProvider
        • DataAnnotationsModelValidatorProvider

ModelMetadataを取得するためのプロバイダ。
モデルやモデルプロパティに関するすべてのメタ情報。
ValidationAttributeの定義や、型情報、出力方法など。

  • ModelValidatorProviders
    • ModelValidatornProvider
      • AssociatedValidatorProvider
        標準のValidationAttribute用のAttributeAdopterクラスとDataAnnotationsModelValidatorを管理。
        • EmptryModelValidatorProvider
        • DataAnnotationsModelValidatorProvider
      • DataErrorInfoModelValidatorProvider
        IDataErrorInfoの実装に対するDataErrorInfoPropertyModelValidatorとDataErrorInfoClassModelValidatorを使って、検証の結果を取得する。
      • ClientDataTypeModelValidatorProvider
        数値型に関するNumericModelValidator。
    • DataAnnotationsModelValidatorProvider
    • DataErrorInfoModelValidatorProvider
    • ClientDataTypeModelValidatorProvider
  • IValueProvider
    • NameValueCollectionValueProvider
      • FormValueProvider
      • RouteDataValueProvider
      • QueryStringValueProvider
    • HttpFileCollectionValueProvider

ModelBinderがモデルにデータをバインドするさいに、値を取得する際に利用する。データの出所がどこなのかをModelBinderは意識しなくてもいいんです。

次に"Factory"とついたクラスを検索!

  • DefaultControllerFactory
  • ValueProviderFactories
    • ValueProviderFactory
      • FormValueProviderFactory
      • RouteDataValueProviderFactory
      • QueryStringValueProviderFactory
      • HttpFileCollectionValueProviderFactory

コントローラの生成用のControllerFactoryと、IValueProviderを実装したValueProviderFactoryたちの2種類です。これがリファクタリングした超重要な部分になります。

で、後は、これらを利用したサンプルの紹介という流れです。

MVC Presenter

↑これね。このサンプルそのものがWebアプリケーションになっていて、サンプルの実行そのものがプレゼンスライドの表示になるという仕掛けだったんですけど...。

プロバイダモデルがうんぬんくらいからちょっとツマラナイ話になってしまいましたね。今回はコードに関する説明がごっそりできてないので、MVC2の良くなった部分がサンプルのどこにどう使われているのか、全然伝えられませんでした。反省してます。Zipで圧縮したのをアップロードしてどうのこうのとか、実はパワポじゃないんだよとか、それ以前に何を伝えようとしてるのかが、伝えられてなかったです。ドン引きってこんな時に使うのかな。

心が折れて、帰りのゆりかもめでは夕日が目に染みた。泣いてなんかないやい。

JOJO'S BIZARRE ADVENTURE Part4 Diamond is not Crash

けど、魂を砕くことはできないぜ!また機会があったら、今回の教訓を生かし、もっと参加者に楽しんでもらえるよう的を絞ったプレゼン+コードにしようと思います。

サンプルの説明を近いうちここにエントリしようと思います(ここ知らない参加者の方には申し訳ないですが)。

2010年2月15日月曜日

Sauce IDE+RCはFirefox+Firebugでの使い勝手がとてもいい

Webアプリのテストの定番といえば、SeleniumかWatiNなのかな、というレベルのWebアプリテスト初心者ですが、Seleniumの後継にあたる(この辺あいまい)Sauceっていうのを試してみたよ!

Products - Sauce Labs

まだないけどいつかDimeCastsでビデオが公開されるに違いない。

試すにあたり、何かしらプログラムが必要だろうと、昨日ハンサムスーツを見ながらシンプルブログ(全然ブログじゃないというのは本題じゃないYO!)を作ってみた。

タイトルいれて本文入れるだけね。せっかくだからASP.NET MVC RC2で。話はそれるんだけど、RC2のItem Templateはイマイチかも。TextBoxForなんかは新しいTemplateベースのDisplayForやEditorForに揃えてくれるともうちょっと素敵さが増すんじゃないかな~。せっかくDataAnnotationsでDisplayName属性を指定してもLabelForで展開しないとそこから表示名を取得してくれないじゃないっすか。Listテンプレートは面倒かもしれないけど、そうじゃないところはDataAnnotationsの属性を生かせるヘルパーを使った物に統一してもらいたいデス。せっかくUIHintでカスタムEditorTemplatesを指定しても効いてくれなくてちょっと切なかった。

sauce1

↑一覧ページ。殺風景ですいません。

sauce2 sauce2_2

↑新規ページ。同じフォーマットで編集ページもあるよ。ここで入力エラーがあると、DataAnnotationsがバシっと効いてくれるのがV2の素敵なところ。

sauce3

↑新規登録すると一覧に表示されるよ。殺風景ですいません。

sauce4

↑詳細ページ。コレといって何も無いですね。あ、本文にはHTMLが入るイメージなのでHTMLエディターをEditorTemplatesで作ってもいいね。今ならMarkdownのほうがオシャレかの~。

sauce5

↑削除ページ。削除すると、一覧ページに戻るだけ。

とりあえずこんなのを作って、これに対してテストを実施させてみようじゃないですか。

実行したいテストは以下の通り。

  1. 1件も登録されてない状態で新規登録
  2. 1件以上登録されてる状態で新規登録
  3. 未入力状態でCreateを押してモデル検証をエラーにする
  4. 登録されているすべてのエントリを削除する

今回認証は無しで。

そもそもSeleniumすら使った事ないけど、まずはSauce LabsのProductsトップにあるビデオを確認。ふ~ん、ってなもんです。とにかく、Sauce IDEをFirefoxにインストールして、Sauce RCをローカルPCにインストール。まずはこれでいいっぽい?う~ん残念。Selenium RCをダウンロードしておかないとUnit TestをC#で実行できないみたいです。

Sauce IDEがテストレコーダーで、Sauce RCがテスト実行サーバー、Selenium RCがテストを実行させるのに必要なアセンブリ(Sauce RCサーバーとの通信を行う)を提供、って具合の理解でよろしかろうか。Sauce RCはJavaアプリで、Webベースの管理インターフェースもありそこでテスト実行ブラウザやその他設定の変更ができると。

sauce6

テスト実行する場合、ブラウザを起動してそのブラウザをコントロールすることでテスト実行と結果取得する仕組み?Sauce IDEが表示してくれる、C#のコードはNUnitを基本になってて、そこはMSTestでよしなに動くようにチョチョッと変更。大枠はTestInitializeでDefaultSeleniumを初期化するようにするのと、TestCleanupでDefaultSeleniumの停止処理、あとはひたすらテスト書く。

    private ISelenium selenium;
    private StringBuilder verificationErrors;

    [TestInitialize]
    public void Setup()
    {
      selenium = new DefaultSelenium(
"localhost",
4444,
"*firefox",
"http://localhost:20337/"); selenium.Start(); verificationErrors = new StringBuilder(); } [TestCleanup] public void TeardownTest() { try { selenium.Stop(); } catch (Exception) { // Ignore errors if unable to close the browser } Assert.AreEqual("", verificationErrors.ToString()); }

あと、ちゃんとSelenium RCのダウンロードしたアセンブリをテストプロジェクトでは参照設定しとこうね。

DefaultSeleniumの1個目と2個目のパラメータはなんじゃらホイ。3個目はブラウザっぽくて4個目がテスト対象アプリのアドレス。どうもこの1個目と2個目がSauce RCのサーバーらしい。RCの制御用Webインターフェースはhttp://localhost:8421/ でポートが8421。実際のアプリはポートが20337と、いろいろ仕組みを理解しておかないと~。Sauce OnDemandっていうクラウドサービスもあるからね~。

上記初期コードを書いておけば、あとはIDEの出してくれるC#コードをコピペっちょ!なんだけど、それだと思ったようなエレメントを指してくれない。実行結果のHTML全体に文字列が含まれてるかをチェックするIsTextPresentなんかを使うのはテストとして成立しないっすよね。

なので、エレメントの参照はCSS SelectorかID指定かXPath指定がオーソドックスな指定の仕方になるかと思うわけです。でね、Firebugってさ、前バージョンまでは別途Plug inを入れないとXPathの取得ってできなかったけど新しいバージョンになって”XPathをコピー”っていう機能が標準で実装されてるじゃないですか。

sauce7

↑エレメントのXPathが表示されてるから↓右クリックでコピーだ!

 sauce8

で、対象となるエレメントを特定するためのXPathは何の苦労もなく取得できちゃうので、テストコードのアサーション部分やClick対象のエレメントなんかもこれを使って一意に特定だぜ!

    [TestMethod]
    public void A_First_Entry()
    {
      selenium.Open("/");

      selenium.Click("link=Create New");
      selenium.WaitForPageToLoad("30000");
      selenium.Type("Title", "first");
      selenium.Type("Body", "最初のエントリ");
      selenium.Click("//input[@value='Create']");
      selenium.WaitForPageToLoad("30000");

      Assert.IsTrue(
        selenium.GetText("xpath=/html/body/div/div[2]/table/tbody/tr[2]/td[2]") 
        == 
        "first");

      // 最初のDetailsをクリック
      selenium.Click("xpath=/html/body/div/div[2]/table/tbody/tr[2]/td/a[2]");
      selenium.WaitForPageToLoad("30000");

      Assert.IsTrue(
        selenium.GetText("xpath=/html/body/div/div[2]/fieldset/div[4]")
        ==
        "最初のエントリ");
    }

↑こんな感じで入力値と入力結果を比較してちゃんとHTML的に想定通りの結果が生成されてることをテストコードに書けちゃうのが素晴らしい。無知って怖い。これまで自分は一体なにをしてたんだと言いたい。

これらテストを実行する時に、MSTest(かReSharperのテストのどっちがそういう仕様なのかは知らないけど)だとテストメソッド名順に実行されるので、テスト内容が直前の実行結果に依存するような場合は、プレフィックスを決めておく必要があったりしそう。テストクラスそのものを分けてしまうのもありだけどね。

実行すると、ブラウザが起動してあとは勝手に処理してくれるっす。

sauce9  sauce10

↑これがFirefoxで実行してるところ。

sauce11

↑こっちがIEで実行してるところ。

ブラウザの切り替えはTestInitializeの設定じゃなくて、Sauce RCの設定画面が優先、っぽい。何にせよ、初めてのWebアプリのユニットテストとしては好感触。今後も精進していきます。

今回のサンプルアプリとテストコードは↓こちらからどうぞ。

※ちなみにサンプルアプリ自体のテストコードはないのであしからず。

2010年2月13日土曜日

OptionalなRoutingパラメータ

ASP.NET MVC 2 Optional URL Parameters

Philさんのところで取り上げられてるUrlParameter.Optional。

RC2で既に実装されてて、標準のプロジェクトテンプレートが生成するGlobal.asaxのルーティング登録で使われてますね。どういう機能なのかはPhilさんが書かれてる通り、Routing登録時に初期値をセットせず、ルーティングデータにキー自体含めなくするためのモノですね。なるほど~。

      routes.MapRoute(
        "Default", 
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = "" }
      );

これまで通りのルーティング登録だと上記のように空文字列を指定したりするところをUrlParameter.Optionalを使って下記のように登録する。

      routes.MapRoute(
        "Default", 
        "{controller}/{action}/{id}", 
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } 
      );

そうすると、デバッグでルートURLにアクセス(~/Home/Indexか何も無しか)すると以下のようなルーティングデータとなりました。

urlparam1

id=””とした場合は、ルーティングデータにidが含まれるのに対し、Optionalの場合はキーすら含まれず、controller名とaction名のみです。

      routes.MapRoute(
        "Default", 
        "{controller}/{action}/{id}", 
        new { controller = "Home", action = "Index" } 
      );

ちなみに↑これはダメっす。idがUrlに含まれない限り解決できないから、ちゃんとすべてのパラメータを指定しなくてもアクセスしたい場合には使えないデス。

なんとなくRoutingの機能が拡張されたのかな?と思えるところですが、MvcHandlerのProcessRequestInitで呼び出してるRemoveOptionalRoutingParametersがOptionalならRouteDataから削除してるんですね。賢い!

これをいじってる時に気がついたんだけど、HtmlHelperにValidation/ValidationForなんてのが追加されてるじゃないですか。RCからなのかな。いったい何に使うのかいまいちよくわからないな~。FieldValidationMetadataをどうしたいんだろう...。クライアントバリデーションに絡んでんのかな~。ViewContext.FormContext.FieldValidatorsに入るはずなのにViewで実行しても特に変化が見られない。使い方間違ってるのかの。FormContextってそもそも誰ぞな。DataAnnotationsModelValidatorのエラーメッセージが日本語なのはなんでっすかね。

2010年2月11日木曜日

お試しAdWords

1月の初め頃だったような、記憶が曖昧なんですけどGoogleからAdWordsのお試し券5000円分が送られてきました。個人的に全く使い道を思いつかなくてすておこうと思ったんだけど、ずいぶん前にamachang(http://d.hatena.ne.jp/amachang/20090113/1231827150 Google Adsense の件について - IT戦記)がAdWordsにブログを広告する実験をしてたのを思い出したので、マネッコマネマネして自分もここのブログを無駄に広告してみようと試してみた。

途中で一度キーワードを変更したり、上限クリック単価を上げてみたりと調整したものの、目的もなくやってるもんだからそれがどうしたと自分にツッコむ以外することもない。

adwords2

↑こんな感じの設定にしておいて、5000円使い切るまで放置!

にしたかったんだけど請求を後払いにしないとお試しできないらしく、使い切るまで完全放置にするのは難しいみたい。すごく長い期間表示するようにして気がついたら請求額が5000円超えなんてことになったら無駄使いにも程がありすぎるし。数日様子をみて1ヶ月くらいは大丈夫かなと期間は1/10~2/5にセット。

adwords1

2種類の数値クリック数と表示回数を表示したのが↑。まぁ、見事な相関関係があります。そりゃそうか。そもそも入札してるキーワードが良くないね。わざわざ広告をクリックしたいようなコンテンツじゃないし、こんな情報を探してる人なら普通に検索しそう。だって”ASP.NET MVC”で検索して出てくる広告でデベロッパーが欲しいのってコンポーネント的なモノだったり書籍だったり以外ないと思うし、そもそもそんなキーワードで検索するようなリテラシーの人が広告をクリックすること自体が疑わしい。

と、言う訳で全くもって試す内容に失敗したAdWords初体験の巻でした。

途中で、ナオキさんに見つかって”クリックしないで!”というやりとりが唯一面白かったところかの~。

  • クリック数 ー 119
  • 表示回数 ー 326,365
  • クリック率 ー 0.04%
  • ご利用金額 ー \4,763

結構ギリギリ金額じゃん。

2010年2月6日土曜日

動的なRouting登録の素敵な方法

MVC V2 RC2が出てるのに関係ない話です。

Editable Routes Using App_Code

少し前にエントリされてたけど、ずっと放置してたです。そもそもEditable Routesで最初の実装サンプルが出てた話。

何をしてるのかというと、動的なRoute登録をするのにいちいちビルドし直さずに、Routes.cs ファイルをBuildManager.GetCompiledAssemblyで実行時にコンパイル。Routes.cs自体にCacheDependencyでファイルの変更監視をさせておき、csを書き換えた時点で動的にコンパイルしなおす(上記BuildManagerで)。そこから取り出される IRouteRegistrar実装がルーティングを登録するようにしておくことで、Global.asaxにルーティング情報を保持しなくて良くなるし、アプリケーションの機能とルーティング情報を分離して、柔軟な環境にできる(Subtextがそうなってるのかね)んですね。

で、 CacheDependencyでファイルを監視させるときにFileSystemWatcherを中で使ってて、それがFullTrustじゃないと実行できないから不便だねっていうのがこのエントリの本題みたい。解決方法としてはシンプルに~/App_Code下にRoutes.csをおいて、自分では監視しないようにするところ。そうしとけば、MediumTrustでも動くんだよと。なるほど。

~/App_Code配下のファイルなら更新時にAppDomainがシャットダウン(CodeDirChangeOrDirectoryRename)して再起動されるから、その ASP.NETの仕組みに任せとかばいいじゃんと。素敵だす。その場合、コンパイルは勝手にされるから、 BuildManager.GetCompiledAssemblyじゃなくてBuildManager.GetTypeでAppDomainに読み込まれてるTypeを取得(もちろんIRouteRegistrarの実装)して実行すればよろしな流れ。

2010年1月24日日曜日

ControlBuilder.ProcessGeneratedCodeを活用

Angle Bracket Percent : Take your MVC User Controls to the next level

確かにNext Level。

普通、ascxをレンダリングするときにはRenderPartialヘルパーを使うところだけど、 T4MVCとか使ってないとパスがマジックストリングになりますね。T4MVC使ってれば、そんなことにはならないけど、そもそもascx単位の RenderPartial的なヘルパーを用意してしまうほうがいいんじゃないの?という話。

で、それを実現するためにいちいちコントロール毎にヘルパーを定義するのはバカらしいってことで、動的生成ですよ。これもまさに黒魔術。

App_Codeの動的コンパイルに介入(ControlBuilder.ProcessGeneratedCodeのoverride)して、実行時動的に拡張メソッドクラスを作り出してます。CodeSnippetTypeMemberとか初めて見た。

まずは、FileLevelUserControlBuilderを派生させたUserControlHtmlHelperControlBuilderを作成。このクラスが動的生成をおこなうクラスで、ProcessGeneratedCodeをoverrideしてCodeTypeMemberCollectionにヘルパークラスのCodeSnippetTypeMemberを追加してます。動的クラスのコーディングはサンプルと言うことでシンプルな実装ですね。

            dummyClass.Members.Add(new CodeSnippetTypeMember(String.Format(@"
        }}
    }}
    public static class {2}Extensions {{
        public static void Render{2}(this System.Web.Mvc.HtmlHelper htmlHelper{3}) {{
            var uc = new {0}();
    {5}
            uc.RenderView(htmlHelper.ViewContext);
        }}

        public static string {2}(this System.Web.Mvc.HtmlHelper htmlHelper{3}) {{
            return {1}.RenderHelper(htmlHelper.ViewContext, () => Render{2}(htmlHelper{4}));
    ", codeGenUserControlFullTypeName, userControlHtmlHelperFullTypeName, helperMethodBaseName, paramBuilder, callParamBuilder, fieldAssignementBuilder)));

 

続いて、このControlBuilderを使ってくれるようにASP.NETに指示をださなきゃいけないんだけど、それってどこでやってるんでしょうね。ぱっと見、以下のクラスの属性指定?

    [FileLevelControlBuilder(typeof(UserControlHtmlHelperControlBuilder))]
    public class UserControlHtmlHelper : ViewUserControl {
        // Helper method which renders the code in a temporary writer and returns it as a string
        public static string RenderHelper(ViewContext viewContext, Action render) {
            TextWriter oldWriter = viewContext.Writer;
            var tmpWriter = new StringWriter(CultureInfo.CurrentCulture);
            viewContext.Writer = tmpWriter;
            try {
                render();
            }
            finally {
                viewContext.Writer = oldWriter;
            }

            return tmpWriter.ToString();
        }
    }

ここが実行時に勝手に解釈されるようになってるのかな~。動的ヘルパーを生成したいascxのInherits="MvcUserControlHtmlHelpers.UserControlHtmlHelper"という記述が通常と違う部分なのはわかるんだけど。

FileLevelControlBuilderAttribute コンストラクタ (System.Web.UI)

実行時に初めてコードが生成されるから、コーディング時には↓こんな感じでメソッドないぜと怒られる。

cb

実行したところで、VSはそんなコードしらないんだってさ。

cb2

そもそもMVCのViewUserControlクラスがこれと同じようにViewUserControlControlBuilderを使ってるんじゃんね。でも、BaseTypeをうわがいてるくらい(目的はよくわかってないデス)ですね。

追記:ascxなりaspxのベースクラス(ViewPage,ViewUserControl)の派生元(親)クラスを指定してるんでした。そりゃそうだ。

<%@ Control Language="C#" Inherits="MvcUserControlHtmlHelpers.UserControlHtmlHelper" ClassName="Gravatar" %>

<script runat="server">
    // Declare the paramaters that we need the caller the pass to us
    public string Email;
    public int Size;
</script>

<%
    // Build hash of the email address
    // Note: in spite of its name, this API is really just a general MD5 encoder
    string hash = FormsAuthentication.HashPasswordForStoringInConfigFile(Email.ToLower(), "MD5").ToLower();

    // Construct Gravatar URL
    string imageURL = String.Format("http://www.gravatar.com/avatar/{0}.jpg?s={1}&d=wavatar", hash, Size);
%>

<img src="<%= imageURL %>" alt="<%= Email %>" title="<%= Email %>" />

※人さまのコードをこんなにコピペしていいのだろうか...。

上記のApp_Codeに作成されてるGravator.ascxのscript blockから正規表現でpublicなプロパティ定義を取り出して、ヘルパーのパラメータ生成に利用してるところも見逃せない。

T4MVCでの動的生成、Emitによる黒魔術に次いで、新たな黒魔術として覚えておくのもいいんではないでしょうか。

※ASP.NET MVCに限らずASP.NETならすべてに適用できるのがミソですよ!

2010年1月23日土曜日

ハーフゾーン+ハーフマンツー

ホッケーを愛する日本全国の人々へ:COVERAGE(カバリッジ)~最終ステップ「5 on 5」 - livedoor Blog(ブログ)

必読。

一度で理解できないとあきらめず、理解できるまで何度でも読もう。アイス・インラインの区別、人数の差異は関係ない内容。

難しい・わからない・理解できない。それなら、理解できるまで考えればいいだけだから。

あぁ~、プレーオフもないし暇すぎる。来期な~。いいんだけど。いい年こいて精神論でホッケーするのはバカらしいと思う今日この頃。

dotnetConf2015 Japan

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