I have read many posts about allowing different controller actions for different view-buttons. However, I cannot get this to work.
I use this code-snippet obtained from http://blog.ashmind.com/2010/03/15/multiple-submit-buttons-with-asp-net-mvc-final-solution/.
public class HttpParamActionAttribute : 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;
}
}
When I step through this code, I see a compare of actionName
with methodInfo.Name
. How can these EVER be equal when the whole purpose is to name the method different from the controller's action.
What is the return value of true or false actually mean as to the behavior/functionality?
Should I be overriding the 'fciContactUs' action with 'Action'?
The Controller = "HomeController"
[HttpParamAction]
[ActionName("Action")]
[AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
public ActionResult DoClearForm(fciContactUs p_oRecord)
{
return RedirectToAction("fciContactUs");
}
[HttpParamAction]
[ActionName("Action")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TrySubmit(fciContactUs p_oRecord)
{
// This must be the Submit command.
if (ModelState.IsValid)
{ ...etc....}
}
The View (view name is 'fciContactUs') form-start:
@using (Html.BeginForm("Action", "Home")) {
The view-buttons:
<input type="submit" name="TrySubmit" value="Submit" />
<input type="submit" name="DoClearForm" value="Clear Form" />
Further, this IsValidName
method ALWAYS returns false and the methods NEVER get executed.
I am concerned that there is an inconsistency in the action-name, the view-name, the controller-name, the button-names and the ActionNameSelectorAttribute
class override.
I am new to MVC and this whole thing has got me twisted up.
Any comments or assistance will be greatly appreciated.
Let's start with the HttpParamActionAttribute
. This inherits from ActionNameSelectorAttribute
which:
Represents an attribute that affects the selection of an action method.
so, when applied to an action, and a request (get or post) comes in, this attribute will be called to see if that action is indeed the correct action.
It determines this by calling IsValidName (slightly confusing method, would be better 'IsMatchingAction' or similar). IsvalidName:
Determines whether the action name is valid in the specified controller context.
With parameters:
controllerContext: The controller context
actionName: The name of the action
methodInfo: Information about the action method
taken from the blog post, you can get the request and all the values on that request via the controllerContext
.
This returns true
if it matches and false
if it isn't the action we're looking for.
When this gets hit:
- actionName = the action name from the
BeginForm
, hardcoded to "Action"
- methodInfo = the controller-action (method) that the attribute is applied to, eg
TrySubmit
So the first check:
if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
return true;
is just a catch-all in the case where you've specified something other than Action in your BeginForm
so that it goes to the requested action (ie not "Action").
The second check checks that you have specified "Action" in the BeginForm
Now the "clever" part:
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
this checks all the request
parameters to see if there is a parameter that matches the controller-action (method).
When you click a submit
button, the name
of that submit button is passed in the request parameters
So if you have:
<input type="submit" name="TrySubmit" value="Submit" />
<input type="submit" name="DoClearForm" value="Clear Form" />
if you click your "Submit", then:
HttpContext.Request["TrySubmit"] == "Submit"
HttpContext.Request["DoClearForm"] == null
and if you click your "Clear Form", then
HttpContext.Request["TrySubmit"] == null
HttpContext.Request["DoClearForm"] == "Clear Form"`
the actual value ("Submit"/"Clear Form") isn't needed, only that it's not null.
So the code
request[methodInfo.Name]
checks the request parameters for the existance of an item which matches the current controller-action (method) name, eg:
[HttpParamAction]
public ActionResult DoClearForm()
{
To answer your first question:
I see a compare of actionName with methodInfo.Name. How can these EVER be equal when the whole purpose is to name the method different from the controller's action.
The first compare is an override for when the BeginForm
doesn't specify Action and the BeginForm
's action does match the controll-action (method) name.
So, in your scenario, no they won't be equal and shouldn't be - it's the last check that's relevant.
Now the question is:
why doesn't this work for you?
The problem is that your action names don't match the expected action names, because you have this:
[ActionName("Action")]
so the HttpParamAction
attribute finds your controller-action (method) and says "use this one", but then MVC says, but I'm looking for "Action" and that's not "Action" so I'll give you "The resource cannot be found". I'm not 100% of the reason it's doing this, but it's the same if you use MVC5 Route("Action")
- it can't find the action having already matched it using the attribute.
If you remove [ActionName("Action")]
then all should be ok.
Alternative: if you remove the first two checks (if form action = controller action and if form action != "Action") and only apply the attribute to the methods you need (and why would you apply it elsewhere?), then it seems to work fine.
Now, I'm using [Route("")]
and changed:
the form to:
using (Html.BeginForm("", "", FormMethod.Post, ...
the controller to:
[HttpParamAction]
[HttpPost]
[Route("")]
public ActionResult DoClearForm()
{
(the initial [HttpGet]
also has [Route("")]
)
and the attribute to:
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
}
}
As an alternative, it looks like your "ClearForm" button simply redirects to the page. You could do this more easily with a simple @Html.ActionLink("Clear Form", "fciContractUS")
and a bit of css to make it look like a button.
You'll also have an issue if you have any client-side validation (eg required fields) as you won't be able to "submit" to clear the form until they have values.