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からでもずいぶん違うのね...。

2008年5月20日火曜日

ひた隠しに

動くものを作ろうと思うと、やっぱり流行りのスタイルに乗っかりたい。そう思うのは浅はかなことでしょうか。で、ASP.NET MVCでRESTfulっぽく作るならやっぱり、HTTP MethodでActionを自動で振り分けたい。 そう思うのは、いたしかたなし。

画像を操作するResourceControllerがあったとしましょう。 画像を追加したいならResourceControllerにPhotoAddNewとかPhotoCreateなんていうActionを作りたくなるでしょう。でも待って!それじゃRESTfulじゃないよ!

複数形とか単数形は置いといて、とりあえず/Photoに対するアクセスでCRUDを処理したいよね! 特に興味無いですか?あ、そうですか。

まぁ、聞いて下さいよ。 以前のエントリーでRESTfulFilterなんてものを書いてみましたが、ぶっちゃけあれじゃ効率悪し。 送り側にActionFilterAttributeを書く方法だと、毎回リクレクションが必要ジャン? それなら、受け側でHTTP Method毎のAction名を指定するほうがよっぽど効率がいいってものですよ。 その辺踏まえて、以下のようなActionに。

/ResourceController [RESTfulFilter] public void Photo(int? id){...} public void PhotoPost(){...} public void PhotoPut(int id){...} public void PhotoDelete(int id){...} ※ActionResultじゃなくてvoidなのは気にしない。

で、 RESTfulFilterのExecutingでHTTP Method見て、現在のAction名の後ろに自動でInvokeActionする名前を作る(もちろん、属性のパラーメータでHTTP Method毎のAction名をセットできるようにした方が優しい)。 こんな風にしとけば、ActionFilterAttributeは1か所に指定するだけで済むので、効率よしって話です。 が、その他のActionがpublic(じゃないと、InvokeAction出来ない、よね?)なので、直接そのActionにもアクセスできちゃうのが何か気持ち悪し。出来て何が悪いってわけじゃ全然ないんだけども。なんとなく。直接のアクセスは拒否したいな、なんて思ったり思わなかったり。

そんなときはまたしてもActionFilterAttribute作成しましょう。 たとえば...RequireReactionAttributeとかって言う名前で。 RESTfulFilter でInvokeActionする直前に、Controllerのプロパティで例えばIsReaction = tureとかしておけば、その先でIsReactionがfalseなら実行をキャンセル(filterContext.Cancel=true)にしちゃう。そうしとけば直接アクセスしてほしくないActionに制限かけれたり(Controllerのbaseプロパティで分かる方法あるのかな)。 同じく、RequireAjaxAttributeとかって言うのも作っちゃって、XmlHtpRequest以外の要求は拒否(リクエストヘッダに何か入れとく)なんてことも、簡単(ヘッダ書き換えできちゃうから完璧なわけじゃないけど)。 prototype.jsなら勝手にヘッダーに入れてくれて楽ちんだし、DELETEやPUTのときには_methodにHTTP Method入れてPOSTにするから、そこ見て判定するようにしとくと、さらに楽ちん。

まだまだ工夫の余地ありだけど、HTTP Methodの自動InvokeActionだけでも、だいぶコード量削減出来てうれしい限りです。

2008年5月14日水曜日

Pagenation

なんか英語だとかっこよく見えるけど、普通にページ切り替えのこと。 LINQ使ってIQueryableなデータをページ切り替えできるようにするってばよ。

ASP.NET MVC: PagedList<T> : Rob Conery

ここのソースをそのまま流用してもよし。少しいじるもよし。自作するもよし。 結局はこういう形に落ち着くはず。 IPagedListとインターフェースを定義しておくのにも訳があり。

ASP.NET MVC - Pagination View User Control | Code-Inside Blog International

↑こういうページ切り替えコントロールを作成する時に、インターフェース渡しにしておくことで、型に依存しなくて済むから。IPagedListを受け取るようにコントロールを作っておけば、どんな型でも関係なく、ページに関する情報のみ取得できるでしょ。

上記、いずれもASP.NET MVCって書いてるけど、LINQだからMVCかどうかは関係ないと思われる。 ところで、ASP.NET MVCに限らず、AJAX的なページの部分更新を行う場合って、一般的な方法というのがあるんだろうか。 今はMVCで作ってるとはいえ、MVCに限った悩みとは思えず。 特に、リストでデータを持ってる場合とか。

