0

I'm trying to setup a simple form submission in MVC5, but I'm finding that my method doesn't get fired unless I have both an ActionLink and submit button.

I have a simple model:

public class LoginModel
{
    public string username { get; set; }
}

Then I have two methods in my controller, one for when a form submission is available and one when not:

[HttpGet]
public ActionResult Login()
{
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel myModel)
{ 
    var username = myModel.username;

    // do something with username

    return View();
}

Finally, my View creates a POSTing form:

@using (Html.BeginForm("Login", "Home", FormMethod.Post))
{
    @Html.AntiForgeryToken()
    @Html.TextBox("username", string.Empty)

    @Html.ActionLink("Enter", "Login")
    <input type="submit" name="submit" value="Enter" />
}

I don't really care whether I use an ActionLink or whether I have a submit button (MSDN implies it should be the latter), but if I have only one of them, my [HttpPost] method is not called, and my page is redirected with the username in the query string:

/Home/Login?ReturnUrl=%2F%3Fusername%3DmyUsernameHere

If I have both on the page, I can click the ActionLink and I see that the appropriate method is called with myModel.username containing the value I provided. The submit button, however, will still redirect.

I want this form method to be POST, not GET (which it is in the generated HTML), and for failures to not contain the key as a GET param. What's the problem with having only one of these form submission mechanisms? Why do they not trigger the POST as expected? Is there something more I need to do to 'register' my model with the view, even though it is submitted properly in my workaround scenario?

Edit -- My configured routes are typically as follows:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
    name: "Default",
    url: "{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.RouteExistingFiles = true;

So normally, I'm going to /Login instead of /Home/Login.

Also, I do have some authentication setup, with my HomeController decorated with [Authorize] and my Login methods both with [AllowAnonymous]. When I remove all annotations, I still find that my [HttpPost] is not called, and username shows up as a GET parameter instead of being POSTed.

user655321
  • 1,572
  • 2
  • 16
  • 33
  • Does it work with only submit button without action link? Usually it's written that way. – abatishchev Nov 12 '14 at 21:42
  • No. A `submit`-only form redirects, just as does an `ActionLink`-only form. – user655321 Nov 12 '14 at 21:54
  • You should only have the `submit` button. The fact you have `..?ReturnUrl=%2F%3Fusername%3DmyUsernameHere` (nothing in your code is adding a route value for ReturnUrl) suggest some conflict with the methods in the default `AccountController` –  Nov 12 '14 at 22:24
  • The route handler is not recognizing your route configuration. Change the Login post method to accept a string username or make sure your textbox binds to the LoginModel. Ie @Html.TextBoxFor(m => m.username) – IsakBosman Nov 12 '14 at 22:30
  • @Bosman: I tried both your suggestions -- having my method accept `string username` and alternately adding `@model LoginModel` to my view and changing my `Html.TextBox(...)` to `Html.TextBoxFor(model => model.username)`. In both cases, I was still redirected – user655321 Nov 13 '14 at 01:19
  • @user655321 that is quite strange. Please update your question with the full controller class (are you using any authentication?) and your routes declaration in the global.asax file. Then I can try and help further – IsakBosman Nov 13 '14 at 15:09

1 Answers1

0

I believe that the application doesn't understand that you're trying to make a model with just username. So, you are sending a string username and attempting to place it into a model. I'm not entirely sure about that. Could you try binding your form to your model? Here's an example:

View:

@model YourApplication.Models.LoginModel

@using (Html.BeginForm("Login", "Home"))
{
    @Html.AntiForgeryToken()
    @Html.TextBoxFor(model => model.username, string.Empty)

    <input type="submit" value="Enter" />
}

Controller:

