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

10,181

Solution 1

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);

Solution 2

First make currentUserId public then put that in a hidden field in your View.

 @Html.HiddenFor(x => x.currentUserId )

Solution 3

currentUserId is a private member, not a property. And it is not bound to any form field in the view. So when you render the view by passing a model which has a non null value of currentUserId, that value is immediately lost when the view is rendered on the client, because it only exists in the model on the server. So there is no way to get it back.

You can create a CurrentUserId property, and have a hidden field in your view using Html.HiddenFor(), so that the value gets sent to the client and you get it back during POST.

Share:
10,181
ITWorker
Author by

ITWorker

Updated on July 20, 2022

Comments

  • ITWorker
    ITWorker almost 2 years

    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.

  • ITWorker
    ITWorker over 7 years
    Thanks for the explanation.
  • ITWorker
    ITWorker over 7 years
    Thanks for the help.