HttpPost vs HttpGet attributes in MVC: Why use HttpPost? - asp.net-mvc

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.

Related

mvc maintain url parameters during post

I currently have a form setup on the following url:
http://localhost/mySite/inventory/create/26/1
this goes to an actionMethod
[HttpGet]
public ActionResult create(int exId, int secId)
[HttpPost]
public ActionResult create(MyModel model, int exId, int secId, FormCollection form)
the form submit and button look like:
#using (Html.BeginForm("create", "inventory", new { #exId= Model.ExId, #secId= Model.SecId}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
and
<div class="col-md-6">
<input type="submit" class="btn blue" value="Search" name="Searchbtn" id="Searchbtn" />
</div>
my question was is there anyway during the post to keep the url as: /create/26/1, right now when the post occurs the url is changed to:
http://localhost/mySite/inventory/create?exId=26&secId=1
is there anyway to keep it as how the get has it, /create/26/1?
That's most likely a routing issue. MVC short-circuits routing. In other words, as soon as it finds something that will work, it uses that, even if there is perhaps a better route available. In your scenario here, it's finding that /inventory/create is a valid route, and since query string params are arbitrary, it's just sticking the rest of the route values there. It's hard to say without seeing your route config, but if the route you have for /inventory/create/{exId}/{secId} comes after whatever route catches just /inventory/create, you should move it before that. If you're using attribute routing, there's no inherent order or routes, so you'll have to use a route name to distinguish which one you actually want to use.
All that said, the easiest thing to do here is to simply not generate the URL. You're doing a postback, so you can just use an empty action. I think you're mostly having this issue because you're trying to pass htmlAttributes to Html.BeginForm, which then requires that you specify a bunch of extra stuff that's not necessary. In these situations I recommend just using a static <form> tag. You don't have to use Html.BeginForm and when you find yourself doing contortions like this, it's better to just not.
<form class="form-horizontal" role="form" action="" method="post">
...
</form>

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.

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.

Why is my form triggering the wrong controller action?

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.

Generating URL through routing, using ID from a form field (ASP.NET MVC)

Say I want to display user details like so:
http://www.mysite.com/user/1
I set a route up like so:
routes.MapRoute("UserDetails", "user/"{id}",
new { controller = "User", action = "Details" });
Then my controller:
public ActionResult Details(int id)
{
User currentUser = _userRepository.GetUser(id);
if (currentUser == null) return View("NotFound");
return View(currentUser);
}
So far so good. Everything works like I expect. Now I also want a form where one can enter the ID to look up then click Submit to get the same result. eg:
<% Html.BeginForm("Details","User",FormMethod.Post); %>
<input type="text" value="" name="id" id="userid" />
<%= Html.Button("Search For User","submit","searchforuser"; %>
<% Html.EndForm(); %>
This is where I'm currently lost. I don't want to just have [AcceptVerbs(HttpVerbs.Post)] and use a RedirectToAction if possible. I just want to take whatever number they input - say 38 - and go to www.mysite.com/user/38
Is this even possible with straight MVC? I'm sure there are jQuery-related ways but so far have had no luck getting anything beyond a basic jQuery alert working so don't really want to waste any more time on it for now.
MVC will automatically match by name query parameters (for GET) or form input files (for POST) to the action parameters by name. Unfortunately, your id does not match the action parameter name, so MVC can't match them. To fix this, you can:
change the id on the <input type="text" value="" name="id" id="userid" /> from userid to id
change the parameter name of the Details action from id to userid (and don't forget to update your route as well)
I just confirmed that at least the first one fixes the issue on MVC2 RC. I don't have MVC1, so I can't check if it works there as well, but as far as I know there are no major differences in how MVC1 and MVC2 match parameters.

Resources