いや~、ずいぶん変わってしまいました。
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を読み込むようにしてます。
実行すると、最初が↓。
PUTリンクをクリックすると↓。
んで、どろんじょリンクをクリックすると↓。
これで今日からRESTful!