Previous GET parameters being unintentionally preserved in POST request - asp.net-mvc

As of current I navigate to a view using a GET request, looking something like this:
/batches/install?Id=2&ScheduledDate=07.29%3A12
From there, I send a POST request using a form (where I include what data I wish to include in the request.
Furthermore I set the forms action to "Create" which is the action I wish to send the request to.
My issue is the fact that sending this request keeps the GET arguments in the POST url, making it look the following:
../batches/Create/2?ScheduledDate=07.29%3A12
I do not want this since:
1: it looks weird
2: it sends data I do not intend it to send in this request
3: if my model already has a property named "id" or "scheduledDate" the unintentional GET parameters will get bound to those properties.
How can I ignore the current GET parameters in my new POST request?
I just want to send the form POST data to the url:
../batches/create
(without any GET parameters)
How would this be done?
As requested, here is my POST form:
#using (var f = Html.Bootstrap().Begin(new Form("Create")))
{
//inputs omitted for brevity
#Html.Bootstrap().SubmitButton().Style(ButtonStyle.Success).Text("Create batch")
}
Note that I use the TwitterBootstrapMVC html helpers (https://www.twitterbootstrapmvc.com/), althought this really shouldn't matter.
As requested, to sum up:
I send a get request to : /batches/install?Id=2&ScheduledDate=07.29%3A12.
Through the returned view I send a POST request to: /batches/create.
However the previous get parameters get included in the POST request URL making the POST request query: /batches/Create/2?ScheduledDate=07.29%3A12 (which is NOT intended).

This is not a great Idea though, but will give you what you want.
[HttpPost]
public ActionResult Create() {
//do whatever you want with the values
return RedirectToAction("Create", "batches");
}

This is a "Feature" of MVC that was previously reported as a bug (issue 1346). As pointed out in the post, there are a few different workarounds for it:
Use named routes to ensure that only the route you want will get used to generate the URL (this is often a good practice, though it won't help in this particular scenario)
Specify all route parameters explicitly - even the values that you want to be empty. That is one way to solve this particular problem.
Instead of using Routing to generate the URLs, you can use Razor's ~/ syntax or call Url.Content("~/someurl") to ensure that no extra (or unexpected) processing will happen to the URL you're trying to generate.
For this particular scenario, you could just explicitly declare the parameters with an empty string inside of the Form.
#using (var f = Html.Bootstrap().Begin(new Form("Create").RouteValues(new { Id = "", ScheduledDate = "" })))
{
//inputs omitted for brevity
#Html.Bootstrap().SubmitButton().Style(ButtonStyle.Success).Text("Create batch")
}

Related

MVC Redirect to a different view in another controller

After a user has completed a form in MVC and the post action is underway, I am trying to redirect them back to another view within another model.
Eg. This form is a sub form of a main form and once the user has complete this sub form I want them to go back to the main form.
I thought the following might have done it, but it doesn't recognize the model...
//send back to edit page of the referral
return RedirectToAction("Edit", clientViewRecord.client);
Any suggestions are more that welcome...
You can't do it the way you are doing it. You are trying to pass a complex object in the url, and that just doesn't work. The best way to do this is using route values, but that requires you to build the route values specifically. Because of all this work, and the fact that the route values will be shown on the URL, you probably want this to be as simple a concise as possible. I suggest only passing the ID to the object, which you would then use to look up the object in the target action method.
For instance:
return RedirectToAction("Edit", new {id = clientViewRecord.client.ClientId});
The above assumes you at using standard MVC routing that takes an id parameter. and that client is a complex object and not just the id, in which case you'd just use id = clientViewRecord.client
A redirect is actually just a simple response. It has a status code (302 or 307 typically) and a Location response header that includes the URL you want to redirect to. Once the client receives this response, they will typically, then, request that URL via GET. Importantly, that's a brand new request, and the client will not include any data with it other than things that typically go along for the ride by default, like cookies.
Long and short, you cannot redirect with a "payload". That's just not how HTTP works. If you need the data after the redirect, you must persist it in some way, whether that be in a database or in the user's session.
If your intending to redirect to an action with the model. I could suggest using the tempdata to pass the model to the action method.
TempData["client"] = clientViewRecord.client;
return RedirectToAction("Edit");
public ActionResult Edit ()
{
if (TempData["client"] != null)
{
var client= TempData["client"] as Client ;
//to do...
}
}

Zend Apigility : DELETE HTTP method Validation

I have recently explored Apigility I want to use HTTP DELETE method to delete some entity but before deleting I need to validate "entityId" must be given and must be Digit and trim.
Problem is documentation mentions that:
Content Validation currently only works for POST, PATCH, and PUT requests. If you need to validate query string parameters, you will need to write your own logic for those tasks.
https://apigility.org/documentation/content-validation/intro
I have make some custome modification in config file as bellow:
'NetworkingNightAPI\\V1\\Rpc\\DeleteSlotByLoginUser\\Controller' => [
'DELETE' => 'NetworkingNightAPI\\V1\\Rpc\\AssignTimeSlotToLoginUser\\Validator',
],
As I have mention DELETE method to validate same as NetworkingNightAPI\V1\Rpc\AssignTimeSlotToLoginUser\Validator but the issue is it always return 'Value could not be empty' even I have added valid row JSON values using PostMan
Thanks!
Thank you for your reply
What I have found is Apigility uses 'zf-content-validation' module for validating the input data (https://github.com/zfcampus/zf-content-validation)
This module dose not restrict such HTTP Methods you can apply validation to DELETE method as well Like it says that
"In the above example, the Application\Controller\HelloWorld\Validator service will be selected for PATCH, PUT, or DELETE requests, while the Application\Controller\HelloWorld\CreationValidatorwill be selected for POST requests."
So you just need to add manual entry for DELETE method in config file as below:
'NetworkingNightAPI\\V1\\Rpc\\DeleteSlotByLoginUser\\Controller' => [
'input_filter' => 'NetworkingNightAPI\\V1\\Rpc\\DeleteSlotByLoginUser\\Validator',
'DELETE' => 'NetworkingNightAPI\\V1\\Rpc\\DeleteSlotByLoginUser\\Validator',
],
In addition HTTP DELETE method will not validate using JSON row body from POSTMAN you have to pass query parameters and in your controller you need to get validated data using plugin like below:
$recruiterId = $this->getInputFilter()->getValues()['recruiterId'];
$timeSlotId = $this->getInputFilter()->getValues()['timeSlotId'];
If you want to delete a resource your should use the url that includes the route to that entity. This means the id would be in your route parameters, not in your query parameters. So the id is a route parameter/identifier and the RestController will search your entity using the identifier in the fetch($id) method of your resource listener. The listener should return a not found (404) response in case the entity with that identifier doesn't exist.
The content validation you mention in your question is for validating POST/GET parameters. So there is no need for such validator in case of a delete request.
So say for example you want to delete a Slot you would have a route:
api/v1/slots/[slot_id]
And if you want to delete Slot with id 1 you would send a delete request to:
DELETE
api/v1/slots/1
Your listener should simply return a 404 response in case a Slot with slot_id 1 doesn't exist.
I see you're using RPC Rather than Rest style - if you're passing the parameter using the query string you will have to validate it yourself inside the controller, for example:
public function someActionMethod()
{
$id = $this->getRequest()->getQuery('id');
$validator = new Input('id');
$validator->getValidatorChain()
->attach(new \Zend\Validator\NotEmpty())
;
$validator->getFilterChain()
->attach(new StringToUpper())
;
$inputFilter = new InputFilter();
$inputFilter
->add($validator)
->setData($this->getRequest()->getQuery())
;
if( ! $inputFilter->isValid()) {
return new \ZF\ApiProblem\ApiProblemResponse(
new ApiProblem(400, $inputFilter)
);
}
}
Apigility won't use any of the config generated using the UI to validate those fields for you wuen passed via query string as it says in the docs - they will be ignored. You would need to generate the valaidator yourself.
You could set it up to generate the validation using a config if you wished and then load the validator inside the controller to save writing boiler plate code as above.

Specify HTTP VERB for Url.Action?

I am using AttributeRouting: http://attributerouting.net/
If I have two actions with the same name, but the route for the GET is different than the route for the POST, why is Url.Action generating a URL that matches on my GET action, even though my action method is pointing to the one that is a post?
I have tried passing the verb as an
Url.Action("CreateEdit", new { method = "POST" })
However this returns the string /enrollments/create instead of /enrollments/createedit which corresponds to the POST.
I wouldn't expect this to work but tried it as some route configurations use this technique:
Url.Action("CreateEdit", new { HttpMethodConstraint = new HttpMethodConstraint("POST") })
However both of these don't solve the problem, and the URL is still for my GET route.
I have verified that I can post to a hardcoded URL of /enrollments/createedit so the POST action does indeed have that route, just to rule out the possibility that the POST Action's default [POST] is defaulting to the expected route.
Problem is that I usually avoid hardcoded URL's and use Url.Action or Html.BeginForm (which also exhibits the same issue) so that all URLs are derived from action routes.
Here are how I have defined my actions:
[GET("create")]
[GET("edit/{id:guid?}")]
public ActionResult CreateEdit(Guid? id)
...
[POST] //default to "createedit"
public ActionResult CreateEdit()
...
Note this question is not about making the actual request, but is generating route urls.
How do you indicate to Url.Action that it should use the route from the [POST] instead of the GET. There is no overload that take constraints or HTTP Verb? Note that Html.BeginForm exhibits that same issue when generating the action URL attribute, which is clearly a POST oriented method.
It also does not work if I simplify my action routes to(although the above are my goal routes):
[GET("create")]
public ActionResult CreateEdit()
...
[POST("createedit")] //default to "createedit"
public ActionResult CreateEdit()
...
Url.Action("CreateEdit", new { method = "POST" }) returns "/enrollments/create" obviously because method = 'POST' is not the correct way to specify the desired route for Url.Action. Is there a way to tell Url.Action or Html.BeginForm to specify that the POST route is desired? The method param of BeginForm effects the method="post" html attribute, but the action attribute is still generated with the wrong /enrollments/create URL. I've seen area="Admin" used to specify Areas in route parameters. Is there some magic property that can be added to specify the verb? If there is not, then clearly there is no way to get the right URL consistently and I will probably need to rename one of my actions if I need to maintain the desired routes. My hope was there was some route value I could add analogous to new { area = "SomeArea" }. method = "POST" was just my wild guess at what might work.
The request pipeline respects route constraints, if they are both the same route, then it works fine. So if both were [POST("creatededit")] and [GET("create"] then it would work fine because the URLs for both are the same, and then when the actual request is made the distinction is made by the pipeline due to the HttpMethodContraint.
If I use parameters to make the routes distinct, it works but only if I eliminate the [GET("create")]
If Url.Action has no known/supported/documented way to take a route property then the answer may simply be that Url.Action doesn't have any way for you to tell it the desired VERB.
If someone asks: Is there a way to specify the area for Url.Action?
The answer is simply: Url.Action("SomeAction", new { area ="SomeArea" })
The answer is not: muck around with your routes so you don't have two actions of the same name in different areas(or any number of other things that don't address the question).
The answer is not: You shouldn't have actions of the same name in different areas. (It might be a valid point, but it doesn't create a useful resource for others who are in a situation where they absolutely must generate a URL for a different area).
I don't understand what is so complicated about that.
I'm not asking how to make a POST request. I know how to do that, but I don't intend to hardcode URLs into my views. That turns into a maintenance nightmare.
I could change my routes if my boss wasn't so set on SOE. So I can't eliminate the [GET("create")] route.
I can resolve by renaming an action like so:
[GET("create")]
[GET("edit/{id:guid?}")]
public ActionResult CreateEdit(Guid? id)
...
[POST("createedit")]
public ActionResult CreateEditPost()
...
That is a bit dirty, but works. It still doesn't answer the question.
If there is a way to specify the VERB for Url.Action, I think the answer would be a useful resource. Maybe not to as many people as Url.Action("SomeAction", new { area ="SomeArea" }) but it would be documenting a feature of Url.Action that for me has been elusive. Maybe it doesn't support it. Perhaps I will need to delve into the source code.
I goofed in my original answer. I wasn't thinking it through really and probably answered too quickly. Here's what it boils down to:
Url.Action is tied pretty inherently to the controller/action style routing. When there's two matching actions (overloads where one is a GET version and the other is a POST), the URL should be the same, and so the GET or really first version is returned. See, AttributeRouting just sort of layers on by letting you customize the outward facing URL, but internally, Url.Action is simply trying to find a route that will get you to the requested action. Once it finds a match, it assumes that's good enough, and especially pre-MVC5, it should have been.
MVC5 introduced attribute routing as a first-class citizen, but from what I've seen, this edge case (where the GET and POST versions of the same action have different URLs and you want the POST version in particular) has not been covered.
That said, I see a couple of potential workarounds:
Use different action names. If your POST action is named something like CreateEditPost, then you can very easily do Url.Action("CreateEditPost") and get the right URL. Since your routes are not affected directly by the action name, it doesn't really matter what it's called.
Use named routes. If you name your routes, then you can use Url.RouteUrl instead and request exactly the route your want. For example:
[POST("createedit", RouteName = "CreateEditPost")]
public ActionResult CreateEdit()
{
...
}
Then:
#Url.RouteUrl("CreateEditPost")

Show querystring after POST

I'm using MVC4/Razor. After a GET request the view shows the querystring, and after a POST request the view does not show the querystring - both as expected.
But, I have an action with [AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)], and I need to POST a form to it, and to show the corresponding querystring.
How can I do this on the server side? I know this can be done on the client side by converting to a GET request, and I am curious to see how to do that, but only if that is the only way to make this work.
You can return RedirectToAction with posted parameters
return RedirectToAction("About", "Create",
new { id = PId, Name=PName }); // more params if needed
The parameters will be included in the querystring.

Should a PUT identifier come from the URL or from the HTTP request data?

This question is probably a dupe, but I couldn't find a similar one (not sure exactly what to search for).
Say I have a restful resource URL like this:
/my/items/6/edit
There is a form at this page which allows me to edit my #6 item. When I submit the form, it POSTS to /my/items/6, with a PUT X-HTTP-Method-Override header.
My question is, where should the server handler get the value "6" from? Should it get it from the URL? Or from the HTTP POST data (say the id was rendered as a hidden input field on the form)?
It seems to me like it should come from the URL. However this makes it a little more trouble to get it out. For example in .NET MVC, you might get it like this from a controller action method:
var id = int.Parse(ControllerContext.RouteData.Values["id"].ToString());
...which seems like more trouble than it's worth. However, if we get it out of the HTTP POST data, then technically you could post/put data for my item #6 to /my/items/7, and the server would still save the item data under id #6.
Are there any standard practices here?
Check this: http://www.codeproject.com/Articles/190267/Controllers-and-Routers-in-ASP-NET-MVC-3
I'm not a .net developer, though best practice in all platforms is to map your URI template to controller. Router must parse and prepare such information and pass it to your function/method.
It should definitely come from the URL. In ASP.NET MVC, if you had a route defined like this:
routes.MapRoute(
"Default",
"/my/{controller}/{id}/{action}",
new { controller = "items", action = "Index" }
);
You would be able to reference the ID from the ActionResult method signature, like this:
[HttpPut]
public ActionResult Index(int id, MyItemEditModel model)
{
// id would be 6 if the URL was /my/items/6/
// and the model need not contain the id
...

Resources