<div id="container">
 <ul id="list">
   <li id="item1">Data1</li>
   <li id="item2">Data2</li>
   <li id="item3">Data3</li>
 </ul>
</div>

こんな感じのHTMLを持ってるとして、ulにliを1つ追加するような処理ってどうしましょうか、っていうね。 1.ページ全体更新。 2.ul全体(ulタグ込み)を取得して、divのinnerHTMLを書き換え。 3.追加対象のli(liタグ込み)を取得して、ulのinnerHTMLに書き足し。 4.追加対象のliの中身(liタグ無し)だけを取得して、document.createElement('li')のinnerHTMLに値を入れた後、ulにappendChild。 5.JSONか何かでデータだけを取得して、クライアントサイドですべての表示要素をcreateElement。

上からデータ量の多い順(3と4は誤差の範囲だけど)。 1はそもそも、ページ全体なんだからAjaxとか関係なし。 5はサーバーサイドでのビュー定義がまったくもって役に立たなくなりかねないし、JavaScriptコードが多すぎて面倒。Google Gearとかだとこの方法じゃないとつらいと思われるけど、今回は対象外としたい。 2・3・4のどれを選ぶのが妥当なんだろうか。 2の場合、ページの初回表示の時に使用するであろうコントロール(ASP.NETだとUserControlだし、Railsならpartialか)を、 Ajaxの結果としてResponse.Write。データ1個を返すだけだったりしても、リスト全体が対象になる分、データ量は増えるけど処理は簡単。 3と4の場合(ほとんど一緒)、リスト全体を表現するコントロールと、リストアイテムのコントロールに分けて作っておけば、ページ出力の場合と、部分出力の場合でも、それほどコード量に差が出てくるわけでもないんだけど、データ量は少なく済む。 んじゃ、常に3か4でいいのか?ってことになるんだけど、結局4だとcreatElementしなきゃいけなくて、コードがめんどっちくなる。3はというと、結構いいんだけど、サーバーサイドのコントロール階層が少し深くなって見通し悪しな気がしなくもなく。 でも結局は、2と3の組み合わせ(適材適所で!)になるんだろうとは思うけど。 今はずっとASP.NET MVCで、PostBack方式でのWebFormsを全然使ってないんだけど、サニタイズ気をつけてれば、こっちのほうが自分の思考にあってて作りやすい気がしてならない。 それもこれも結局は好みの問題なんだろね。

ハァ~。NHLプレーオフ生で観たい。デトロイト今年こそやってくれそうだし。

2008年5月6日火曜日

面白いPNGの利用法

nihilogic: Compression using Canvas and PNG-embedded data 面白い人もいるもんだな~。 JavaScriptファイルをPNGファイルにしちゃって、CanvasのgetImageDataを使って再度JavaScriptに復元。 画像ファイルはあまりにも砂嵐。スクリプトの文字コードをそのままRGBにセットして、画像を生成してるからね。なので、8ビット(0~255)グレースケールの画像になるし、1文字が1ピクセル。そりゃ砂嵐だぜ。 CanvasはIEじゃ使えないからFirefoxとOpera、あと最新のWebkitでしか、この方法は使えませんよ、って言う事なんですね。 スゴイな。こんな発想なかった。GZipでいいじゃね~か、と思う自分がちょっと恥ずかしいっす。 スクリプトから画像生成するのはPHPのコードになってたけど、そのままだと試しにくいので、C#で書きなおしてみた(そのままデス)。

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

using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace MakeJSPng.Controllers
{
 public class HomeController : Controller
 {
   public void Index()
   {
     string js = this.ReadFromRequest("JSSource") + "";
     int hash = Math.Abs(js.GetHashCode());
     string fileName = string.Format("{0}.png", hash);
     string filePath = Request.MapPath("~/PNGs/") + fileName;

     if (js.Length > 0)
     {
       ViewData["JSSource"] = js;
       byte[] bytes = Encoding.UTF8.GetBytes(js);
       int width = (int)Math.Ceiling(Math.Sqrt((double)bytes.Length));
       int height = width;

       using (Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb))
       {
         int pos = 0;
         for (int y = 0; y < height && pos < bytes.Length; y++)
         {
           for (int x = 0; x < width && pos < bytes.Length; x++)
           {
             int val = (int)bytes[pos++];
             bmp.SetPixel(x, y, Color.FromArgb(0, val, val, val));
           }
         }
         bmp.Save(filePath, ImageFormat.Png);
       }
     }

     ViewData["PngPath"] = "/PNGs/" + fileName;

     RenderView("Index");
   }

   public void About()
   {
     RenderView("About");
   }
 }
} 

