2010年7月4日日曜日

Razorのポテンシャル

Introducing “Razor” – a new view engine for ASP.NET - ScottGu's Blog

↑これ読みました?

Razor View Syntax

↑これも。

これね、よくよく考えたらかなり凄いことに取り組んでるんだと思います。そもそもASP.NET MVCはASP.NETのフレームワークにのっかったものですよね。それはどういう事かというと

  • HttpApplicationのパイプラインで処理
  • HttpContextで現在のリクエストコンテキストに関する全ての情報にアクセス可能
  • System.Web.UI.PageがIHttpHandlerの実装としてPageパイプラインを処理しつつレンダリング

ですね。ASP.NET MVCのViewでは、このフレームワーク(Pageに関する部分)の使い方で変わった部分はほとんどないんですが、構造上使われなくなったものといえばコントロールツリーの構築とポストバックです。コントロールツリーに関しては「runat="server"なコントロールを使ってる場合には作られるじゃないか」と言われればそうなんですが、そこはPageクラスがそうだからで、Viewの性質として必須な機能じゃないのでWebFormとの対比という意味では使ってないと言うことにしましょう。ポストバックですが、これは特にMVC2になってから大きく変化のある部分で例えば以下の単純なViewでもIsPostBackはtrueになりません。

<%@ Page Language="C#" 
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage" %> <script runat="server"> void Page_Load() { if(IsPostBack) { // PostBack eventはいずこ?
// ここには決して入ってこない } } </script> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <form runat="server"> <asp:Login ID="Login1" runat="server"> </asp:Login> </form> </asp:Content>

Viewのレンダリングの仕様が変更になってるのでWebFormViewにはイベントがイベントとして認識できない形になったから(TextWriter渡しのRender呼び出し)。ようはControllerに対するPOST/PUT/DELETE/GETのHttp MethodがViewに影響を及ぼすことを是としない、という意志の表れなんじゃないか~、なんてな。

話がずれましたがRazorはこのPageクラスに依存しないViewEngineとして造られてるので、Page Pipeline(イベント)は処理されない、コントロールツリーも構築されないという事になって、ASPX構文解析→C#生成の部分と、Razorの準備コスト(テンプレートエンジンとしてのコスト)が同程度だとしても、処理コストは低くなるのでよりスケールするフレームワークになるんじゃないかと思います。

Pageクラス(WebForms)におけるPostBackという発明はPage Controllerの素晴らしい実装だと思いますが、MVCとなればControllerは役割の違うものとして分離されてるので、PostBackは不要だし、それを実装してるPageクラスはリッチすぎるでしょう。DataSetからRepositoryへのスタイルの変化も同じような考え方からきてるんじゃないでしょうかね(メモリバウンドよりCPUバウンド的な)。Pageクラス、Page Pipelineからの脱却。凄くないですか?

と、なんの根拠もないですが、妄想したりしてます。

2010年7月3日土曜日

ふぉー!フォー!デレシシシ。

なんかね~、そっけない箱がね~、あったんですよ。

IMG_0005


でも、開けるとピカピカな箱なんですよ。

IMG_0006


長かった。オンラインショップで予約した負け組で始まった今回の機種変。サイトダウンにめげずF5アタックを繰り返すも当日予約は途中で断念。翌朝7:30にはすんなり予約できたから、これは当日入手期待大だと妄想。それまでiPhone 3GにiOS4を入れて気分だけでも味わってやろうとしたら信じられないくらいパフォーマンス悪くてなんどもたたき壊してやろうかと思う辛い日々。だけど発売日を過ぎてもなんの連絡も無く。ちなみに受付番号下6桁は818XXX。一体全体この放置プレーはなんだ。そんなこんなで6/30にメールが来た。遅すぎるし放置しすぎ。遅れております、のメール1つで全然違うのになぜ放置。まぁ、いいか。

で、箱。長かった。

さて、アクティベーションしようかとしたところ、9:00~19:00が受付時間らしく何も出来ず。敗北感。仕事変えようかと本気で思った。

IMG_0009

もろもろ設定完了して使ってみると3Gとは比べものにならないくらいのパフォーマンスで操作が快適。さすがですぜ!

