2010年1月2日土曜日

ASP.NET MVCに似合うSubmitの振り分け

How can I change the action a form submits to based on what button is clicked in ASP.NET MVC? - Stack Overflow

前にも、この方法について考えたことがあって、その時はストラテジパターンを使ってDelegateでのコマンド振り分けでの実装をしてたんですが、少し前に違う方法を実装してるのを見て悔い改めたんです。

MVCによく似合う方法は、属性ベースで対象となるアクションを振り分ける(判定する)方法ですよね。

AcceptVerbsでHTTP Method毎に処理を振り分ける事ができるのを上手く利用して、Submitの値毎に処理を振り分けるためにActionMethodSelectorAttributeを派生したSubmitCommandAttributeというのを定義していました。

using System;
using System.Reflection;
using System.Web.Mvc;

namespace MvcApplication2.Controllers
{
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
  public class SubmitCommandAttribute : ActionMethodSelectorAttribute
  {
    private string _submitName;
    private string _submitValue;
    private static readonly AcceptVerbsAttribute _innerAttribute = 
new AcceptVerbsAttribute(HttpVerbs.Post); public SubmitCommandAttribute(string name) : this(name, string.Empty) { } public SubmitCommandAttribute(string name, string value) { _submitName = name; _submitValue = value; } public override bool IsValidForRequest(ControllerContext controllerContext,
MethodInfo methodInfo) { if (!_innerAttribute.IsValidForRequest(controllerContext, methodInfo)) return false; // Form Value var submitted = controllerContext.RequestContext
.HttpContext
.Request.Form[_submitName]; return string.IsNullOrEmpty(_submitValue) ? !string.IsNullOrEmpty(submitted) : string.Equals(submitted, _submitValue,
StringComparison.InvariantCultureIgnoreCase); } } }

これだけなんですけども...。HttpPostのコードを参考に内部にAcceptVerbsAttributeを保持してPOST時のみをIsValidにしてますが、DeleteとPutも必要なら要修正。

using System.Web.Mvc;

namespace MvcApplication2.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }

    [ActionName("Different")]
    [SubmitCommand("DoSave")]
    public ActionResult DifferentSave()
    {
      TempData["message"] = "saved! - defferent";
      return View("Index");
    }

    [ActionName("Different")]
    [SubmitCommand("DoDelete")]
    public ActionResult DifferentDelete()
    {
      TempData["message"] = "deleted! - defferent";
      return View("Index");
    }

    [ActionName("Same")]
    [SubmitCommand("DoSubmit","保存")]
    public ActionResult SameSave()
    {
      TempData["message"] = "saved! - same";
      return View("Index");
    }

    [ActionName("Same")]
    [SubmitCommand("DoSubmit","削除")]
    public ActionResult SameDelete()
    {
      TempData["message"] = "deleted! - same";
      return View("Index");
    }
  }
}

コントローラでこんな感じにアクションを定義しておいて、Viewを以下のようにしておきます。

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Index</title> </head> <body> <h1><%= TempData["message"] ?? "Click some button" %></h1> <h2>異なるnameのsubmit</h2> <% using (Html.BeginForm("Different", "Home")) { %> <input type="submit" name="DoSave" value="保存" /><br /> <input type="submit" name="DoDelete" value="削除" /> <% } %> <h2>同一nameでValueの異なるsubmit</h2> <% using (Html.BeginForm("Same","Home")) { %> <input type="submit" name="DoSubmit" value="保存" /><br /> <input type="submit" name="DoSubmit" value="削除" /> <% } %> </body> </html>

動かしてみると、最初に↓。ボタンが4種類出てきます。上2つはnameとvalueの異なるsubmitで、下2つがnameが同じでvalueが違うsubmit。

submit1

上から順にボタンを押したのが↓。

submit2 submit3 submit4 submit5

ちゃんとTempDataの値が違うのが確認できますね(ページ上部のH1)。

で、ココまで書いときながら、前にどこで見たのかを検索して探し出してみてショック。

ASP.NET MVC – Multiple buttons in the same form - David Findley's Blog

まんま、同じになるという大失態。やっぱり年始からこんなコードを書いてて先が思いやられる...。