4

I can't get my form to call the POST action on my controller.

My page code

@model My.Models.ManageUserViewModel

@using (Html.BeginForm("Manage", "Account", FormMethod.Post, new { @id = "manageAccountFormHolder", @class = "form-horizontal col-sm-8 col-sm-offset-2 v-offset-4" }))
    {
        @Html.AntiForgeryToken()
        <h4 class="text-center">Change Password</h4>

        <div class="form-group">
            @Html.LabelFor(m => m.OldPassword, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-9">
                @Html.PasswordFor(m => m.OldPassword, new { @class = "form-control" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(m => m.NewPassword, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-9">
                @Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-9">
                @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
            </div>
        </div>

        <h4 class="text-center v-offset-4">Edit Personal Details</h4>

        <div class="form-group">
            @Html.LabelFor(m => m.FirstName, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-4">
                @Html.TextBoxFor(m => m.FirstName, new { @class = "form-control", @placeholder = "First name" })
            </div>
            <div class="col-sm-5">
                @Html.TextBoxFor(m => m.LastName, new { @class = "form-control", @placeholder = "Last name" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(m => m.Mobile, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-9">
                <input class="form-control" id="manageMobile" name="manageMobile" type="tel">
                @Html.TextBoxFor(m => m.Mobile, new { @class = "form-control", @placeholder = "First name" })
                <p class="help-block">For authorisation code. We will never share or display your mobile number.</p>
            </div>
        </div>

        <h4 class="text-center v-offset-4">Edit Address</h4>

        <div class="form-group">
            @Html.LabelFor(m => m.Address1, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-2">
                @Html.TextBoxFor(m => m.Address1, new { @class = "form-control"})
            </div>
            <div class="col-sm-7">
                @Html.TextBoxFor(m => m.Address2, new { @class = "form-control" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(m => m.TownSuburb, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-9">
                @Html.TextBoxFor(m => m.TownSuburb, new { @class = "form-control" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(m => m.StateRegion, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-9">
                @Html.TextBoxFor(m => m.StateRegion, new { @class = "form-control" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(m => m.PostCode, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-9">
                @Html.TextBoxFor(m => m.PostCode, new { @class = "form-control" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(m => m.Country, new { @class = "col-sm-3 control-label" })
            <div class="col-sm-9">
                @Html.TextBoxFor(m => m.Country, new { @class = "form-control" })
                @Html.HiddenFor(m => m.CountryIso, new { @class = "form-control" })
            </div>
        </div>

        <div class="text-center">
            <button class="btn btn-default" type="submit">Save Changes</button>
        </div>
    }

My Model

public class ManageUserViewModel
{
    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Current password")]
    public string OldPassword { get; set; }

    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm New Password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

    [Required]
    [Display(Name = "Full Name")]
    public string FirstName { get; set; }

    [Required]
    public string LastName { get; set; }

    [Required]
    [Display(Name = "Mobile Number")]
    public string Mobile { get; set; }

    [Display(Name = "Street Address")]
    public string Address1 { get; set; }

    public string Address2 { get; set; }

    [Display(Name = "City")]
    public string TownSuburb { get; set; }

    [Display(Name = "State")]
    public string StateRegion { get; set; }

    [Display(Name = "Postcode")]
    public string PostCode { get; set; }

    [Display(Name = "Country")]
    public string Country { get; set; }

    public string CountryIso { get; set; }
}

My Controller actions

[HttpGet]
public ActionResult Manage(ManageMessageId? message)
{
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Manage(ManageUserViewModel model)
{
    //Save changes code here
    //Redirect
}

It won't validate either. If I leave one of the required fields blank, no validation error shows.

In Fiddler, I see that the form is posting to the server, but the GET method is still called? See Fiddler raw below, this still calls the GET method (I updated my model to only one property to make it simpler).

POST https://localhost:44301/Account/Manage HTTP/1.1
Host: localhost:44301
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:33.0) Gecko/20100101 Firefox/33.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://localhost:44301/account/manage
Cookie: __RequestVerificationToken=xxxx..........
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 20

OldPassword=dfgdfgdg
garethb
  • 3,951
  • 6
  • 32
  • 52

3 Answers3

9

It's fixed! If anyone can tell me why because I have NFI.......

Changing this line

@using (Html.BeginForm("Manage", "Account", FormMethod.Post, new { @id = "manageAccountFormHolder", @class = "form-horizontal col-sm-8 col-sm-offset-2 v-offset-4" }))

to

@using (Html.BeginForm())

and I'm posting again. Yey, coding genius, 4 hours to post a form! Anyone want to hire me for a job?

EDIT:

So after further digging, the issue was that we had implemented lower case routes. Since the route was being rewritten from /Account/Manage to /account/manage, the post action is never found (finds the get action fine though). Html.BeginForm() was rendering the action in lowercase, whereas Html.BeginForm("Manage", "Account", FormMethod.Post, new{}) wasn't since I specified camel case for the action and controller.

garethb
  • 3,951
  • 6
  • 32
  • 52
  • Weird... I can understand your frustration, we have to see such days in programming sometimes. May be someone else would through some light on what was the real issue? – SBirthare Mar 31 '15 at 06:17
  • In your original question, what was the indication that the POST wasn't working. I.e. did you receive a blank page? Did you have a break-point in `ActionResult Manage(ManageUserViewModel model)` that wasn't hit? – Shaun Luttin Mar 31 '15 at 14:17
  • @ShaunLuttin - I had a break point in both the post and get actions. Only get was hit. Fiddler told me the form was posting from the browser. – garethb Mar 31 '15 at 23:05
  • when you explicitly give action name and controller name then form posts to that particular controller and action. When you omit action name and controller name then form posts to the URL. This is the only difference i am seeing here. I guess your controller name might have created problem. – Manpreet Singh Dhillon Jun 12 '18 at 19:17
  • 1
    Correct on the lowercase issue. See the answer here for a solution on how to only convert GET urls to lowercase, which resolves the issue: https://stackoverflow.com/questions/878578/how-can-i-have-lowercase-routes-in-asp-net-mvc – Savage Apr 30 '20 at 11:52
2

Change your button to input

<input class="btn btn-default" type="submit" value="Save Changes" />
Mairaj Ahmad
  • 14,434
  • 2
  • 26
  • 40
0

The two different BeginForm methods you used are:

BeginForm(HtmlHelper, String, String, FormMethod, IDictionary), which means that...

@using (Html.BeginForm("Manage", "Account", FormMethod.Post, new {}))
{
    // Produces the following form element
    // <form action="/Account/Manage" action="post">
}

BeginForm(HtmlHelper)

Assuming that your /Original Controller/ was named AccountController and your /Original Action/ was named Manage, then...

@using (Html.BeginForm())
{
    // Produces the following form element
    // <form action="/Account/Manage" action="post">
}

So, it should work, really. Here is a Fiddle that I created to try to figure out the discrepancy.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • I would have thought the form actions would be the other way around in your fiddle? The second form action has the index action where I would have thought the first should. My form posts now using BeginForm(HtmlHelper), however as soon as I use BeginForm(HtmlHelper, String, String, FormMethod, IDictionary) without changing anything else, get is called. I might dig deeper into the issue when I have time! – garethb Mar 31 '15 at 23:16