Nick Spicer
Nick Spicer

Reputation: 2707

Model not populating on POST

I created a simple class to test this directly, on the Edit POST action I was under the impression if a property is not in the form it will use the value which exists in the database already, instead it is getting set to null and overwriting any values.

This is from the scaffold generation, lets say I comment out Password and Password Confirmation because I don't want them to edit it, so they will get left out of the POST values.

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>User</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email)
                @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.FirstName, new { @class = "control-label col-md-2" })
           <div class="col-md-10">
                @Html.EditorFor(model => model.FirstName)
                @Html.ValidationMessageFor(model => model.FirstName)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </div>
        </div>

        @*<div class="form-group">
            @Html.LabelFor(model => model.Password, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Password)
                @Html.ValidationMessageFor(model => model.Password)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.PasswordConfirmation, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.PasswordConfirmation)
                @Html.ValidationMessageFor(model => model.PasswordConfirmation)
            </div>
        </div>*@

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

So the request would look like this:

__RequestVerificationToken=wcxt2SbPmLuI-FzGh7b0okDgfFIXacVKnvvuYpVkgJjpNEAgCbMzHTeMQv61xhbxch0kId6nh6mK-qoKML3CHpLOfk1SawIQIpdtVicWkys1&ID=1&Email=foo.bar%40test.com&FirstName=foo&LastName=bar

And here I simply want to save the values that were edited and any values which were not included use the original values instead.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Email,FirstName,LastName,Password,PasswordConfirmation")] User user)
{
    if (ModelState.IsValid)
    {
        db.Entry(user).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(user);
}

This is just an example to help me understand how to object gets populated when it's not in the form, if anyone could elaborate or point to resources which clearly show how the model is populated when the values aren't explicitly in the POST request that would be awesome.

Upvotes: 0

Views: 259

Answers (1)

Shyju
Shyju

Reputation: 218812

I was under the impression if a property is not in the form it will use the value which exists in the database already

That is not true. How does the form (just some HTML in the client browser) knows what value you have in the database ?

You have 2 solutions

1) In your HttpGet method, Send all the properties of your entity to the form, but don't display those to user. Keep in hidden fields inside the form and when you post the form, It will be available.

public ActionResult Edit(int id)
{
  var vm = new EditUserVM();
  var user = GetUserFromYourDb(id);
  vm.Name = user.Name
  vm.Email= user.Email

  //The below property you don't want user to edit in form. But still sending.
  vm.Token= user.Token

  return View(vm);
}

in your view

@model EditUserVM
@using(Html.Beginform())
{
  @Html.TextBoxfor(s=>s.Name)
  @Html.TextBoxfor(s=>s.Email)
  <!-- Hidden field here -->
  @Html.HiddenFor(s=>s.Token)
  @Html.HiddenFor(s=>s.UserId)
  <input type="submit" />
} 

2) If you do not wish to send all the fields to form, You may try this.(I recommend this). Have only fields which you want the user to edit in your GET form and In the HttpPost action method, Read the existing entity again from the database and update the fields with the values you received from the form. Your other field values stays same.

public ActionResult Edit(int id)
{
  var vm = new EditUserVM();
  var user = GetUserFromYourDb(id);
  vm.Name = user.Name
  vm.Email= user.Email

  return View(vm);
}

in your view

@model EditUserVM
@using(Html.Beginform())
{
  @Html.TextBoxfor(s=>s.Name)
  @Html.TextBoxfor(s=>s.Email)
  @Html.HiddenFor(s=>s.UserId)
  <input type="submit" />
} 

And in your HttpPost

[HttpPost]
public ActionResult Edit(EditUserVM model)
{
   var existingUser = GetUserFromYourDb(model.UserId);

   existingUser.Name = model.Name;
   existingUser.Email = model.Email;
   SaveUser(existingUser);
   return RedirectToAction("UserSaved");
}

Upvotes: 5

Related Questions