Ambiguous action methods with different HttpMethod - c#

I'm experiencing a weird behaviour, at least to me. I written two methods within a controller with apparently different signatures:
[Route("~/Wallets/{walletId}/Transactions/Add")]
public async Task<ActionResult> Add(long walletId)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Add(AddTransactionViewModel model)
The thing is every time I try to call the POST method using Ajax.BeginForm the GET method (the first) gets called.
#using (Ajax.BeginForm("Add", "Transactions",
new AjaxOptions() { HttpMethod = "POST" })
{
...
}
Now, why this is happening? Of course if I change the name of the GET method say to AddTransaction the code works, but I want to understand why it doesn't as it is.

This is because BeginForm uses GetVirtualPath internally to get the url from the route table. The first link is added to the route table in your example.
Simply editing the POST method with the following should do the trick:
[HttpPost]
[ValidateAntiForgeryToken]
[Route("Add")]
public async Task<ActionResult> Add(AddTransactionViewModel model)

Related

Action Routing: Application gets confused between the GET and POST methods

Context:
The view constructed with the GET method takes at this moment two parameters: id and date. In this view there is a form which sends information back to the Controller thru the POST method using the parameter id
GET Method
[HttpGet]
public async Task<IActionResult> DetailsAdmin(int? id, [ModelBinder(typeof(PModelBinder))]DateTime? date)
{
{...}
}
POST Method
[HttpPost, ActionName("DetailsAdmin")]
[ValidateAntiForgeryToken]
[Route("HechosLiquidadors/DetailsAdmin/{id}")]
public async Task<IActionResult> DetailsAdmin(int? id)
{
{...}
}
The problem:
When the form inside the View sends the information to the Controller, it goes to the GET Action instead of the POST action.
The form:
<form id="#(String.Format("{0}{1}","form",Model[i].HechosID))"
asp-action="DetailsAdmin" method="post" asp-route-id="#Model[i].HechosID" ></form>
I've tried using a Custom Routing to the POST Action method but no luck. How can I correct this so the form points to the POST Action correctly?
In both action methods all parameters as optional. therefore Mvc cannot select "Best candidate" method for executing.
change first action as below and check id not 0:
[HttpGet]
public async Task<IActionResult> DetailsAdmin(int id = 0, [ModelBinder(typeof(PModelBinder))]DateTime? date)
{
if (id != 0)
{
}
{...}
}

Razor form not passing back to controller method