ASP.NET MVC Preview2で動くよ。 Home/Index.aspxは↓。

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MakeJSPng.Views.Home.Index" %>
<%@ Import Namespace="MakeJSPng.Controllers" %>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">
<script type="text/javascript" src="/Content/pngdata.js"></script>
<% using (Html.Form<HomeController>(c=>c.Index())) { %>

<p>スクリプト</p>
<%=Html.TextArea("JSSource", ViewData["JSSource"]) %>

<p>
<%=Html.SubmitButton("Make", "生成") %>
</p>

<p>画像</p>
<img src="<%= ViewData["PngPath"] %>" id="png" />

<p>復元</p>
<%=Html.TextArea("PNGSource", "") %>
<% } %>


<script type="text/javascript">

var path = '<%= ViewData["PngPath"] %>';
loadPNGData(path, function decodeCallback(data){
 var element = document.getElementById("PNGSource");
 element.value = data;
});

</script>
</asp:Content>

でも、IEじゃ動かないよ! 試しに、prototype.1.6.0.2.jsを圧縮した画像↓。 img.aspx

砂嵐。 本家はこれが30KBなんだけど、こっちは50KBなんでじゃ~!!画像サイズ少し違うし。PHPでのデフォルトエンコードがUTF8じゃないのかもしれないしね。 本家は8bppだけど、こっちは24bppなのが原因?それじゃしょうがないよね! ところでPHPのコードは良くわかんない(GDが良くわかんない?)けど、TrueColorを保存して自動で8ビットになるんでしょうか? ちょっとした息抜きってことで。

2008年5月2日金曜日

SSDS

SQL Server Data Servicesの略でSSDS。 SQL Server Data Services

クラウドコンピューティングっていうんですかね。 公開は全然先の話だからAmazon,Googleにはずいぶん遅れてしまうけど、SQL ServerをエンジンにLINQを使ったオンライン上のDB。CPUとストレージリソースいずれも雲の中。 SQL Server Data Services Overview

ここから概要のPDFがダウンロードできるっす。 Customer { SSDS account (1..N) { Authority (1..N) { Container (0..N) { Entity (0..N) こんな階層。 Entities are “flat scalar property bags.” と、書かれてるように基本Dictionary<T1,T2>の型をEntityに入れて自分でフィールドを管理。クエリー発行はLINQで。 Jeff's thoughts on Software Architecture, Large Scale Services and the Technical world at large : Interacting with SQL Server Data Services using SOAP ↑ここに基本的なサンプルコードあり。.NET Framework上での開発ならSOAPでやれば、超簡単にプロキシができるからね。

発表はMIX08のときだっけ? 忘れかけてた矢先、Private Beta Invitationって書かれたメールが来た。 使えるようになるのはずいぶん先の予定、ってことでほっといたのに。 とりあえず、ドキュメントをダウンロードして読んでみます...。 簡単なサンプルとか作ったり? ストアド書けるのかな~...。

あと、dotNetOpenIDScott Hanselman's Computer Zen - The Weekly Source Code 25 - OpenID Edition ASP.NET MVCでの実装サンプルもあったから動かしてみたら、もちろんだけどちゃんと動いてちょっと感動。しかもコードはチョビット。

public void Authenticate() { var openid = new OpenIdRelyingParty(); if (openid.Response == null) { // Stage 2: user submitting Identifier openid.CreateRequest(Request.Form["openid_identifier"]).RedirectToProvider(); } else { // Stage 3: OpenID Provider sending assertion response switch (openid.Response.Status) { case AuthenticationStatus.Authenticated: FormsAuthentication.RedirectFromLoginPage(openid.Response.ClaimedIdentifier, false); break; case AuthenticationStatus.Canceled: ViewData["Message"] = "Canceled at provider"; RenderView("Login"); break; case AuthenticationStatus.Failed: ViewData["Message"] = openid.Response.Exception.Message; RenderView("Login"); break; } } }

サンプルそのままでスイマセン。 認証はOpenIDでデータストレージはSSDSで、っていうのが自分でリソースを持たない方法としてなかなかいいかも。BLOB持てるなら画像とかのファイルもSSDSなんだけどどうなんだろ。