[HttpGet]
public ActionResult Login()
{
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login([Bind(Include="username")] LoginModel myModel)
{ 
    var username = myModel.username;

    // do something with username

    return View("Congrats");
}

Here's an alternate option. If you wanted to do something else, maybe try accepting the string "username" and creating your model after? Example:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string username)
{ 
    LoginModel myModel = new LoginModel();
    myModel.username = username;

    // do something with username

    return View("Congrats");
}

Either way, you should be using the submit button.

Brittany
  • 96
  • 1
  • 9
  • Based on @Bosman's comment, I'm now doing basically this. I hadn't added the `[Bind...]` until now, but that still fails to post to my method. I'm only using the model because it seems that's the 'proper' way to submit a form. I'm fine with accepting `string username` instead of `LoginModel myModel` (though I'd rather follow convention). Either way, neither approach works with just a submit button. – user655321 Nov 13 '14 at 01:48
  • @user655321 I tested out your work and it appears to be working as expected with just the submit button. Where is the ReturnUrl parameter coming from? Also, do you have your routes registered (Setup in your RouteConfig and called in Application_Start)? – Brittany Nov 13 '14 at 17:45
  • I updated my post to include info. I am usually configured for defaulting my routes for `{action}` instead of `{controller}/{action}`. I just changed my routes back to have **both** the {controller} and {action}, and when I do that, it now fires my POST method when using a submit button (and no ActionLink). When I change it back to {action} only, it no longer fires. So it's an issue with the routes, but I can't explain why – user655321 Nov 13 '14 at 19:22
  • @user655321 This might be a work around, but can you try decorating your POST with [Route("~/Home/Login")], or removing the parameters from `Html.BeginForm("Login", "Home")` to look like `Html.BeginForm()`. I believe that there might be a miss communication because of the form parameters and how you have your routes specified. – Brittany Nov 13 '14 at 19:53
  • I tried each independently and together while still leaving my default route as `{action}/{id}` (and, alternately, as `{action}`). In all cases, it fails to hit my POST method and is still doing a redirect. My view is properly generating HTML as `
    ` when that's my route config. When I change the route to be `{controller}/{action}`, it's correspondingly generating a `
    `, which works.
    – user655321 Nov 13 '14 at 20:16
  • It looks like I'm having the same symptoms as in [this question](http://stackoverflow.com/questions/6260394/mvc-httppost-attribute-not-working); however, when I compare the HTML between `{account}` and `{controller}/{account}` routes (non-functional and functional, respectively), the _only_ differences are [1] the form action changes appropriately, [2] the __RequestVerificationToken value changes, and [3] the Visual Studio Browser Link requestId changes. So the cause of the problem doesn't seem the same as in that question. – user655321 Nov 13 '14 at 20:30
  • @user655321 I don't think your problem is coming from the same place as the other thread. It sounds like your form is set up and renders correctly. I'm wondering if something isn't routed correctly regarding your post route. Is the redirect still showing '/Home/Login?returnUrl...' when you're configured to hit '/Login'? Do you have the [Postman extension for Chrome](https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en)? Can you ensure that you're hitting the Login POST route at '/Login' instead of '/Home/Login'? – Brittany Nov 13 '14 at 20:51
  • With routes configured for `{action}`, the HTML is generated for `/Login` and the redirect is to `/Login?ReturnUrl=...`. With a route for `{controller}/{action}`, the HTML and redirect include the controller name as appropriate. With it configured as I want (`{action}`), Postman _does_ properly trigger my POST method when I hit `http://localhost:28171/Login`. – user655321 Nov 13 '14 at 21:29
  • @user655321 Based on a few [questions](http://stackoverflow.com/questions/16305962/what-initially-sets-the-returnurl-parameter-when-using-authorizeattribute) I found, I now think that your `[Authorize]` decorations are causing the redirects. Looks like your user isn't authenticated, causing the redirect with the ReturnUrl parameter getting appended to the '/Login' redirect. Maybe something isn't matching up right with `[AllowAnonymous]` decoration, or maybe there's something in your web.config that's rerouting before it hits the method. Sorry, I don't know how the authentication works exactly. – Brittany Nov 13 '14 at 22:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/64906/discussion-between-brittany-mazza-and-user655321). – Brittany Nov 13 '14 at 23:39