2010年6月22日火曜日

千の夜をこえて

いやはや、以前100を超えた時に次は500を目標にする!と言ってからほぼ1年?

stackoverflow

長かった。やっと500を超えた(いつマイナス評価がついて500を切るかヒヤヒヤするけど)デス!

嬉し~よ~。

んで、仕事で面接したりしてるんだけど、興味がある方は採用情報 | 株式会社クロスワープここから応募してみてね!意地悪なこと聞かないよ~。コーディングのテストはあるよ~。また社員募集やってますと同じ募集要項ですよ~。テスト問題はたけはらが考えましたよ~。なので「リンクリストの循環参照検出」なんていう難しい問題ではないですよ~。ぜひどうぞ~。

2010年6月18日金曜日

yield break

最近HaackさんとこでChecking For Empty EnumerationsNull Or Empty CoalescingでEmptyなIEnumerableに対する拡張メソッドで盛り上がりを見せてるますね。面白いですよね。

でね、ふと思ったんですけどね、IEnumerable<T>の空イテレータってどう表現するのがオシャレなのかな、と。

  public IEnumerable<T> Empty<T>()
  {
    return Enumerable.Empty<T>();
  }

  public IEnumerable<T> EmptyZero<T>()
  {
    return new T[0];
  }

  public IEnumerable<T> EmptyYield<T>()
  {
    yield break;
  }

んん~。個人的にはyield breakなんだけど。

2010年5月29日土曜日

ラテアート

いったいいつまでブログ放置をし続ければ気が済むのかね。ズボラにも程がありますよ、まったく。かといってコレといって面白いことも無く。あ、最近Code Contractsが面白そうか。

と、いいつつも最近テニス始めたんですよ。

IMG_2552

最近と言っても、今年に入ってからなんでもう半年位たちますね。いやはや。でも月1くらいしか出来てないんだけど、やり始めるとテレビとかでもテニスみちゃうもんですね。フランスのレザイが要注目やで!

雨の日にランチを食べに行くカフェでたまに素敵なカフェアートをしてくれるんですけど、これがちょっとカワイイ。

IMG_2568

いきなり3Dですよ!アリス イン ワンダーランドも真っ青ですよ。

IMG_2580

4匹のクマちゃん?たぶんクマちゃん。

IMG_2586

で、iPad。あ、間違えちゃった。ラテアートと間違えちゃった。てへ。

なんせ本を読むのとビデオを見るのをメインの用途に考えてるんだけど、本ってどうやったら見れるんだろ。Kindle入れとけばいいのけ?それともCloudReader?

ってことで、↑の写真はCloudReader入れてるところ。KindleってAmazon.comのアカウントじゃないとダメなんすかね。登録してとりあえず無料の本を買ってみたけど...。

もっと気軽に普通にPDFをみるのもいいかと思うも、そもそもPDFをどうやってiPadにコピーしたらいいのかよくわかんないじゃないか。Dropboxが入れてあるからそっちからPDF開いてみたら普通に見れたけど、なんかそんな面倒なハズない!と調べてみたらなんてことなかった。iTunesからD&Dで転送出来た。まずはPDF(いろんな公開ドキュメント)が見れるようになるだけでも超便利。iPhoneで見るとか小さすぎてありえない。見れるのと、ちゃんと読めるっていうのとこんなにも違うのかと電子書籍の衝撃ですよ(この本とても面白いのでおすすめ。あとアーキテクトの審美眼も面白かった)。

来週からの通勤が楽しみです。

そうそうそうそう。社員募集してるみたいデス。興味があればどうぞ。

2010年2月28日日曜日

続Demotterについて

途中まで書いて力尽きたので、続きを。

ValuProviderFactoryについての説明まで進みましたが、そもそものIValueProviderは何なのかというところをすっ飛ばしてます。IValueProvider自体はMVC 1の頃から存在してるものです。GetValueでValueProviderResultとして値を取得するためのインターフェースです。UpdateModelなんかもIValueProviderを渡すオーバーライドがちゃんとあります。FormCollection.ToValueProvider()なんかを渡しますね。

