2008年8月9日土曜日

イベントの実行順が面白くて

SQL Server 2008に合わせて.NET Framework 3.5 SP1が見えてるところですね。 ウェイトリフティングの三宅選手がスナッチあげてる時に使ってるタオルがスティッチ。ジャークのときにはサメのキャラを期待。

ASP.NET MVCのControllerではoverride出来るイベント(Onなんちゃら)が6個ありまして。

  • OnAuthorization(承認)
  • OnException(例外)
  • OnActionExecuting(Action実行前)
  • OnActionExecuted(Action実行後)
  • OnResultExecuting(Result実行前)
  • OnResultExecuted(Result実行後)
このActionとResultって何なんですか?って気になるところだったりしませんか? 以下のようなコードをHomeControllerに書くとどんな出力がでるのか試してみるとよく分かります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
 [HandleError]
 public class HomeController : Controller
 {
    protected override void OnAuthorization(AuthorizationContext filterContext)
   {
     System.Diagnostics.Debug.WriteLine("OnAuthorization");
     base.OnAuthorization(filterContext);
   }

   protected override void OnException(ExceptionContext filterContext)
   {
     System.Diagnostics.Debug.WriteLine("OnException");
     base.OnException(filterContext);
   }

   protected override void Execute(ControllerContext controllerContext)
   {
     System.Diagnostics.Debug.WriteLine("before Execute");
     base.Execute(controllerContext);
     System.Diagnostics.Debug.WriteLine("after Execute");
   }

   protected override void OnActionExecuting(ActionExecutingContext filterContext)
   {
     System.Diagnostics.Debug.WriteLine("- OnActionExecuting");
     base.OnActionExecuting(filterContext);
   }

   protected override void OnActionExecuted(ActionExecutedContext filterContext)
   {
     System.Diagnostics.Debug.WriteLine("- OnActionExecuted");
     base.OnActionExecuted(filterContext);
   }

   protected override ViewResult View(string viewName, string masterName, object model)
   {
     System.Diagnostics.Debug.WriteLine("-- View");
     return base.View(viewName, masterName, model);
   }

   protected override void OnResultExecuting(ResultExecutingContext filterContext)
   {
     System.Diagnostics.Debug.WriteLine("- OnResultExecuting");
     base.OnResultExecuting(filterContext);
   }

   protected override void OnResultExecuted(ResultExecutedContext filterContext)
   {
     System.Diagnostics.Debug.WriteLine("- OnResultExecuted");
     base.OnResultExecuted(filterContext);
   }

   public ActionResult Index()
   {
      System.Diagnostics.Debug.WriteLine("-- Index action execute");

     ViewData["Title"] = "Home Page";
     ViewData["Message"] = "Welcome to ASP.NET MVC!";

     return View();
   }

   public ActionResult About()
   {
     ViewData["Title"] = "About Page";

     return View();
   }
 }
} 

で、これだけだとちょっと見落としちゃうタイミングがあるので、Views/Home/Index.aspxの先頭にもコード追加。

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">

<% System.Diagnostics.Debug.WriteLine("-- page rendering"); %>

   <h2><%= Html.Encode(ViewData["Message"]) %></h2>
   <p>
       To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
   </p>
</asp:Content>

これで、準備完了。 ※太字が追加したコードです。 実行してみると、デバッグ出力が↓こうなります。

before Execute OnAuthorization - OnActionExecuting -- Index action execute ← ここでアクション実行 -- View - OnActionExecuted - OnResultExecuting -- page rendering ← ここでASPXのレンダリング - OnResultExecuted after Execute

面白いでしょ? ASPX の実行はずいぶん後なんですよね。Viewの実行時にレンダリングされるわけじゃないっていうのが、なるほどなと思わずにはいられない。ちなみに Redirect/RedirectToAction/RedirectToRouteもContent/JsonもViewと同じタイミング。ASPX のレンダリングのタイミングと同じタイミングでResult実行されるのを気をつけておく必要ありです。 ControllerのExecuteの中でこんな順序で処理されてるっていうことが分かれば、いろいろできそうじゃないですか。 で、なんでこんなこと書いてるかというと、TempDataですよ。 この中でTempDataってどういうタイミングで保存復元されるんだろかと。 ソースを追いかけるとController.cs内のprotected internal virtual void Execute(ControllerContext controllerContext)に書かれてますね。 InvokeActionを呼び出す前に、TempData.Load(TempDataProvider)。InvokeAction後にTempData.Save(TempDataProvider)。 と、いうことはControllerのExecuteをoverrideしてbase.Executeの前後でTempDataにデータを入れても意味ないってことですよ(ね?)。 TempDataの出し入れのタイミングを間違えると、入れたのに取り出す時にはnullってことになりかねないので注意が必要です。

