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. 」 って(...どういう意味?)。 使っていいのか!?いいのかガスリー君!?

2008年6月3日火曜日

MVCContribでRESTful

CodeProject: RESTful routing in ASP.NET MVC. Free source code and programming help こんなに簡単に...。

SimplyRestfulRouteHandlerを使えばいいよ!ってことですね。 そもそもMVCContribってなんですか?ってなもんです。

なので、ちょっとソースをダウンロードして確認(現時点でのダウンロードが55っていうのが人気の無さを物語ってる気がしなくもなくもないけど気にしない!)。 かゆい所に手が届く系のライブラリって感じ?

RESTfulを単純にHTTP Methodだけで考えれば、リソースに対してGET/PUT/DELETE/POSTでアクションを決定するようにすればいいよね。ってことはリソース=Actionじゃなくて、リソース=Controller。 でも、WebでHTMLをViewにする場合、入力ページ(新規と編集)もGETだからリソースのGETとかぶる。あと、コレクションのGETも。 そうなると少し面倒な感じがしなくもないけど、RoutingでうまいことなんとかなるのがASP.NET MVCのいいところ。 URIの設計をしてて思ったのは、URIのリソースをそのままコントローラと一致させないで、以下に内包したコントローラとして頭を切り替えるかが結構キモなんですね。

会社と社員を内包関係で表現すると /Companies/{companyId}/Employees/{employeeId}

だけど、それぞれがコントローラになるんですよ~。 てことは、個別にしてもいいじゃない?

/Companies/{companyId} /Employees/{employeeId}

でも、これだと所属を表現できないからヤダってこともあるでしょう(会社と社員じゃないサンプルの方がよかったね...)。 デフォルトアクションがそれぞれIndexだとしたら、CompaniesController.Indexと EmployeesController.Indexがそれぞれのコレクションリソースで、個別のリソースを取得するためにもうひとつModelアクションを定義。で、RoutingでうまくModelアクションにルーティングしてあげればうまいこと出来るよね。

GET /Companies → 会社一覧取得 GET /Companies/New→ 会社新規フォーム POST /Companies → 会社新規登録 GET /Companies/123 → IDが123の会社取得 GET /Companies/123/Edit → IDが123の会社編集フォーム PUT /Companies/123 → IDが123の会社更新 DELETE /Companies/123 → IDが123の会社削除

こうしたいって時のコントローラでのアクション。 ※前回書いたRESTfulAttributeがある前提。

[RESTful(Post="Create")]
public ActionResult Index(int? id) {...}

[RESTful(Put="Update",Delete="Destroy")]
public ActionResult Model(int? id)
{
 if (id.HasValue)
  return Show(id);
 else
  return New();
}

public ActionResult Update(int id) {...}
public ActionResult Destroy(int id) {...}
public ActionResult Show(int id) {...}
public ActionResult New() {...}

これをうまくRoutingでさばきます。

routes.MapRoute( "Companies-Model", "Companies/{id}/{action}", new { controller="Companies", action="Model" }, new { id = @"[\d]+" } ); routes.MapRoute( "Companies", "Companies/{action}", new { controller="Companies", action="Index" }, new { action=@"[a-zA-Z]*" } );

こうやって、2つのRoutingでひとつのコントローラに対する、ルーティングを登録すればIDのありなしをちゃんと1つのコントローラで処理できるから、なんて素敵にRESTful!ってなりませんかね。 ※正規表現はよしなに。

ROAなURIを考えた場合{controller}/{action}/{id}だとアクションがリソースになるけど、{controller}/{id}/{action}でコントローラをリソースにしちゃいましょうっていう。

/Companies/123/Employees/456

とかのルーティング↓。

routes.MapRoute(
 "Employees-Model",
 "Companies/{companyId}/Employees/{id}/{action}",
 new { controller="Employees", action="Model" },
 new { companyId=@"[\d]+", id = @"[\d]+" }
); 

※コレクション部のルーティングも同じように。

イロイロ内包するならたくさん書くか、"/Employees"を"/{controller}"にしてあげる。 統一インターフェースでのアクセスは規約でアクションを決め打ちすることで最強になるでしょう。 全然MVCContribの話じゃないっすね...。

で、これを簡単にするのにMVCContrib。 Simply Restful Routing ここにあるように、アクション名を規約に通りにつければ、HTTP MethodとURIを見て、うまいことルーティングしてくれます。PUT/DELETEはブラウザから送れないので、そこはHidden で"_method"にメソッド名を入れてオーバーロードPOST。Railsっぽく。 どっちを使うかは気分次第で!