ようは何でもいいからキーに対応する値を渡せばいいんです。基本実装のDictionaryValueProviderは値の実体そのものをキーと共に保持してるものなんですが、キーに対応する値を常に保持しておく必要はなく、GetValueのたびに取得しにいくという実装もアリです。HttpCookieValueProviderの例としてとてもいいサイトが有ります。

Dive Deep Into MVC - IValueProvider - Mehdi Golchin's blog

このサンプルコードではGetValueのたびにRequest.Cookieを参照する作りなのは、値の実体はHttpContextにそもそも保持されてるので、Dictionaryとして二重に保持する必要がないからです。ただ、これだとテストはHttpContextBaseのモック作成から必要になるんで、少し面倒ですけど。

Demotter(MvcPresetner)で作成したのは、PresentationZipValueProviderFactoryでプロジェクトルートにあるやつです。

mvcp

これまで説明したValueProviderFactoryのサンプル実装としてZipファイルをアップロードすると、サーバーサイドで解凍し、Presentationクラス(特にModelBinderの対象となるクラスを限定する縛りはないです)のインスタンスを生成するためにDefaultModelBinderから利用出来るようになっています。

なんで、ここに注目してるのかというと、SRP(Single Responsibility Principal)でテストを簡単にしたいからです。やっぱり楽して作りたいというのがあるからね。Actionのテストってモデルクラスを渡してしまえば、実行コンテキストに依存させなくてもいいじゃないです。DefaultModelBinderは標準機能だからテストなんかしなくていい。そうすると、カスタムValueProviderFactoryだけがHttpContextBaseのMockを使ったテスト対象になるわけです(ViewのテストはWebサーバーで実際に動かしてSeleniumとかでどうぞ)。素晴らしいリファクタリングだと思います。

Zipファイルを解凍するために、外部の依存アセンブリとしてDotNetZip Libraryを利用しています。ZipFileクラスのReadでZipファイルを指定し、ExtractAllで全解凍です。使ってる機能はそれだけ。

PresentationのリポジトリとしてStoragePresentationRepositoryクラスを作ってます。IPresentationRepositoryのファイルストレージ保存用の実装です。なので、ここはデータベースに保存するようなRepositoryを実装すれば、データファイルの保存場所は上位のサービス層(このサンプルではサービス層はなくControllerで直接Repositoryを使ってます)が知る必要はないような作りです。

実ファイルを”~/App_Data”に保存するようにしてるのでServer.MapPathを使う必要があり、Controller.InitializeでIPresentationRepositoryの実装クラスのインスタンスを作成してるので、LinqToSqlPresentationRepositoryを作成したとしても、単純変更はできないのが、手を抜いたところです。RepositoryのResolverというかCreatorを作っておいて、そいつに任せるようにしておく実装であれば依存性を排除できますね。

    private IPresentationRepository _repository;
    public static Func<RequestContext,IPresentationRepository> RepositoryCreator = 
      (requestContext) => new StoragePresentationRepository(
requestContext.HttpContext.Server.MapPath("~/App_Data")
); protected override void Initialize(RequestContext requestContext) { base.Initialize(requestContext); // Serverプロパティなどの参照はInitialize以降じゃないと // できないので気をつけましょう _repository = RepositoryCreator(requestContext); }

たとえば、現状のコードを↑こうしてみるとか。LinqToSqlPresentationRepositoryはRequestContextを必要としないですけど、簡単にするにはこういうのを渡すルールにしておくのもいいんじゃないですかね。Global.asaxなんかで以下のようにCreatorを変えちゃえば、うまくいくはず。

    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();

      RegisterRoutes(RouteTable.Routes);

      ValueProviderFactories.Factories.Add(new PresentationZipValueProviderFactory());

      HomeController.RepositoryCreator = (_) => new LinqToSqlPresentationRepository();

      // カスタムModelBinderを使うなら↓ここで登録忘れずに。
      //ModelBinders.Binders.Add(typeof(Presentation), new PresentationModelBinder());
    }

ここはMVC関係ないところ。今回のサンプルではControllerが生成されるたびに、毎回Repositoryの中でApp_Dataを見てPresentationのインスタンスを取得するので無駄が多いですが、その辺もサンプルということで勘弁してもらえると助かります。

