Thursday, November 10, 2011

Multiple submit buttons with MVC 3.

I recently needed to create a MVC (MVC3) view with multiple submit buttons.
A quick look around with Google and I found a couple of approaches, but I thought to myself, there must be a cleaner way.

Up till now I've been using the simple approach of creating a controller action where the method takes an extra parameter to describe which action to take like:

Controller:
public class MyController
{
public ViewResult Index() { return View();}

public ViewResult DoSomething(string action, string someData)
{
if(action == "Action1") {}
else if(action == "Action2") {}
else {/*unknown action.*/ }
}
}

xhtml:
<form action="My/DoSomething>
<input name="someData"/>

<input type='submit' name='action' value='Action1' />
<input type='submit' name='action' value='Action2' />
</form>

What I wanted was to be able to define my Controller to have multiple Actions, one for each submit action on a Form, like this:

public class MyController
{
public ViewResult Index() {return View();}

public ViewResult Action1(string someData) {return View();}
public ViewResult Action2(string someData) {return View();}
}

But alas, with MVC I couldn't construct a Route that would do this; MVC routing always uses the Forms action attribute to decide which Controller method to call.

In the end the answer wasn't that hard. I just created a smarter IRouteHander, and applied it to the MVC default route.
The HTML for the form looks almost the same, though I remove the Action from the Form's action attribute, since the route handler will look after that.
xhtml:
<form action="My>
<input name="someData"/>

<input type='submit' name='action' value='Action1' />
<input type='submit' name='action' value='Action2' />
</form>


Now the methods on my controller don't need 'special' arguments, and the world is a better place :D
Here is the custom RouteHandler:

namespace derek.kowald.MVC
{
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
/// <summary>
/// Custom route handler so a form submit button can
/// define the mvc action to take.
/// </summary>
/// <remarks>
/// Be careful to only define submit buttons with the name action.
/// </remarks>
/// <example>
/// Update your routes to use this hander:
/// routes.MapRoute("Default",
/// "{controller}/{action}/{id}",
/// new { controller = "Home", action="Index", id = UrlParameter.Optional})
/// .RouteHandler = new SubmitActionHandler();
///
/// Update your markup so sumbit button(s) have the name action:
/// &lt;input type='submit' name='action' value='MyAction' /&gt;
/// </example>
public class SubmitActionHandler : IRouteHandler
{
#region IRouteHandler Members
/// <summary>
/// If an input parameter named 'action' is provided,
/// update the RouteData action property with the form data.
/// </summary>
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string reqAction = null;
if (requestContext.HttpContext != null &&
requestContext.HttpContext.Request != null)
{
reqAction = requestContext.HttpContext.Request["action"];
}
if (!String.IsNullOrEmpty(reqAction))
{
requestContext.RouteData.Values["action"] = reqAction;
}
return new MvcHandler(requestContext);
}
#endregion
}
}

No comments:

Post a Comment

A short list of C# coding issues.

Injecting services into entities It been bugging me for a while now that to use services in a entity can be a bit of pain. It usually starts...