mvc maintain url parameters during post - asp.net-mvc

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>

Related

Using Custom paths or absolute URI with Html.BeginForm() [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Html.BeginForm() with an absolute URL?
How to put and absolute URI in the action attribute of the <form> tag generated by the Html.BeginForm() MVC3 Html helper. I am using MVC3 validation and do not want to lose it by writing my own Form tags.
I tried out both Html.BeginForm(new { action ="http://absolute.com"}) and Html.BeginForm(new { #action ="http://absolute.com"}) and the rendered html was <form action="/Pro/Contact/http%3a/absolute.com/">. As you can see it is being appended instead of replaced.
BeginForm method with single parameter is used to generate a relative path from routedata that is provided within parameter variable. Which means, that you are setting URI parameter with name action and giving it and value http://absolute.com, therefor value is going to be encoded.
What you want to use is overload where it asks you for htmlAttributes.
This will not encode value for action attribute:
#using (Html.BeginForm(
null, null, FormMethod.Post,
new {#action="http://absolute.com/submit/example"}
)){}
// the result is:
<form action="http://absolute.com/submit/example" method="post">
</form>
UPDATE: method show below will not utilize JavaScript client-side validation.
since, you don't really need the helper to figure out the path of the form. You can use the html and set action of the form manually.
Validation will still work, if you are using helpers for the input fields.
<form action="http://absolute.com/submit/example" method="post">
#Html.LabelFor(model => model.Example)
#Html.ValidationMessageFor(model => model.Example, "*")
#Html.TextAreaFor(model => model.Example, 8, 60, null)
</form>
One way is..we can use RedirectResult("http://absolute.com"); in the controller action method.
If you are redirecting from your controller (or action filter, etc.) you can use the RedirectResult as your ActionResult type.

How can I emulate model binding behaviour when rendering an ActionLink?

In the following code, the get action returns a betting card for a given race date, and the post I use the post action to transform properties of the bound model to route values for the get action.
Essential aspects of the Details View:
#using (Html.BeginForm("Upload", "BettingCard",
FormMethod.Post, new { id = "uploadForm", enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true, "The upload was unsuccessful. The following error(s) occurred: ")
<div id="date-selector">
<div id="ymd">
#Html.LabelFor(model => model.RaceDate)
#Html.DropDownListFor(model => model.RaceDay, Model.YmdLists.Days)
#Html.DropDownListFor(model => model.RaceMonth, Model.YmdLists.Months)
#Html.DropDownListFor(model => model.RaceYear, Model.YmdLists.Years)
<input type="submit" value="Upload for this date" />
</div>
</div>
#Html.Telerik().Upload().Name("UploadedFiles")
}
Essential aspects of the controller code:
[HttpGet]
public ActionResult Details(int year, int month, int day) {
var model = new BettingCardModel
{
ResultMessage = "No betting card was located for the selected date."
};
DateTime passedDate;
if (!DateTimeHelper.TrySetDmy(year, month, day, out passedDate)) {
ModelState.AddModelError("", "One or more values do not represent a valid date.");
return View(model);
}
model.RaceDate = passedDate;
var bettingCard = _bettingCardService.GetByRaceDate(passedDate);
model.MapFromEntity(bettingCard);
return View(model);
}
[HttpPost]
public ActionResult Details(BettingCardModel model)
{
return RedirectToAction("Details", new { year = model.RaceYear, month = model.RaceMonth, day = model.RaceDay });
}
A good deal of the above code is experimental and diagnostic, so I'd like to avoid getting into a review of code that works, and rather concentrate on what I need to achieve. In the Details view I only need one 'command', being 'Display for Date', so I get off easily by using the submit button and the http post takes care of model binding. However, in the Upload view, I need two commands, being 'Display for Date' and 'Upload for Date', so I would like to make the 'Display for Date' operate strictly with the get actions, and only use a post action to submit an uploaded betting card for the date.
My problem is that when I make the 'Display for Date' command use an ActionLink instead of a submit, using model.RaceDay etc. as routing values, the URL parameters passed to Details all still contain their initial values, not values set by the user in the dropdowns. It seems the model binding code (whatever that may be) is not invoked for action links. What could I do here to avoid need a post just to do that binding?
I realise this probably not a direct model binding issue, but I don't know how else to express my question. When elements 'bound' to model properties are rendered, they have a bit more on their side than a simple input, say, and some basic styling, but something is 'built' around that input with lots of metadata. I would like some way to use that metadata to map to a URL when a get link on the page is clicked.
The problem you're having is that all of the model data and metadata is generated on the server dynamically and given to the client as static content. The binding is only aware of a change to the Model once it is submitted to the Server. All of that model metadata is static on the client side, using pure .NET it will have no way to know when a user changes a value in the drop-down to also change that value in a static anchor tag, which is what the ActionLink renders to. The answer is to use javascript. There are many many way to accomplish what you're trying to do through javascript. You could potentially write a custom HtmlHelper class to generate the javascript for you. However if you don't want to use javascript then you will HAVE to do a post to get the data the user selected to the Server.
If you're trying to avoid having to re-write code then you can create a partial view for the contents of the form and embed that in two separate views. Another thing you could try is to detect which button was pushed by having two submit buttons with the same name like so:
<input type="submit" name="command" value="Update" />
<input type="submit" name="command" value="Display" />
Then in your Controller in the [HttpPost] action you can detect which was pushed via the Request.Forms like this:
[HttpPost]
public ActionResult Details(BettingCardModel model)
{
if (Request.Forms["command"].Equals("Display"))
{
return RedirectToAction("Details", new { year = model.RaceYear, month = model.RaceMonth, day = model.RaceDay });
}
// Do your update code here...
return // Whatever it is you return for update.
}
hopefully this helps you.

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.

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