ModelBinderは標準のDefaultModelBinderを使っていて、DataAnnotationModelValidatorがそのまま機能します。カスタムModelBinderでもDataAnnotationを機能させるなら、多分以下のような作り方になると思います。

  /// ValueProviderFactoryを定義しない従来の手法だと、ModelBinderを作成して
  /// 以下のように自分でマップしたモデルに対してValidationを実行することになります。
  public class PresentationModelBinder : DefaultModelBinder
  {
    public override object BindModel(ControllerContext controllerContext, 
ModelBindingContext bindingContext) { // ここでモデルを生成してModelMetadataに入れておくと、 // CreateModelでは生成せずに、OnModelUpdating/OnModelUpdated // を内部で呼び出してくれるようになる。 // でも、この書き方であってるのか自信無いですが...。 var valueResult = bindingContext.ValueProvider.GetValue("Name"); var model = StorageAccessor.Load(
PresentationZipValueProviderFactory.UploadTempPath,
true, valueResult.AttemptedValue); bindingContext.ModelMetadata.Model = model; return base.BindModel(controllerContext, bindingContext); } }

これがあってるのかどうかは自信がないです。BindModelの戻り値にインスタンスをそのまま返すだけではDataAnnotationが効かないので、BindingContext.ModelMetadata.Modelに対象モデルのインスタンスを入れて、後はbase.BindModelに任せてしまう実装です。いいのかな~。ちゃんと動くのは確認してます。

リポジトリから取得できたものをHTMLとして生成するために、Viewにモデルを渡し(Presentationクラスのインスタンス)、後はViewにまるなげです。

スライドとして表示したいデータをMarkdown書式で送信したものを利用するようにしてるので(Stackoverflow.comをまねっこしてみたかった)、Markdown書式からHTMLに変換する必要があります。クライアントでの変換実装としてWMDというのもありますが、今回はサーバーサイドでHTMLに変換するMarkdownSharpを利用しています。2個目の外部依存アセンブリです。Markdownで厳しいところはUL/LIの入れ子が2段までしかできないところ。できる方法があるんだろうか。当たり前ですが、利用目的がスライドじゃないのでしかたないところですね。

スライドの動き自体はカーソルの上下でフェードさせながらの切り替え、左右でアニメーション無しでの切り替えの2種類のみで、リッチなアニメーションは実装してないです。その辺はS5やS6なんかがあるので、そっちに差し替えてもらえればいいかな~、なんて。

そんなこんなで、実行すると↓こんなです。

mvcp2

後は、F11でフルスクリーン表示にしておけば、それっぽく見えます。

あとアップロードしたコンテンツの削除をHttpDelete属性を指定したActionで実装してますが、これだけだとHttpVerbs.DeleteなリクエストじゃないとActionInvokerの対象として選択されないです。一般的なブラウザではGET/POSTしか送信してくれないので困りもの。でも、MVC 2ではHttpVerbsのオーバーライドを簡単にできるように拡張メソッドも用意されてるので、HtmlHelperの拡張メソッドHttpMethodOverrideをForm内で呼び出せば、POSTでもオーバーライド(hiddenに埋め込まれる)されてうまく動くようになります。Railsなんかでも"_method"でHttpVerbsをオーバーライドできるのでそれと同じです。

Viewでは以下のように書いてます。

    <ul>
    <% foreach (var item in Model) { %>
    
    <li>
      <%= Html.RouteLink(item.Name, "Viewer", new { id = item.Name })%>
      &nbsp;-&nbsp;
      <% using (Html.BeginForm("Delete", "Home", new { id = item.Name }, 
FormMethod.Post, new { style = "display:inline;" })) { %> <%= Html.HttpMethodOverride(HttpVerbs.Delete) %> <input type="submit" value="削除" /> <% } %> </li> <% } %> </ul>

簡単ですね。

mvcp3

