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.
Related
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>
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.
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.
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.
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.