ViewDataに比べてあんまり注目されてない気がするTempDataだけど、結構使い道があって(メッセージ出力時や、ViewDataで使うモデルデータに関連するデータを入れたり)するので、積極果敢に攻めの姿勢で使っていこうと思うところですよ。 ※ただしTempDataはシリアライズの問題もあり、LINQ to SQLのモデルをそのまま入れることはできない(StateServerとSQLのSession変数に入れられないのと同じ理由)ので、匿名クラスとかに変換して入れたりします。 それにしても、スナッチ1回目で90kg上げる中国チン選手恐るべし!最終的にミスなしで95kgて...。自重の倍て...。

2008年7月29日火曜日

IsMvcAjaxRequest

ナオキさんのサイトで取り上げられていたので、流行りに乗っかっていこうと思います! ASP.NET MVC Preview 4からAjaxが少しとりいれられてます。

クライアントサイドはMicrosoft Ajax Libraryがベース。 これの使いどころはやっぱり部分更新ですよね。ASP.NET AJAXならUpdatePanelのような動きと言えばわかりやすいかな?

とにかく動かしてみることにしましょう。 まずは、Preview 4のプロジェクトテンプレートで新しいプロジェクトを作成。 そしたら、AccountControllerとHomeContoroller、Views/AccoutとViews/Homeとか出てきます。 Views/Homeの中には最初に表示されるIndex.aspxとAboutページのAbout.aspxが出来てます。 とりあえずHomeControllerのIndexアクションとViews/Home/Index.aspxだけを使って試してみることにします。

初期のIndexアクションは↓。

public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";

return View();
}

Index.aspxは↓。

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
   <h2><%= Html.Encode(ViewData["Message"]) %></h2>
   <p>
       To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
   </p>
</asp:Content>

なんとまぁ、スッキリしたものが出てきますわ。 ※ViewData["Title"]はShared/Site.Masterで使ってますよ。 で、ページにFORMを張り付けてPOSTさせてみよう! IndexアクションでPOSTした値を取得してViewDataに入れようじゃないですか。

public ActionResult Index()
{
 ViewData["Title"] = "Home Page";
 ViewData["Message"] = "Welcome to ASP.NET MVC!";
   
 ViewData["result"] = "";
 if (Request.HttpMethod.ToLower() == "post")
 {
   ViewData["result"] = string.Format("こんにちは、{0} さん!", Request.Form["yourName"]);
 }

 return View();
} 

↓これをIndex.aspxに追加(</p>の後に)。

    <% using (Html.Form("Home", "Index"))
      { %>

      名前は?<%= Html.TextBox("yourName") %>
      <input type="submit" value="ぼたん" />
      <span id="result"><%= ViewData["result"] %></span>
    
   <%} %>

こんな感じですね。POSTしたyourNameをViewData["result"]入れて、それをspanタグ内に表示するものです。ここまではすんなりです。 img.aspx

↑こんな表示になるんで、テキストボックスに適当になんか入れて「ぼたん」押すと、↓こんな感じでボタンの横に表示されます。

img.aspx2 とびっきり普通の処理です。

ここからです! IndexアクションへのPOSTが発生した場合、ブラウザからのものなのかXMLHttpRequestからのものなのかを簡単に判別する方法として、Request.IsMvcAjaxRequest()というのがあるので、それを使うことにします。 なので、Indexアクションを変更。

    public ActionResult Index()
   {
     ViewData["Title"] = "Home Page";
     ViewData["Message"] = "Welcome to ASP.NET MVC!";
   
     ViewData["result"] = "";
     if (Request.HttpMethod.ToLower() == "post")
     {
       ViewData["result"] = string.Format("こんにちは、{0} さん!", Request.Form["yourName"]);
        if (Request.IsMvcAjaxRequest())
         return Content((string)ViewData["result"]);
      }

     return View();
   } 

↑太字の部分が追加したコードです。 差を分かりやすくするのに、Index.aspxには追加でAjax.Formを入れることにします。 なので、Index.aspxの全体は↓。

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication5.Views.Home.Index" %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
   <script src="/Content/MicrosoftAjax.debug.js" type="text/javascript"></script>
   <script src="/Content/MicrosoftMvcAjax.debug.js" type="text/javascript"></script>
 
   <h2><%= Html.Encode(ViewData["Message"]) %></h2>
   <p>
       To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
   </p>
 
   <% using (Html.Form("Home", "Index"))
      { %>

      名前は?<%= Html.TextBox("yourName") %>
      <input type="submit" value="ぼたん" />
      <span id="result"><%= ViewData["result"] %></span>
    
   <%} %>
 
   <% using (Ajax.Form("Index", new AjaxOptions { UpdateTargetId = "result2" }))
      { %>
    
      名前は?<%= Html.TextBox("yourName") %>
      <input type="submit" value="ぼたん" />
      <span id="result2"><%= ViewData["result"] %></span>
    
   <% } %>
 