↑こんなボタン出てきます。なんで、わざわざHTTPメソッドでActionを選択するのかというのはRESTfulなアーキテクチャスタイルの話になるので割愛。ただ、この実装方法であれば、ブラウザ以外からDeleteやPutのリクエストと、ブラウザからの同リクエストを区別するようにActionを書かなくて済むのがいいですよね。もちろんAction名が”Remove”とか”Update”とかでPostで処理をするようにしても、結果は一緒ですけどね。

DemotterことMvcPresenterが何を実装したサンプルなのか、だいたい分かってもらえたでしょうか。これを10分で話すのはさすがに無理ですね。詰め込みすぎました。

Demotterについて

ASP.NET MVC 2になって変更された箇所はとても多いです。MVC 2での新しくなった部分を紹介するサンプルとしてMvcPresenterというのを作成することにしました。Demotterという名前はEdtterへのオマージュ(?)。

MVC 2の新機能のうちマニアにはたまらないだろうなと思って目をつけたのが各種Providerモデルへのリファクタリング部分で、MvcPresenterではそのうちIValueProviderを実装したValueProviderFactoryのカスタム化と言う部分をメインに実装しています。

いきなりそんな話をされても意味がわからないと思うので、順を追って説明していきます(ASP.NET MVCについての基本的な知識は前提です)。

そもそもMVCではポストバックがないので、TextBoxやRadioButtonなどの入力用サーバーコントロールは使用しません。HTMLとしてのinputやtextarea、selectを使用するのみです。そうすると入力値をサーバーサイドで取得するにはどうすればいいかというと以下の3通りあります。

  • Request.Formで取得
  • FormCollection型の変数をAction引数に指定する
  • ModelBinderに任せる

Request.FormとFormCollectionを使用する方法はあまりにも原始的すぎます。入力に対する検証も自分で行う必要があり、とても煩雑なコードになります。

Viewとして以下のようなものがあるとします。

  <% using (Html.BeginForm("FormPost1")) { %>
  
    <% = Html.TextBox("name") %>
    <% = Html.TextBox("age") %>
    <% = Html.CheckBox("isDeveloper") %>
    
    <input type="submit" value="do post" />
    
  <% } %>

これを受け付けるActionとしてRequest.FormやFormCollectionの場合↓こうなります。

    public ActionResult FormPost1()
    {
      var name = Request.Form["name"];
      int age;
      int.TryParse(Request.Form["age"], out age);
      bool isDeveloper;
      bool.TryParse(Request.Form["isDeveloper"], out isDeveloper);

      // 処理

      return View();
    }

    public ActionResult FormPost2(FormCollection form)
    {
      var name = form["name"];
      int age;
      int.TryParse(form["age"], out age);
      bool isDeveloper;
      bool.TryParse(form["isDeveloper"], out isDeveloper);

      // 処理

      return View();
    }

ほとんど同じなんですが、テストする際にRequestなどのコンテキストに依存させないようにするためにFormCollectionを使用するという書き方が存在します。

これに対しModelBinderを利用するスタイルの場合は以下のようになります。

    public ActionResult FormPost3(string name, int age, bool isDeveloper)
    {
      // 処理

      return View();
    }

Action引数に直接入力値が入ってきます。型変換も自動です。変換できないならエラーになるという便利なものです。でも、これだと細かく入力エラーを処理できないです。しかも値が多いとAction引数がとんでもないことになります。

なので以下のようにクラスを定義して、そのクラスのインスタンスをAction引数に取得するというスタイルがオーソドックスな手法となるはずです。

  public class Person
  {
    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsDeveloper { get; set; }
  }

↑これがクラス定義で、↓これがAction。

    public ActionResult FormPost4(Person person)
    {
      // 処理

      return View();
    }

何が違うかは一目瞭然。本来personという仮引数名を使用する場合、Formのname属性にプレフィックスとして"person."とつけておくんですが、そこは自動でプロパティ名とname属性をみて一致するなら埋めてくれます。なので、あえて"person.name"や”person.age”とname属性に指定しなくてもModelBinderは賢いのでなんとかしてくれるんですね。明示的に分けたいときにname属性にプレフィックスを指定する必要があります。

