Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected model binding

Going through the scaffolded MVC 5 template with Individual Accounts authentication, i've stumbled on a behavior i can't get my head around.

Given a request url

http://localhost:53487/Account/ResetPassword?userId=4&code=T634Hfv%2BxMAlo2XjdLV6a%2Bd1%2BxGsfdiQiKRW0Nh2fB3I1U3S%2BNdXU4ixHC9uJ5F5PSRMZkQgV907CDH0x3aQPSdFliXJqD7nrjk3TLnOTawPeO8CJjk5OEyYijVur1i1Fr7DE7nmaDD93I000fXbQA%3D%3D

and action method in AccountController

[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
    return code == null ? View("Error") : View();
}

and the view ResetPassword.cshtml

@model OPLA.Web.Models.ResetPasswordViewModel
@{
    ViewBag.Title = "Reset password";
}

<h2>@ViewBag.Title.</h2>

@using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Reset your password.</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-danger" })    
    @Html.HiddenFor(model => model.Code)
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Reset" />
        </div>
    </div>
}

and viewmodel ResetPasswordViewModel

public class ResetPasswordViewModel
{
    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; }

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

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

    public string Code { get; set; }
}

When the view is loaded, the line @Html.HiddenFor(model => model.Code) produces this html output with Code property of the viewmodel properly filled/bound:

<input id="Code" name="Code" type="hidden" value="T634Hfv+xMAlo2XjdLV6a+d1+xGsfdiQiKRW0Nh2fB3I1U3S+NdXU4ixHC9uJ5F5PSRMZkQgV907CDH0x3aQPSdFliXJqD7nrjk3TLnOTawPeO8CJjk5OEyYijVur1i1Fr7DE7nmaDD93I000fXbQA==">

How did the model binder know the code query string parameter belongs to the Code property of the viewmodel and bound it automatically?

like image 740
Sang Suantak Avatar asked Apr 12 '26 07:04

Sang Suantak


1 Answers

You have a parameter in your method named code. When the method is executed, the value of code is added to ModelState.

Your model also has a property named Code. Your view uses the @Html.HiddenFor() to generate an <input> for that property. All the HtmlHelper methods that generate form controls (except PasswordFor()) determine thevalue for the <input> by reading values in the following order

  1. ModelState
  2. The ViewDataDictionary
  3. The actual value of the property

Because ModelState contains a value for code (its not case sensitive), the value is set from the method parameter (i.e. the query string value).

For a more detailed explanation of why this behavior is by design, refer the 2nd part of TextBoxFor displaying initial value, not the value updated from code.


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!