</asp:Content>

これを実行すると2つのテキストボックスとボタンが表示されます。 img.aspx3

フォームも2つあるからそりゃそうですね。 最初に上段のテキストボックスとボタンに”さる”と入れて上段のボタンを押します。

img.aspx4

ボタンの横に両方とも”さる”と出ます。 続いて、下段のテキストボックスに”いぬ”と入れて下段のボタンを押します。

img.aspx5 小さすぎて見にくい...。 まぁ、それはいいとして、下段だけが"いぬ"になりましたね。 結果だけを見ると分かりにくいんですけど、実際にコードを書くとですよ、上段のボタンはページ全体をPOSTで取得するのに対し、下段はボタン横のテキストのみAJAXで取得して書き換えてる動きになってるのが確認できると思います。

2008年7月18日金曜日

やったねPreview 4!

一部で話題沸騰中のASP.NET MVCですが、Preview 4が出ましたね! 教えてくれたナオキさんに感謝デス!

ASP.NET - Release: ASP.NET MVC CodePlex Preview 4 Installer + Source

さっそくダウンロードしてインストール。Preview 3のアンインストールが必須です。

で、今回のリリースでどこがどう変わったのか気になるよね! イロイロ変わってるみたいですが、Preview 3からの変更点はそれほど大きくなくて、実装してるものはほぼそのまま移行できるというすぐれものです。 ただし!ViewDataは手を加える必要があるくらい変わりましたね。

例えば。 Page(aspx)でUserControl(ascx)をHtml.RenderUserControlでレンダリングするとき、第2引数にViewDataを指定しないなら、そのままUserControl(ascx)でもViewDataが参照できます。 ここまでは今まで通り。

じゃ、ViewDataとして匿名クラスを渡した場合どうなるでしょ?

<% = Html.RenderUserControl("~/Views/UserControls/MyControl.ascx", new {Val1=1,Val2="弐"}) %>

↑こんな時ね。あるよね?ない?

今までは上記のMyControl.ascx内で参照するとき(int)ViewData["Val1"]とか(string)ViewData["Val2"]で参照できたとことが、Preview 4になってからはそれができなくなりました。 デバッガでどうなってるのか見てみると、上記のような場合でもViewData.Modelに値が入ってしまうんですね。でも、待って下さい、匿名クラスなんですが! そうね、UserControl内でキャストしようにもできませぬ。

そんな時はViewDataDictionaryクラスに新しく追加されたEval関数を使いましょう! 上記の例で行くと(int)ViewData.Eval("Val1")とか、(string)ViewData.Eval("Val2")って具合です。 ※もっといい書き方あるかもしれないけど...。

匿名クラスをViewDataとして渡すという書き方自体があまりオーソドックスじゃないかもしれないけど、新しくクラスを作るほどでもなく、複数の値をUserControlに渡したいというものぐさな人にはピッタリ(自分)。 今回のPreview 4で感激したのがAuthorizeAttributeとHandleErrorAttribute。いずれのフィルターもControllerのActionどっちでも使えて楽ちんぽん!

Ajax 的な処理の部分はprototype.jsを使ってるので、改めてMicrosoft Ajax Libraryを使う気にはなれないけど、Microsoft Ajax Libraryが標準装備(scriptタグは自分で書かなきゃダメだけど)になってて、Ajax.Formなんかで部分更新(UserControlのレンダリングは見つからないけど)なんかも簡単にできるようになってますね。

Scott Hanselman's Computer Zen - ASP.NET MVC Preview 4 - Using Ajax and Ajax.Form

あとはね~、そうだな~、そうだ!TempDataがSerializableになりました!TempDataProviderっていうのを実装すれば、Sessionじゃないところにも入れれるけど、そこまではちょっと...。単体テスト向け? これで自分で作ったFlashDataなんていうクラスも使わなくて済むかも!と、思ったけど、TempDataに入れたのがずっとSessionで残ったままになってて意味ないじゃん!みたいな。 ※StateServer指定して試しました。 ↑ちゃんと消えました。 すいません。

HomeControllerでのテスト。 1.HomeController.Index TempDate["Message"]=”Test”; 2.Home.aspx <%= TempData["Message"] %> 3.About.aspx <%= TempData["Message"] %>

