2008年3月13日木曜日

ASP.NET MVCとASP.NET AJAX

ポストバック手法とMVC手法は相互に乗り入れるということを前提に作るものじゃないってことなんだろうか。 ASP.NET MVCでのサンプルを作ってみて、コントローラ/アクションからビューへの展開が今までのポストバック手法(WebForms)に比べて新鮮で面白い。クリーンなHTML(ViewStateとかその他のスクリプト)を出力するし。 型指定のViewDataじゃないと無駄にコードが長くなる(Viewの中で)ので、基本的にはきちんとViewData用のクラスを用意して利用(ViewPage<ViewDataの型>)するべし。

    <tr>
     <th>カテゴリ</th>
     <td><%=Html.Select("CategoryID", ViewData["Categories"] as List<Category>, (ViewData["Product"] as Product).CategoryID)%></td>
   </tr>
   <tr>
     <th>業者</th>
     <td><%=Html.Select("SupplierID", ViewData["Suppliers"] as List<Supplier>, (ViewData["Product"] as Product).SupplierID)%></td>
   </tr>
   <tr>
     <th>単価</th>
     <td><%=Html.TextBox("UnitPrice", (ViewData["Product"] as Product).UnitPrice)%></td>
   </tr>

↑キャスト長すぎ...。

  public class ProductEditViewData
 {
   public Product Product { get; set; }
   public List<Supplier> Suppliers { get; set; }
   public List<Category> Categories { get; set; }
 }

↑これがあれば↓これで済む。

    <tr>
     <th>カテゴリ</th>
     <td><%=Html.Select("CategoryID", ViewData.Categories, ViewData.Product.CategoryID)%></td>
   </tr>
   <tr>
     <th>業者</th>
     <td><%=Html.Select("SupplierID", ViewData.Suppliers, ViewData.Product.SupplierID)%></td>
   </tr>
   <tr>
     <th>単価</th>
     <td><%=Html.TextBox("UnitPrice", ViewData.Product.UnitPrice)%></td>
   </tr> 

※ベタだけどNorthwind使ってスコットさんと同じように作ってみました。 ちなみにこれらのFormの値をコントローラで取得する場合は、UpdateFormを使う模様。

Product product = new Product(); BindingHelperExtensions.UpdateFrom(product, Request.Form, new[] { "ProductName", "CategoryID", "SupplierID", "UnitPrice" });

※ヘルパーとか12月のやつからちょこちょこ変わってる。 Formの値を個別に取得する場合は、コントローラの拡張メソッドになってるReadFromRequestを使う。 Html の要素作成にヘルパー使うなら気にしないけど、手書き(input/radio/textarea/select/hidden)のときはnameに指定したものじゃないとサーバーで値取れません(単なるRequest.Form/QueryStringだから当たり前なんだけど)。 runat="server"じゃないし。 で、もちろんViewStateなんてないのでイロイロこまごましたのは自分でちゃんと実装する必要があり。Pageのインスタンスをサーバーサイドで再構築出来ないんだから。 ここで気になるのがASP.NET AJAXとの絡み。試してみたけどやっぱりすんなりできるものじゃないっす。ASP.NET AJAXのうまみは基本的に全く利用できない感じです。UpdatePanelとかに必要なデータを自分で文字列生成して返すとか考えられない。自身にポストバックする従来の方法なら、いくらでもPage再構築出来るかもしれないけど、コントローラに"post"するんだからサーバーサイドでPageの再構築とか厳しい。 そうなると、普通に自分でAjaxを実装(もちろんライブラリを使うけど)していくことになって、あっちをとればこっちが立たずみたいな。機能的に完全に分離してもよくて、運用サイドが使うような機能なら従来の手法(楽ちんに作れる)、ユーザーサイドが使うならMVC(綺麗に作れる)とかなのかな~?

そもそもASP.NET AJAXを使ってないから、あんまり気にしないんだけど、実際市場ではどういう使い分けをするのかが気になるデス。 そうそう、ASP.NET MVCのパフォーマンスってどうよ?みたいなことも、もちろん気になるわけで。

従来のフローをはしょって書くと↓。 System.Web.HttpRuntime → System.Web.HttpApplication → Pageのインスタンス

MVCだと↓。

System.Web.HttpRuntime → System.Web.HttpApplication → System.Web.Mvc.MvcHandler(ルーティング) → System.Web.Mvc.Controller(ターゲットアクション探す:リフレクション) → System.Web.Mvc.ActionFilterExecutor(アクション起動) → 自分のコントローラ → System.Web.Mvc.WebFormViewEngine(ビューへの橋渡し) → System.Web.Mvc.ViewPage.RenderView(ビュー準備) → Pageのインスタンス → 自分のView

たぶんこうなんじゃないかと...。 なんとなくそうなのかなっていう程度です。 途中"自分のコントローラ"の直前に[ネイティブからマネージの移行][マネージからネイティブへの移行]っていうのが入ってて、これが何してんのかよくわかなかった。 ※ちなみに、IISじゃなくてVS2008から起動するWebServer上で、デバッグ実行時の呼び出し履歴を見ただけなので、かなり推測でしゃべってます。 少なくとも、Page(View)に到達するまでに結構いろいろやってる。でもポストバック元のPageをオンメモリに再構築するっていう手間がないから、結局どっちがいいのか判断難しいところです(CPUリソースかメモリリソースかっていう問題なのかな)。 あとね~、LINQ to SQL。DataContextは結局コントローラのプロパティにして使いまわすのでいいんじゃないかな。いろいろなところで、 HttpContext.Current.Itemsに突っ込んで同一コンテキスト内ではシングルトンな使い方がいいみたいに書いてたけど。

public class DContext { public static T Get() where T : System.Data.Linq.DataContext, new() { // HttpContextが不明なら常にnewして返す if (HttpContext.Current == null) { System.Diagnostics.Debug.WriteLine("no context!"); return new T(); } T context; // ItemのKeyを生成(同一コンテキスト内なら同じキーができるはず) string key = "__WRSCDC_" + HttpContext.Current.GetHashCode().ToString("x") + Thread.CurrentContext.ContextID.ToString(); context = HttpContext.Current.Items[key] as T; if (context == null) { context = new T(); HttpContext.Current.Items[key] = context; System.Diagnostics.Debug.WriteLine("new context!! key:" + key); } return context; } }

※試しに作ったクラス。

パフォーマンスをテストしてみたけど、普通にコントローラでDataContext使うのが早かった。 もちろんコード自体は分離していかないと、ごっちゃごちゃになっちゃうけど、DataContextそのものをPartialで書き足して行かないで、別途機能ドメイン毎にクラスを作って、それぞれの中で個別にDataContextのインスタンスを作るくらいでいいんじゃないかと。 複数のDataContextが1度のリクエストで発生するのが無駄遣いなら、コントローラで作ったDataContextのインスタンスを渡すとか。

なんだかんだ大量のデータ更新を一度にやりたい時とかはストアド使ってやればいいし。ホントはバッチ更新とかできるのかなとも思ったけどそれは無理みたい。

Public Sector Developer Weblog : I was wrong :(

トランザクションが良くわかんなかったけど、分散でまたがないし、複数DataContextを使わないならTransactionScopeいらないっぽい。SubmitChangesが上手いことやってくれるっぽい。

方法 : データ送信をトランザクションで囲む (LINQ to SQL)

ちなみに↑これはts.comlete()を実行してないから動かないと思うんだけど、どうなんでしょう(試した限りではコミットしてくれなかった)? なんだかんだと、まとまりのない文章になっちゃった。 誰かえらい人イロイロ教えて。