Introduction
Recently, I started working on a large eCommerce website, implemented in ASP.NET MVC 3. I soon found that many of the forms in the site contained code similar to Figure 1, which I took an instant dislike to. The reason I dislike it is that "Action" doesn't relate to anything (i.e. it isn't an actual action name) and causes the URL to change when the form is submitted, so a URL like "/Blog/AddPost" will suddenly become "/Blog/Action". Another side-effect is that the destination action method must specify explicitly the view name. I then discovered that it was to enable multiple submit buttons by using an implementation of the ActionNameSelectorAttribute class (Figure 2).
1 2 3 4 5 | @ using (Html.BeginForm( "Action" )) { < button name ="Save" > Save < / button > < button name ="Publish" > Publish < / button > } |
Figure 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class SubmitButtonNameActionNameSelector : ActionNameSelectorAttribute { public override bool IsValidName( ControllerContext controllerContext, string actionName, MethodInfo methodInfo) { if (actionName.Equals(methodInfo.Name, StringComparison .InvariantCultureIgnoreCase)) return true ; if (!actionName.Equals( "Action" , StringComparison .InvariantCultureIgnoreCase)) return false ; var request = controllerContext.RequestContext.HttpContext.Request; return request[methodInfo.Name] != null ; } } |
Figure 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class BlogController : Controller { [ SubmitButtonNameActionNameSelector ] public ActionResult Save(...) { ... } [ SubmitButtonNameActionNameSelector ] public ActionResult Publish(...) { ... } } |
Figure 3
Previous technique
In other projects I've used a popular technique of binding the button's name to a parameter name in the action method and then determining the button's value, in order to branch to a different method. There are downsides to this approach but the main thing I dislike about it is that it's quite messy. The ActionNameSelectorAttribute implementation allows for a cleaner separation of behaviour for each button.
Solving the problem
To circumvent the problems I have mentioned, I decided to use a variation of the SubmitButtonNameActionNameSelectorimplementation (Figure 4).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class FormActionAttribute : ActionNameSelectorAttribute { public override bool IsValidName( ControllerContext controllerContext, string actionName, MethodInfo methodInfo) { return controllerContext.HttpContext.Request[Prefix + methodInfo.Name] != null && !controllerContext.IsChildAction; } public string Prefix = "Action::" ; } |
Figure 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class BlogController : Controller { [ FormAction ] public ActionResult Save(...) { ... } [ FormAction (Prefix = "Action_" )] public ActionResult Publish(...) { ... } } |
Figure 5
1 2 3 4 5 | @ using (Html.BeginForm()) { < button name ="Action::Save" > Save < / button > < button name ="Action_Publish" > Publish < / button > } |
Figure 6
Conclusion
By prefixing the button's name there is no need to have "Action" as the form's action (Figure 6), which means that the URL stays the same and there is no need to explicitly specify the view name.
More