I am trying to pass a textbox's text back to a method in my controller, but the data is not being passed in the parameter
I am genuinely confused, i'm following another example but i'm getting a different result, ie - my method behaving as if no parameter is passed
Code
public ActionResult Index(string searchString)
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
var listOfAnimals = db.Animals.ToList();
if (!String.IsNullOrEmpty(searchString))
{
listOfAnimals = listOfAnimals.Where(a => a.AnimalName.ToLower().Contains(searchString.ToLower())).ToList();
}
return View(listOfAnimals);
}
and here is my razor form from my view page
#using(Html.BeginForm("Index", "Home"))
{
#Html.TextBox("searchString")
<input type="submit" id="Index" value="Index" />
}
Can anybody spot why this isn't working?
If more code is needed, please let me know but i think the issue is isolated to here
You code is correct.
Since you didn't add [HttpGet] or [HttpPost] before index method.
This method was called twice.
The first call ran when producing the page with form via url http://server/Home/Index. This call was an http get and searchString mapped from URL was null, which is correct.
The second call ran when you clicked submit button. Correct value would be mapped by MVC correctly.
You need to have 2 Index actions (two methods), one without decorations (GET verb) and another one decorated with HttpPost (POST verb). Basically, when you go to the index page, the GET action is executed. When you submit the form, a POST request is executed and the Index decorated with HttpPost is executed.
// GET
public ActionResult Index() { ... }
// POST
[HttpPost]
public ActionResult Index(string searchString) { ... }
Francisco Goldenstein wrote the recommended way. It means you can have two Index() actions:
// for GET
public ActionResult Index() { ... }
// for POST
[HttpPost]
public ActionResult Index(string searchString) { ... }
However it is possible to have one Index() method for handling both (GET and POST) requests:
public ActionResult Index(string searchString = "")
{
if(!string.IsNullOrEmpty(searchString)
{ /* apply filter rule here */ }
}
You wrote, your code is not working. Do you mean, your action method is not requested after click on the button? Consider to allow empty value Index(string searchString = "")
If your action method is fired but variable is empty, check the name on the View() side. Textbox must not be disabled, of course.

Posting a Partial View Model to the Same MVC Page

From the bare-bones MVC template, I have modified Manage.cshtml to include a partial, the _AccountSettingsPartial:
<section id="accountSettings">
#Html.Partial("_AccountSettingsPartial")
</section>
#if (ViewBag.HasLocalPassword)
{
#Html.Partial("_ChangePasswordPartial")
}
Manage.cshtml does not use #model anywhere, and it is loaded in AccountController.cs, in public ActionResult Manage(ManageMessageId? message).
_AccountSettingsPartial uses #model crapplication.Models.ManagePublicSettingsViewModel and _ChangePasswordPartial uses #model crapplication.Models.ManageUserViewModel.
Originally, AccountController.cs had just a post method for ManageUserViewModel:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Manage(ManageUserViewModel model)
I have tried to overload it for the other partial:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Manage(ManagePublicSettingsViewModel model)
But this does not work, and produces the following error on a post:
System.Reflection.AmbiguousMatchException: The current request for action 'Manage' on controller type 'AccountController' is ambiguous between the following action methods:
System.Threading.Tasks.Task`1[System.Web.Mvc.ActionResult] Manage(crapplication.Models.ManagePublicSettingsViewModel) on type crapplication.Controllers.AccountController
System.Threading.Tasks.Task`1[System.Web.Mvc.ActionResult] Manage(crapplication.Models.ManageUserViewModel) on type crapplication.Controllers.AccountController
How can I make my _AccountSettingsPartial page post back data from the user account management portal?
You need to have separate forms in your partials, in order to post to different actions. (Or you could change the action of a single form with JQuery, etc., but this will be easier.)
#using (Html.BeginForm("ManagePublicSettings", "Account", FormMethod.Post))
...
#using (Html.BeginForm("ManageUser", "Account", FormMethod.Post))
You then need to uniquely name the actions.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ManagePublicSettings(ManagePublicSettingsViewModel model)
...
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ManageUser(ManageUserViewModel model)

How to withdraw code from an action method into helper in Asp.Net MVC 3?

I'm writing my app using Asp.Net MVC 3. In my controller I have two action methods with the very same code apart from one line. Here it is:
[HttpPost]
public ActionResult EditPost(Post post)
{
if (ModelState.IsValid)
{
_postsRepository.UpdatePost(post);
return RedirectToAction("NewsFeed");
}
return View("EditPost", post);
}
[HttpPost]
public ActionResult AddPost(Post post)
{
if (ModelState.IsValid)
{
_postsRepository.UpdatePost(post);
return RedirectToAction("NewsFeed");
}
return View("AddPost", post); // the return view is different
}
So, I want to withdraw all this code into helper method.
What I've already tried:
1) I tried to put all the code into helper method and pass as parameters ModelState.IsValid and View name. And then in AddPost and EditPost I call this helper method instead of code listed above. Here is the new code:
[HttpPost] // also tried without this attribute
public ActionResult HelperPost(Post post, string viewName, bool modelState)
{
if (modelState)
{
_postsRepository.UpdatePost(post);
return RedirectToAction("NewsFeed");
}
return View(viewName, post);
}
[HttpPost] // also tried without this attribute
public void AddPost(Post post)
{
HelperPost(post, "AddPost", ModelState.IsValid);
}
The EditPost code is almost the same. The view name is "EditPost".
When I run the app and AddPost method executes the validation works and the new post is created but this line never executes:
return RedirectToAction("NewsFeed");
So I'm redirected to "AddPost" view again and again.
2) Also tried to redirect to HelperPost method instead of calling it withing AddPost and EditPost. The result is still the same: seems like RedirectToAction("NewsFeed") doesn't execute. (Here I neglected the validation just to simplify the example, cause I would have to create new model with properties: Post post, string viewName, bool modelState). The code:
[HttpPost] // tried without attribute
public void AddPost(Post post)
{
return RedirectToAction("HelperPost", post);
}
[HttpPost] // tried without attribute
public RedirectToRouteResult HelperUpdatePost(Post post)
{
_postsRepository.UpdatePost(post);
return RedirectToAction("NewsFeed");
}
So, How could I refactor my code so my action methods (EditPost and AddPost) would not contain the same chunk of code?
p.s. I need different views for AddPost and EditPost methods cause the "back to content" links in them are different. So, I can't just redirect to the EditPost view from AddPost method.
Thanks for help in advance!
Just put your "back to content" link in the model, then use the same view for both, then you can use the same HttpPost method. Saves having to duplicate everything.
I would solve it like this:
I would withdraw the method implementation into separate private
method. This method will be invoked by each of the public action
methods. Since the View name differs for both methods I would pass
the view name as parameter to the private method.
The private method doesn't need the HttpPostAttribute!
Don't forget to declare Add and Edit action methods as returning
ActionResult! As parameter they will expect only Post, the view name has to be hard-coded into the action methodsiteslf ;-)
I hope this helps.

How to differ between a parameterless and a string-parameter mvc action method?

Today I found it hard to discover the difference between two MVC action methods.
My arearegistration:
public override void RegisterArea(AreaRegistrationContext context)
{
// My test route.
context.MapRoute(
"testRoute",
"Test/{action}",
new { controller = "Test", action = "Index" }
);
}
And the two methods, that differ from both the used http-method and the parameter.
[HttpPost]
public ActionResult Test(TestModel model)
{
return View("Confirm", model);
}
[HttpGet]
public ActionResult Test(string title)
{
Response.Write(title);
Response.End();
return null;
}
Unregarded the http method, it will always end up rendering the second Test() method. Even when no title parameter is supplied (normally by querystring /Test/Test/?title=test). Probably because string is a reference type and can be null.
But how to overcome this problem? How to make a difference between these methods?
Thanks in advance.
I follow this signature, basically always use the 'GET' method signature with the model as last parameter.
[HttpPost]
public ActionResult Test(string title, TestModel model)
By the way, I've never seen the behavior you've mentioned. So I doubt whether this is a MVC problem rather than something in your configuration. [HttpGet] methods never fire on a POST method. Is the method really post (check the Request property of your ControllerContext).

Categories