↑こんな感じで実行すると、Homeではもちろん表示されます。

で、"About us"リンクをクリックすると、Aboutでも表示されます。 もう一度"About us"リンクをクリックすると、今度は消えてます。 1の処理でTempDataに値を入れます。で、2の処理は同じパイプラインでの実行なので、そのまま表示されます。その後、同じパイプラインでTempDataProvider.SaveTempDataでセッションに保持。 一回目のAbout表示の時にTempDataProvider.LoadTempDataでセッションから取り出し、セッションをクリア。で、取り出したのを表示するから、ちゃんと出る。でも2回目のAbout表示ではセッションから消しちゃってるから表示されませんね。 あってます!自分が処理間違ってました!さーせん!TempData最高!

AccountControllerは結構普通だったのがちょっと残念かも。 そうそう、最初に生成されるSite.Masterのcharsetが相変わらずiso-8859-1なのは、なんか意味があるんだろうか。 ちなみに今回のリリースで一番感動するのが「ASP.NET MVC API Changes From Preview 3 to CodePlex Preview 4」というタイトルのソースの変更箇所一覧が書かれてるPDF。超感動。前回は差分がどこかわかんなくて苦労したけど、今回はこれがあるから大丈夫(だと思います)!

2008年7月17日木曜日

コメントがんばるガスリー君

ASP.NET MVC Preview 4 Release (Part 1) - ScottGu's Blog

ナオキさんとこで知ったPreview 4。
まだ水曜だけど、iPhone以上にリリースが待ち遠しくて、ボケ~と読み返してたんだけど、コメント欄が凄いことになってる...。

ガスリー君3つ子説俄然説得力を増してくる...。
JoeOn.net In Japanese : Scott Guthrie はどうしてあれほど多くの仕事ができるのか

気になるコメント。
>>>>>> Can you please give some date as to when will the ASP.Net MVC go live?

You can go live with ASP.NET MVC today.  The license supports production deployments.

製品開発に使ってもよかとですか。

>>>>>> Now that we are on Preview 4, what comes next? Beta 1?  RC1?

I think Beta1 probably isn't far off now.

P4の次はとうとうベータ1!?

>>>>>>> Hi Scott, love the blog, always a great read! One slightly (read completely) off topic question, what theme are you using for VS? It looks fantastic!

I have a slightly custom theme that I use.  You can download it here: www.scottgu.com/.../scottgu-dark.zip

ペーター君はなぜにそこが気になったのか...。
Using SubDataItems and View User Controls in ASP.NET MVC
Page でRenderUserControlするときに、第3引数になんらしらのViewDataを渡すと、UserControl内で ViewData.Modelは見れても、Controllerで入れたViewData[~]が空になって全く参照できなくてガッカリなことがあると思うんです。
で、Preview3のソースを追っかけてたところ、UserControlExtensions.csのDoRendering関数が怪しいんじゃないかと思って、検索してたら発見したのが↑のサイト。
同じようにSubDataItemsも子コントロール内での参照ができないから、ViewDataもソースいじれば引き継いでくれるようになるんだろうけど、なんかいじるのヤダナ。消極的にPreview 4を待ちます...。

2008年7月1日火曜日

ユニットテストですよね

ASP.NET MVC Tip #12 – Faking the Controller Context - Stephen Walther on ASP.NET MVC

Rhino Mocksでコンテキストをうまいことくるんでテストコード書いてたんだけど、これがまた使い方が良くわかんなくてシックハック症候群。

そんなところで、モック無でのテストコードを見せられた日には飛びつきたくなるってものですよ!
MvcFakeプロジェクトだけを使わせてもらえば、それはそれは至れり尽くせりとまではいかないまでも簡単テストができるじゃないですか。感激ですね。

Tip11への突っ込みに対して(個人的な心の中で)、見事Tip12で答えに導くあたり、心を読まれてるんじゃないかと軽くエスパー疑惑です。

2008年6月20日金曜日

DataControllerでDynamic Dataみたいな

ASP.NET MVC Tip #4 - Create a Custom Data Controller Base Class - Stephen Walther on ASP.NET MVC これもありだな~、と思います。

Google App Engineでの開発っぽい。 DataControllerだけが大事なところなのにコード量がべらぼうに少なくて、なんか嬉しい。 Form 値はRequest.Form.Keysをforeachで取り出したのとエンティティのプロパティが同じなら(上手く言えないけどリフレクションで)書き換える感じで。エンティティそのままでViewData用のクラスなわけじゃないから、 System.Web.Mvc.BindingHelperExtensions.UpdateFromを使えないのかな(NewとUpdateで同じコード書いてるのか切ない部分)?

