ITWorker ITWorker - 3 months ago 35
ASP.NET (C#) Question

ASP.NET MVC Model property is null when POST method is called

In a controller action, I have the following call, where

userId
is a known non-null value:

return View("ChangePassword", new ChangePasswordBindingModel(userId));


Here is the definition of ChangePasswordBindingModel:

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

[Required]
//[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; }

[Required]
[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; }

public ChangePasswordBindingModel()
{

}
public ChangePasswordBindingModel(string userId)
{
this.currentUserId = userId;
}

public string GetUserId()

{
return this.currentUserId;
}
}


Here is the view that is bound to this Model for ChangePassword:

@model IdentityDevelopment.Models.ChangePasswordBindingModel
@{ ViewBag.Title = "ChangePassword";
}

@Html.ValidationSummary(false)
<h2>Change Password</h2>


@using (Html.BeginForm("ChangePassword","Account", FormMethod.Post))
{
@Html.AntiForgeryToken();
<input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" />

<div class="form-group">
<label>Current Password</label>
@Html.PasswordFor(x => x.OldPassword, new { @class = "form-control" })
</div>

<div class="form-group">
<label>New Password</label>
@Html.PasswordFor(x => x.NewPassword, new { @class = "form-control" })
</div>


<div class="form-group">
<label>Re-enter New Password</label>
@Html.PasswordFor(x => x.ConfirmPassword, new { @class = "form-control" })
</div>

<!-- <button class="btn btn-primary" type="submit">Save</button> -->
<input class="btn btn-primary" type="submit" value="Save" />
}


And lastly, here is the Post method for clicking
Save
on the above form:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ChangePassword(ChangePasswordBindingModel loginChange)
{
if (ModelState.IsValid)
{
IdentityResult result = null;
try
{
string userid = loginChange.GetUserId();
result = await UserManager.ChangePasswordAsync(userid, loginChange.OldPassword, loginChange.NewPassword);

}
catch (Exception ex)
{

}
if (result != null && result.Succeeded)
{
return RedirectToAction("Index");
}
else
{
if(result != null)
{
AddErrorsFromResult(result);
}

}
}


return View(loginChange);
}


The problem is that the
userid
does not get set to a non-null value, as I thought
GetUserId()
would do. Why does the
currentUserId
property of the Model that was sent to the post method not contain the value? I confirmed this is so by stepping into the call to
GetUserId()
during debugging.

Thank you.

Answer

currentUserId isn't submitted to the post action method because of two reasons:

  1. currentUserId isn't a public property.
  2. It's not present inside using (Html.BeginForm block in <input>, <textarea> or <select> tags.

Remove currentUserId and create a new public property named CurrentUserId in your model. You also don't need the GetUserId() method. Your model should look like below

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

    [Required]
    //[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; }

    [Required]
    [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; }

    public string CurrentUserId { get; set; }

    public ChangePasswordBindingModel()
    {

    }
    public ChangePasswordBindingModel(string userId)
    {
        this.CurrentUserId = userId;
    }

}

and use a HiddenField to include CurrentUserId in your view

@using (Html.BeginForm("ChangePassword","Account", FormMethod.Post))
{
   @Html.AntiForgeryToken();
    @Html.HiddenFor(x => x.CurrentUserId)
    <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" />

    <div class="form-group">
        <label>Current Password</label>
        @Html.PasswordFor(x => x.OldPassword, new { @class = "form-control" })
    </div>

    <div class="form-group">
        <label>New Password</label>
        @Html.PasswordFor(x => x.NewPassword, new { @class = "form-control" })
    </div>


    <div class="form-group">
        <label>Re-enter New Password</label>
        @Html.PasswordFor(x => x.ConfirmPassword, new { @class = "form-control" })
    </div>

    <!-- <button class="btn btn-primary" type="submit">Save</button> -->
    <input class="btn btn-primary" type="submit" value="Save" />
}

In your post action method, you can get the value of CurrentUserId property as below

result = await UserManager.ChangePasswordAsync(loginChange.CurrentUserId, loginChange.OldPassword, loginChange.NewPassword);