2008年5月29日木曜日

進化の過程をウキウキウォッチング

いや~、ずいぶん変わってしまいました。 Preview2からPreview3への移行をしてみようと作業してて思ったのが、簡単にできるようになったかもってところです。 もちのろんでASP.NET MVC Preview3ですよ! 分かりやすくRESTfulフィルター作ります。

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

namespace MvcApplication4.p3
{
 public class RESTfulAttribute : ActionFilterAttribute
 {
   public string Post { get; set; }
   public string Put { get; set; }
   public string Delete { get; set; }

   public RESTfulAttribute() : this("", "", "") { }
   public RESTfulAttribute(string post, string put, string delete)
   {
     Post = post;
     Put = put;
     Delete = delete;
   }

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
     string httpMethod = filterContext.HttpContext.Request.HttpMethod.ToLower();
  
httpMethod = (filterContext.HttpContext.Request.Form["_method"] ??
filterContext.HttpContext.Request.HttpMethod).ToLower();

     var actions = new Dictionary() {
       {"post",Post!="" ? Post : filterContext.ActionMethod.Name + "Post" },
       {"put",Put!="" ? Put : filterContext.ActionMethod.Name + "Put"},
       {"delete",Delete!="" ? Delete : filterContext.ActionMethod.Name + "Delete"}
     };

     if (actions.ContainsKey(httpMethod) && actions[httpMethod] != "")
     {
       filterContext.Cancel = true;
     
       var controller = filterContext.Controller as Controller;
       var actionInvoker = new ControllerActionInvoker(controller.ControllerContext);
       actionInvoker.InvokeAction(actions[httpMethod], new Dictionary());
     
       return;
     }

     base.OnActionExecuting(filterContext);
   }
 }
} 

はぁ、もうこの時点で違うんだね。 InvokeActionがControllerじゃなくて、ControllerActionInvokerクラスに移動になりました。 素直に呼び出せるからこれの方がいいね。 2個目のパラメータの意味が不明。MVCのソース見てもExecuteで↑みたいにnewしてる。 RESTful属性をつけたアクションの実行時にPOST/PUT/DELETE毎にアクションを振り分ける処理です。 属性のパラメータで名前を指定しなかった場合は、自動で呼び出しアクション名+HTTP MethodをInvoke対象のアクション名にしてます。

ホントはGETのときにNew(新規フォーム)/Edit(編集フォーム)/Show(表示だけ)を振り分けるのもID見たりしてフィルターでやった方がよりカッコよしかも。あと、どの表現(XHTML、JSON、XMLとか)を返すかとかも拡張子みたいな形で分けるとなお素敵さアップ。 続いてHomeControllerにアクションを追加。

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

namespace MvcApplication4.p3.Controllers
{
 public class HomeController : Controller
 {
   public ActionResult Index()
   {
     ViewData["Title"] = "Home Page";
     ViewData["Message"] = "Welcome to ASP.NET MVC!";

     return View();
   }

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

     return View();
   }

   [RESTful]
   public ActionResult Resource(int? id)
   {
     return Content(string.Format("GET!({0}) - {1}", Request.Form["value"], id), "text/html");
   }

   public ActionResult ResourcePost()
   {
     return Content(string.Format("POST!({0})", Request.Form["value"]), "text/html");
   }
 
   public ActionResult ResourcePut(int id)
   {
     return Content(string.Format("PUT!({0}) - {1}", Request.Form["value"], id), "text/html");
   }
 
   public ActionResult ResourceDelete(int id)
   {
     return Content(string.Format("DELETE!({0}) - {1}", Request.Form["value"], id), "text/html");
   }

   public ActionResult Item()
   {
     ViewContext vc = new ViewContext(ControllerContext, "dummy", "", null, null);
     var page = new ViewPage();
     page.Html = new HtmlHelper(vc, page);
     page.Url = new UrlHelper(vc);

     string partial_html = page.Html.RenderUserControl("~/Views/UserControls/Item.ascx");
     return Content(partial_html, "text/html");
   }
 }
} 

Resourceって言う名前のアクションを定義して、RESTful属性をくっつけました。 前 (Preview2)まで、単純にテキストを出力するのがなかったから、RenderTextなんてのをControllerにくっつけてたんだけど、新たにContentって言うメソッドで出力できるようになりましたね!ちなみに今回は使ってないけどJsonもあるよ!