クラスを指定するのも基本型を指定するのもModelBinderにしてみれば同じことです。固有のクラスを使用して、DefaultModelBinderがきちんとインスタンスを生成できないときには独自のModelBinderを作成することになると思いますが、MVC 2ではそういう手法はあまりとらないんじゃないかと思ってます。理由はValueProviderFactoryが指定できるようになったからです。

不思議に思わないですか?Routeに指定した場合でもAction引数に割り当てられるし、FormからPostしても割り当てられる。もちろんQueryStringの場合でも自動でAction引数に値がわたってくるんですよ?データの出所がそれぞれ違うじゃないですか。RouteとQueryStringはURLだから同じだと見ることもできるんですけど。

ここで、もうひとつ忘れてはいけないのがUpdateModelとTryUpdateModelです。これはIValueProviderを指定するか、Formの値を利用するかのどちらかでクラスのインスタンスを生成してくれるんですが、それもValueProviderFactoryを利用することでデータの出所を意識しなくても良くなります。

さっきから"データの出所(でどころ)"という言葉を使ってますが、それってどういう意味かというと、ModelBinderが値を復元する際にどこから値を持ってくるのか?ということです。Request.FormなのかRequest.QueryStringなのか、RouteData.Valueなのか、ですね。じゃーRequest.Cookieから復元させたいときはどうすればいいと思いますか?MVC 1の時はAction内でRequest.Cookieを直接みて自分で変数に割り当てるか、カスタムModelBinderを作成し、そこでRequest.Cookieを参照してモデルに復元させる必要がありました。MVC 2になるとデータの出所をValueProviderFactoryから取得するという仕様になっているので、カスタムなValueProviderFactoryを作成し、Global.asaxでValueProviderFactoriesに追加しておけば、標準のValueProviderFactoryで見つからなかった場合、カスタムValueProviderFactoryから値を取得して、ModelBinderが値(クラスのインスタンスか基本型)を復元してくれます。Cookieから値を取得して復元させたければ、ModelBinderを作成するのではなく、そこはDefaultModelBinderに任せたまま、CookieValueProviderFactoryを作成するとなるでしょう。そうすることでDataAnnotationも有効な状態で値を取得できます(カスタムModelBinderでもできますがそれはまた別の時に)。

ModelBinderのデータの出所(データ取得元)を自分で好きなように指定できるということです。すごいことですよね。ちなみにValueProviderFactoryとして実装しなければいけない唯一のメソッドは

public override IValueProvider GetValueProvider(ControllerContext controllerContext)

です。IValueProviderの基本実装はDictionaryValueProvider<object>で、KeyValueなディクショナリです。ModelBinderの仕組みそのものはMVC 1の時から変わってないので、詳細ははしょりますが(書いたほうがいいですか?)、キーとしてForm要素のname属性やQueryStringのKeyを指定するのを想定して値を取得します。

なのでDictionaryとしてCookieから取得した値を返そうが、JSONをデシリアライズしてキー指定で取得できるようにしたものを返そうがXMLをキー指定で取得できるようにしたとしても、Dictionaryとして取得できるならなんでもいいんです。データの出所だけではなくデータのフォーマットにも依存させなくて済むということです。

JSONとして以下のようなデータがあったとしましょう。

{
  name:'たけはら',
  age:15,
  isDevelopper:true
}

ここから以下のように値が取得できるDictionaryを返すことができれば、ModelBinderは値を復元できるということです。

dict["name"] = "たけはら";
dict["age"] = 15;
dict["isDeveloper"] = true;

おなじ理屈でXMLでもいいですよね。自分で取りやすいスキーマさえ定義しておけばいいので。つまり、ファイルシステムにKeyValueでアクセスできるValueProviderFactoryを作成するなら、Zipでアップロードしたファイルを解凍し、フォルダ構造とファイル名がキーになっていてファイルの実体が取得できるようなものも作成できるわけです。実際にファイルの実体をbyte[]なんかで復元するのはリソースの無駄遣いになるので、ModelBinderが復元するのはファイルのパスにしておくというのが現実的でしょう。MvcPresenterがまさにそのように処理をしています。

ちょっと長くて疲れてきたので、続きは今度にしてもいいですかね。いいですよね。中途半端でさーせん。眠いっす。

dotnetConf2015 Japan

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