Why is my form triggering the wrong controller action? - asp.net-mvc

This form has multiple submit buttons, when clicked, it calls a simple JavaScript function to change the value of a hidden input (function is called "setHidden". This worked before, after some other not relevant code, it has ceased working.
Essentially, the action it is supposed to call is never called, instead it seems to default back to a previous URL.
The Form:
<form action="/League/RemoveOwner" method="post">
<input type="hidden" value="1007" name="lid"/>
<input type="hidden" value="0" id="index" name="index"/>
<input type="image" src="../../Resources/Images/Delete.png"
height="12" alt="Remove Owner" title="Remove Owner"
onclick="setHidden('index', '1031')"/></a> coach<br />
</form>
The Controller:
[HttpPost]
public ActionResult RemoveOwner(int id, string index)
{
//yada
return PartialView();
}
When clicking the image, it should call the remove owner controller, instead it calls the "View" controller:
public ActionResult View(int id) {
//yada
return View();
}

After searching high and low, I finally found the problem. There was another form on the page that didn't have a closing tag. THAT form was supposed to take it back to the "View" controller. It was on a partial, so I didn't catch it until... well until I had racked my brain for 2 hours...

Another suggestion for you form action code is don't write the absolute path for your form action. You could use the Html.BeginForm() or write your site url in the web.config and fetch it to the Application["URL"] while the application start at global.ascx. Then your form should look like:
<form action="<%=Application["URL"]%>League/RemoveOwner">
This approach can avoid some errors while publishing to the IIS or another web server.

Related

View form calls on postback all controllers' actions with [HttpPost] from different controllers

I know that maybe the title sounds a bit weird but I believe that my problem is weird indeed. I have an ASP.NET MVC 4 application (this is my first MVC real-world application) with Razor view-engine.
I have a layout view where I'm rendering two partial views like this:
<!-- Login -->
#Html.Action("RenderLoginPopup", "Login")
<!-- Registration -->
#Html.Action("RenderRegisterPopup", "Login")
Each of those actions from the Login controller just renders a partial view:
[ChildActionOnly]
public ActionResult RenderLoginPopup()
{
return PartialView("Partial/_LoginPopupPartial");
}
Just for exemplification sake (both are built the same way), the login partial view contains an ajax form like this:
#using (Ajax.BeginForm("Login", "Login", new AjaxOptions()
{
HttpMethod = "POST",
OnSuccess = "loginResponseReceived"
}, new { #id = "loginForm" }))
The Login action from the Login controller (the target of the form) is signed with the following attributes (worth to mention and notice the HttpPost one):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public JsonResult Login(LoginModel model)
{ }
So far, so good... Everything works perfect - both the login and the register actions are working without any issues.
The issue that I want to speak about shows-up when I have a #Html.BeginForm() in a view that is loaded along with the main layout. For example, if I have a pure and simple form like this:
#using (Html.BeginForm())
{
<input type="hidden" name="name"/>
<input type="submit" value="Send"/>
}
along with the controller CaptionExtendedController:
[HttpPost]
public ActionResult Index(string nume)
{
return View();
}
So, in the end, in my final html generated file I will have 3 forms - 2 for login and register (ajax) and one simple form generated from the last view. Please keep in mind that all three forms are independent (meaning that they are not one in another).
The issue is that everytime I'm pressing the button "Send" from the last form all controllers that are signed with the [HttpPost] attribute from my view (Login, Register from LoginController and Index from CaptionExtendedController) gets called.
WHY??? In order to have a temporary fix, I've removed the [HttpPost] attribute from the Login and Register actions and now it's working but I don't think this is correct.
Please, there is someone who can explain me why this is happening and eventually point me to the right direction in fixing this issue?
Thank you in advance.
Try specifying the controller and action with your Html.BeginForm and we can start from there. Also, you can utilize #Html.RenderPartial to render your partials which would get rid of some of your unneeded actions/controllers, making it a bit more manageable.
This doesn't address the root problem, but might be a work-around which is all you need anyway. :)
You could write some jQuery which catches the button click and then submits the form directly by name. For example, if you add the id below:
#using (Html.BeginForm())
{
<input type="hidden" name="name"/>
<input type="submit" id="regularSubmitButton" value="Send"/>
}
Then you could write:
$(document).ready(function() {
$("#regularSubmitButton").click(function(e) {
e.preventDefault();
$(this).parent("form").submit();
return false;
});
});
I'm not sure it would work without seeing everything, but seems to be worth a try.
Cheers,
Michael
It doesn't make sense anymore... Starting from the comments and answers, I've mapped 3 functions to the submits on those tricky forms: login, register and index (also I've put back the HttpPost attribute to the Login and Register actions). In those jquery functions I've just put an alert with a string (name of the form); in order to be able to write a jquery id-based selector, I've declared also the last form with an id (it didn't had it; only login and register had one), like this (an example taken from another form with the same issue):
#using (Html.BeginForm("Index", "PersonalizeCard", new {data = Model.EncryptedDataQueryStringValue}, FormMethod.Post, new {#id = "personalizeCardForm"})) { }
(what has been added - last two parameters - form method and html id).
After this, I've run the application and no exception anymore... I've put breakpoints on the login and register actions - nothing... I've even removed those 2 extra parameters from the BeginForm - still nothing...
WHY???? Again, why??? I mean, I'm not upset that it's fixed but I don't understand why it's fixed by itself...
THANK YOU FOR ALL YOUR TIME AND COMMENTS / ANSWERS.

Exclude <input type='file'> from ViewModel

I have the following file input tag in the "Create" View:
<input type="file" id="RequestFile" name="RequestFile"/>
#Html.ValidationMessage("RequestFile")
The ViewModel contains this corresponding property:
[Required(ErrorMessage="Please select a file")]
public HttpPostedFileBase RequestFile { get; set; }
This works fine in the "Create" View, but in the "Edit" View, I get ModelState.Isvalid as false. Using the same ViewModel I would like to exclude this field from validations because I would not want to upload the file again.
I tried simply disabling the input tag like this:
<input type="file" id="RequestFile" name="RequestFile" disabled/>
This has a disabled input control but the Validation still fired.
Also applying the BindAttribute in the Controller did not work (see this Question)
Ideally (I know it sounds unlikely), if there is a server-side solution to this, please post your thoughts. If there is a small client-side trick, please let me know!
The best ways are remove the property altogether, and always access it directly from the form collection (and manually validate it) or manually remove the model state error using the property name (as #cheesemacfly has in his comment, ModelState.Remove("RequestFile")). The latter makes it very easy to fix then.
You could use form.onsubmit to check to see it document.getElementById("RequestFile").value is not null/empty and cancel the submit if it is.
something like
<form onsubmit="if(!document.getElementById('RequestFile').value){alert('Please select a file.');return false;}" >
<input type="file" id="RequestFile" name="RequestFile" />
<input type="submit"/>
</form>
return false cancels the submission.
http://jsfiddle.net/Cg7HY/1/
or put it in the click event of the submit button itself
http://jsfiddle.net/Cg7HY/3/

HttpPost vs HttpGet attributes in MVC: Why use HttpPost?

So we have [HttpPost], which is an optional attribute. I understand this restricts the call so it can only be made by an HTTP POST request. My question is why would I want to do this?
Imagine the following:
[HttpGet]
public ActionResult Edit(int id) { ... }
[HttpPost]
public ActionResult Edit(MyEditViewModel myEditViewModel) { ... }
This wouldn't be possible unless the ActionMethodSelectorAttributes HttpGet and HttpPost where used.
This makes it really simple to create an edit view. All the action links just points right back to the controller. If the view model validates false, you just pop right back to the edit view again.
I will be bold and say this is best practice when it comes to CRUDish things in ASP.NET MVC.
EDIT:
#TheLight asked what was needed in the view to accomplish the post. It's simply just a form with method POST.
Using Razor, this would look something like this.
#using (Html.BeginForm())
{
<input type="text" placeholder="Enter email" name="email" />
<input type="submit" value="Sign Up" />
}
This renders the following HTML:
<form action="/MyController/Edit" method="post">
<input type="text" name="email" placeholder="Enter email">
<input type="submit" value="Sign Up">
</form>
When the form is submitted, it will perform an Http Post request to the controller. The action with the HttpPost attribute will handle the request.
Its so you can have multiple Actions that use the same name, you can use the HttpPost attribute to mark which method gets handled on a Post request like so:
public ActionResult ContactUs()
{
return View();
}
[HttpPost]
public ActionResult ContactUs(ContactUsModel model)
{
//do something with model
return View();
}
As far as best practices for HttpGet and HttpPost, it is good practice in any web development to use HttpPost for Creates, Updates, and Deletes (data modification). Post is good because they require a form submission, which prevents users from clicking poisoned links(e.g. [https://www.example.com/Delete/1]) in emails, social sites, etc. and changing data inadvertently. If you are basically just Reading data HttpGet works great.
See OWASP for more in-depth security considerations and why the validation token increases security.
This is mainly so that you can have two Actions with the same name,
one which is used on GETs and perhaps displays a form for user entry and the other being used on POSTs when the user submits the form displayed by the original GET. If the Actions are not differentiated in this way, an error will occur due to being unable to resolve which Action is intended to handle the request.

Why is MVC persisting on HttpGet?

I thought I understood MVC until now.
To me a GET should be from a clean slate. But I discoved today MVC assumes a GET to be a POST if a page request a get from itself.
The text box should always show the text "Red" but instead it persist its last value from the previous view.
Acting like an HTTPPost. You have to uncomment ModelState.Clear to act like a HttpGet.
This appears to me to be a bug.
<form action="" method="get">
<div>
<%=Html.TextBox("search") %>
<input type="submit" value="Search" />
</div>
</form>
[HttpGet]
public ActionResult Index(string search)
{
//ModelState.Clear();
ViewData["search"] = "Red";
var items = GetYourTestData;
if (!string.IsNullOrEmpty(search))
{
var items2 = items.Where(x => x.Color == search).ToList();
return View(items2);
}
return View(items);
}
The search results returns correct and different data so it is not browser cache.
For the purpose of a search results page it does not make sense to have to redirect to another page to avoid this. That is why I chose GET thinking it should be clean each time.
Like I stated in the description. Other contents on the page does change so it is not cache. Uncomment ModelState.Clear() and all is good so that is not cache.
You can put a dynamic datetime label always showing the latest time from the server and it does change. That also proves it is not page cache.
It is a very simple test to do. Yes, just a sure as there is gravity, MVC2 framework 4.0 considers it an HTTPPost if the HttpGet requested page is the same as the requester. If you are not aware of this during programming the results could be disastrous. For instance, If someone like TurboTax uses MVC I hope that their developer are aware of that.
... ViewData["AdjustedTaxAmount"]=3435.00; ... is not going to work unless they call this
ModelState.Clear().
I don't know of why there should be ModelState on a get so
one sure fire work around is to
Inherit Controller from a base class
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
ModelState.Clear();
}
base.OnActionExecuted(filterContext);
}
The browser will cache GET requests that are from the same URL. If you add a querystring variable that is random then the GET request will be different each time and the browser will not cache the result.
That's to be expected.
When you submit a form via GET, you're serializing its elements and passing those to the target via the QueryString. So, it makes sense that your search value would be part of MVC's model in the next request. When you use the Html.TextBox helper, it's going to automatically inject the model's value as the HTML input's value. That's why you're seeing the value cover over.
One solution to avoid that is to not use the HTML Helper to render the input:
<form action="" method="get">
<div>
<input type="text" name="search" />
<input type="submit" value="Search" />
</div>
</form>
Then, you should get the clean slate you're expecting after each form submission.

asp.net mvc actionlink shows address in FF, cannot get button to function

I'm currently in the process of learning ASP MVC and am running into a few issues.
First, when I use
<%=Http.ActionLink("Add / Modify", "AddModify" %>
it will show as Add / Modify (/Home/AddModify) in Firefox and Add / Modify in IE. It is doing that to all links in FF and none in IE. Anyone know what reasoning is for that?
Edit: What is displayed in the browser (FF in this case) is "Add / Modify (/Home/AddModify)" while in IE shows just "Add / Modify".
Here is a screenshot of what I see on my site in FireFox: http://img6.imageshack.us/img6/1331/19748435.png
You can see how it shows the text and the appropriate link afterwards (only populated in Database with /).
Also, I am trying to have buttons (both standard and image) on my site which will link to new pages, while also performing hidden tasks (saving data, etc...). Anyways, When I do the following:
<form method="post" action="/Home">
<input type="submit" value="AddModify">
</form>
and the controller has a simple
[ActionVerbs(HttpVerbs.Post)]
public ActionResult AddModify()
{
return View();
}
And I still cannot get that function to call, yet when I do http://localhost:port/Home/AddModify, the function calls and I can get to that page. I am doing it this way because there may be code that has to execute before redirecting to that page, rather than just a direct link to that page. I have tried with and without the ActionVerbs line, I have tried this form of the Html Form:
<% using (Html.BeginForm()) { %> ... <%}%>
and still nothing. I have also tried no form and still nothing, but here's something that may affect this...I am using a master page with everything inside a content place holder inside a form that uses runat="server". Would that matter? So its Master Page -> form (masterform runat server) -> ContentPlaceHolder -> form (for postback and action) -> submit button. Any help would be greatly appreciated.
If I am thinking correctly, your form action should be calling the name of the action method:
<form method="post" action="/Home/AddModify">
<input type="submit" value="AddModify">
</form>
The ActionLink would be the same way.
Otherwise you will need to modify your routes to go to that action method by default.
Let's do this in two parts
1 I think your ActionLink should be:
<%=Http.ActionLink("Add / Modify", "AddModify", "Home")
...to force routes.
First parameter: text shown
Second parameter: action name
Third parameter: controller name
2 Change your submit button to: (I assume we're currently looking at your "index" action from your "Home" controller)
<form method="post" action="/Home">
<input type="submit" value="AddModify" name="ModifyBtn" >
</form>
Then in your Home controller:
edit:
//GET
public ActionResult Index()
{
return View();
}
/edit
[ActionVerbs(HttpVerbs.Post)]
public ActionResult Index(FormCollection form, string ModifyBtn, string OtherBtn)
{
if (ModifyBtn!=null)
{
//do stuff
return RedirectToAction("AddModify");
}
if (OtherBtn!=null)
{
//do stuff
return RedirectToAction("OtherAction");
}
return View();
}
edit:
I think you're trying to submit directly to another Action. The best way is to handle the POST method inside your code then redirect to another action. That way, you can use
<% using (Html.BeginForm()) { %>
without trouble.
<form method="post" action="/Home">
Will create a form with a href of /Home, this will only call the AddModify action if the AddModify action is default on that route.

Resources