最後のItemアクションはユーザーコントロール(ascx)の実行結果を出力するため(パーシャルっす)のサンプル。これは前とほぼ変わってないけど、全体的に引数の数が増えてる感じ?

最後にページ部分。Home/Index.aspxを書き換えてます。

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

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
<h2><%= Html.Encode(ViewData["Message"]) %></h2>

<p>
<a href="<%= Url.Action("Resource") %>/1" class="restful">GET</a>
<a href="<%= Url.Action("Resource") %>" class="restful">POST</a>
<a href="<%= Url.Action("Resource") %>/1" class="restful">PUT</a>
<a href="<%= Url.Action("Resource") %>/1" class="restful">DELETE</a>
</p>

<p>
 <a href="javascript://" class="partial">どろんじょ</a>
 <div id="partial"></div>
</p>

<script type="text/javascript">
Event.observe(window, 'load', function(){
 var baseAction = '<%= Url.Action("Resource") %>';
 $$('a.restful').each(function(anchor){
   anchor.observe('click',function(e){
     var method = anchor.innerHTML;
     var index = anchor.href.indexOf(baseAction);
     var url = index >= 0 ? anchor.href.substring(index) : baseAction;

     new Ajax.Request(url,{
       method: method,
       parameters:'value=restful',
       onComplete:function(ajax){
         alert(ajax.responseText);
       }
     });
     Event.stop(e);
   });
 });

 $$('a.partial').first().observe('click',function(e){
   new Ajax.Request('<%= Url.Action("Item") %>',{
     onSuccess:function(ajax){
       partial.innerHTML = ajax.responseText;
     }
   });
 });

});
</script>
</asp:Content>

UserControls/Item.ascxの中身は何でもいいんだけど、とりあえず↓。

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Item.ascx.cs" Inherits="MvcApplication4.p3.Views.UserControls.Item" %>
<div>
 <ul>
   <li>マジで恋する5秒前</li>
   <li>ムゴ、ン色っぽい</li>
 </ul>
</div>

処理を簡単にするために、Site.Masterでprototype.jsを読み込むようにしてます。 実行すると、最初が↓。 img.aspx

PUTリンクをクリックすると↓。

img.aspx2

んで、どろんじょリンクをクリックすると↓。

img.aspx3

これで今日からRESTful!

2008年5月28日水曜日

Preview3!!!

やっと出た~!!

ASP.NET MVC Preview 3 Release - ScottGu's Blog ASP.NET - Release: ASP.NET MVC Preview 3 Source Download details: ASP.NET MVC Preview 3

Northwind使ったサンプルプロジェクトも出てるけど。 Updated Northwind Demo For ASP.NET MVC Preview 3 このままじゃなんかエラーで動かない!

と、コメントに書いてる人もいて、同じくアセンブリをいったん削除(System.Web.Abstractions,Mvc,Routhing)してから、Program Filesに入ったDLLを参照で追加すると動いた。 ソースも即行ダウンロードしたけど、どこがどれだけ変わったのやら。 とりあえず、Preview3 Readme.docを見たけど、もっとこう細かいところの一覧とかも欲しかったりする。わがままですね。

でも、HtmlヘルパーのDropDownList(ListBoxも)がMultiSelectListを渡すようになって、ちょっと良くなったのはいいけどRadioButtonListは相変わらずstring[]を返すんだね。結果に対するAggregateによる生成はどうなんでしょうね。いいんですかね。

あと、せっかくだから(最近ドはまり気味の)RESTfulなサンプルにしてほしかったりするな~。 URIは名詞で!統一インターフェースで! あと、個人的にかなりショッキングなのがViewDataのModelプロパティ...。同じ名前でプロパティを定義するようにしてたからっていうね。悲しいね。

ソース見てて思ったけど、あれだね、カスタムViewDataと、標準ViewDataの両方がViewにわたるんだね。標準ViewDataのModelプロパティにカスタムViewDataが入ってる(カスタムとか標準って言い方をしないよね、きっと)。この辺の変更でHtmlヘルパーのデフォルト値指定が楽になるみたい。 これは素敵かもしれない。TempDataは相変わらずSessionなのがちょっと残念。 CopyToは同じの作ってたから、最初から欲しかったので最高! あとは、いじりつつ確認するぞ~!! ※416からでもずいぶん違うのね...。

dotnetConf2015 Japan

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