でもね、GetDynamicGetがカッコよすぎる。 IdentityColumnNameも主キーがintの項目を1つにするルールを貫けば(Railsとかそうだし)超絶便利な予感。 派生してるのがHomeControllerだからリソース名がHomeに思えるけど、そこはデフォルトのままいじってないってことなんだろうから気にしない。実際にはこのサンプルならMoviesControllerとかにするのがナウなヤングのRails風?

RESTfulじゃないけど「設定より規約」なところがいいっす! 最近のお気に入りはDBのテーブル全てにEntryDateとModifyDateをDateTime型で作っておいて、DataContextクラスのSubmitChangesをオーバーライドして勝手に値を入れるようにすること。

public override void SubmitChanges(ConflictMode failureMode) { ChangeSet changes = this.GetChangeSet(); DateTime now = DateTime.Now; Action setNow = (entity, name) => { var etype = entity.GetType(); var prop = etype.GetProperty(name); if (prop != null) prop.SetValue(entity, now, null); }; // insert foreach (var entity in changes.Inserts) { setNow(entity, "EntryDate"); setNow(entity, "ModifyDate"); } // update foreach (var entity in changes.Updates) { setNow(entity, "ModifyDate"); } base.SubmitChanges(failureMode); }

GetChangeSet()で追加・更新・削除それぞれの対象レコードを取得できるから、それを取り出して自動更新。エンティティの型は宣言しないでvarで。varと規約ベースの開発は最高っす! ただ悩みもありまして...。

入力値の検証(Validation)をどこでやるのがいいのかってことなんですけどね、LINQ to SQLのEntityのpartial classでやると、確実にチェックではじけていいんだけど、タイミング的にはもう少し早い段階でやりたかったりする。ViewDataクラスにバリデーション機能をつけるのがいいのかな...。アプリケーションの制約と、データベースの制約とレイヤーが違うからそれぞれに必要なんだろうけど(例えばAと Bっていうカラムがあって、更新するときにどっちも空は嫌だけど、どっちも空でもDBでは問題なしとか)。 タイムリーにオノさんところで紹介されてたサンプル(SingingEels : ASP.NET MVC in the Real World)を見てみたけど、普通にコントローラでForm値を検証してるじゃんよ...。 こんな感じなのかな~(とりあえずTempDataに入れちゃうとロードバランサとかで無茶ぶりできなくなる)。

追記なんですけど。 ASP.NET MVC Tip #5 – Create Shared Views - Stephen Walther on ASP.NET MVC これまた面白いのが...。Tip #4の続きで今度はまさにDynamic Data。 コントローラ用のViewフォルダに、コントローラでView()するASPXがない場合の初期動作として、Sharedフォルダ使う動きをうまく生かして、すべてに共通のViewでページ生成。なので、インターフェイスを動的に作る必要があるからASPXのコードビハインドにコード書いてる。 全然気になんない!Scaffold!

2008年6月13日金曜日

フリーマーケット

何気に海外のドラマを見てる時にフリーマーケットのシーンがあって、テロップに「Flea Market」って書いてて初めてフリマの意味を知りました。

さて、DataContractJsonSerializer使ってますか? かつて何でもありだったJavaScriptSerializerの次に出てきた標準クラスなんだけど、これを使おうとするときは基本型指定ですよね。

で、ASP.NET MVC Preview3になってからJSONをレスポンスに返すためControllerに新たにJsonResult Json(...)が定義されてますよね。 で、これってobjectを引数に渡すんだけど、型指定しないってことじゃないですか。もしやと思って、ソース見てみたんです。 そしたら...。

#pragma warning disable 0618 JavaScriptSerializer serializer = new JavaScriptSerializer(); response.Write(serializer.Serialize(Data)); #pragma warning restore 0618

こんなコードになってた。強引っすね。 いいのかな~。自分も使いたいんだけど、使っていいのかな~。 だってね、LINQ to SQLのデータモデルクラスをそのままJSONで扱いたいけど、DataContractじゃないし、DataMemberついてもないからそのままだと使えなくて、でもそのためだけに別クラス作るのも馬鹿らしいじゃないっすか。みんなどうしてるんだろう。

そしたらさ、ガスリー君(本物なのかな?)が言うんですよ。 First thoughts on ASP.NET MVC Preview 3 | Aaron Lerch 「The JavaScriptSerializer class is actually undeprecated in .NET 3.5 SP1. 」 って(...どういう意味?)。 使っていいのか!?いいのかガスリー君!?

dotnetConf2015 Japan

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