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).
@
using
(Html.BeginForm("Action"
, null
))
{
<
button
name
="Save"
>
Save</
button
>
<
button
name
="Publish"
>
Publish</
button
>
}
Figure 1
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
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 SubmitButtonNameActionNameSelector implementation (Figure 4).
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
public
class
BlogController
: Controller
{
[HttpPost
, FormAction
]
public
ActionResult
Save()
{
//...
}
[HttpPost
, FormAction
(Prefix = "Action_"
)]
public
ActionResult
Publish()
{
//...
}
}
Figure 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.