user1011627
user1011627

Reputation: 1831

Html.DropDownListFor binding for complex child object

I have the following code (ASP.NET MVC/C#)...simplifying some for brevity.

public class UserViewModel
{
   public int UserId { get; set; }
   public string Name { get; set; }
   public AddressViewModel Address { get; set; }

   public IEnumerable<SelectListItem> CitySelectList { get; set; }
}

public class AddressViewModel
{
   public int AddressId { get; set; }
   public string StreetAddress { get; set; }
   public int CityId { get; set; }
}

In the razor view I have the following:

@Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList)

The right city is selected in the dropdown at load time (ie...the original value) so that works fine, but when I submit the form, the CityId property is not updated with the newly selected value....it still contains the original city's id.

I added a new property to the UserViewModel called "SelectedCityId" and bound that to the dropdownlist like this to test the complex nested object being the problem:

@Html.DropDownListFor(x => x.SelectedCityId, Model.CitySelectList)

When I submit the form using this method, the SelectedCityId does contain the right id so I can see model binding working properly when the object is a simple object, but not when it's a nested child object.

Just for troubleshooting, I checked the postal code field as it also binds to a child property of the Address class, but it does bind properly when using the similar TextBoxFor helper:

@Html.TextBoxFor(x => x.Address.PostalCode)

So, my question is why does model binding not seem to work with the DropDownListFor helper when the expression ties the control to a nested child property? I'm sure its something trivial that I am missing, but I'm not seeing what it is and haven't found a similar answer here.

EDIT: Here is the entire html form from the cshtml file

@using (Html.BeginForm("MyProfile", "Account", FormMethod.Post, new { id = "myProfileForm" }))
        {
            @Html.HiddenFor(x => x.UserId)
            @Html.HiddenFor(x => x.AddressId)
            @Html.HiddenFor(x => x.Address.CityId)

            <article class="mysettings">
                <h1>Personal details</h1>
                <table>
                    <tr>


                <th>E-mail:</th>
                    <td>@Model.Username
                        <!--edit fields-->
                        <div class="edit_field" id="field3">
                            <label for="new_email">New Email:</label>
                            @Html.TextBoxFor(x => x.Username, new { id = "new_email" })
                            <input type="submit" value="save" class="gradient-button" id="submit3"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td></td>
                </tr>
                <tr>
                    <th>First name:</th>
                    <td>@Model.FirstName
                        <!--edit fields-->
                        <div class="edit_field" id="field1">
                            <label for="new_name">New First Name:</label>
                            @Html.TextBoxFor(x => x.FirstName, new { id = "new_name" })
                            <input type="submit" value="save" class="gradient-button" id="submit1"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field1" class="gradient-button edit">Edit</a></td>
                </tr>
                <tr>
                    <th>Last name:</th>
                    <td>@Model.LastName
                        <!--edit fields-->
                        <div class="edit_field" id="field2">
                            <label for="new_last_name">New Last Name:</label>
                            @Html.TextBoxFor(x => x.LastName, new { id = "new_last_name" })
                            <input type="submit" value="save" class="gradient-button" id="submit2"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field2" class="gradient-button edit">Edit</a></td>
                </tr>
                <tr>
                    <th>Display name:</th>
                    <td>@Model.DisplayName
                        <!--edit fields-->
                        <div class="edit_field" id="field_display">
                            <label for="new_display_name">New Display Name:</label>
                            @Html.TextBoxFor(x => x.DisplayName, new { id = "new_display_name" })
                            <input type="submit" value="save" class="gradient-button" id="submit_display"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field_display" class="gradient-button edit">Edit</a></td>
                </tr>
                <tr>
                    <th>Password:</th>
                    <td><input type="password" value="@Model.Membership.Password" disabled="disabled" style="border: none; background-color: white; padding: 0;" />
                        <!--edit fields-->
                        <div class="edit_field" id="field4">
                            <label for="new_password">New Password:</label>
                            @Html.PasswordFor(x => x.Membership.Password, new { id = "new_password" })
                            <input type="submit" value="save" class="gradient-button" id="submit4"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td></td>
                </tr>
                <tr>
                    <th>Cell phone:</th>
                    <td>@Model.CellPhone
                        <!--edit fields-->
                        <div class="edit_field" id="field_cell">
                            <label for="new_cell">New Cell Phone:</label>
                            @Html.TextBoxFor(x => x.CellPhone, new { id = "new_cell" })
                            <input type="submit" value="save" class="gradient-button" id="submit_cell"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field_cell" class="gradient-button edit">Edit</a></td>
                </tr>
                <tr>
                    <th>Home phone:</th>
                    <td>@Model.HomePhone
                        <!--edit fields-->
                        <div class="edit_field" id="field_home">
                            <label for="new_home">New Home Phone:</label>
                            @Html.TextBoxFor(x => x.HomePhone, new { id = "new_home" })
                            <input type="submit" value="save" class="gradient-button" id="submit_home"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field_home" class="gradient-button edit">Edit</a></td>
                </tr>
                <tr>
                    <th>Street Address:</th>
                    <td>@(Model.Address != null ? Model.Address.Address1 : "No address given")
                        <!--edit fields-->
                        <div class="edit_field" id="field5">
                            <label for="new_address">New Address:</label>
                            @Html.TextBoxFor(x => x.Address.Address1, new { id = "new_address" })
                            <input type="submit" value="save" class="gradient-button" id="submit5"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field5" class="gradient-button edit">Edit</a></td>
                </tr>

                <tr>
                    <th>Town / City:</th>
                    <td>@(Model.Address != null ? Model.Address.City.Name : "No address given")
                        <!--edit fields-->
                        <div class="edit_field" id="field6">
                            <label for="new_city">New City:</label>
                            @Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList, new { @class = "f-item" })
                            <input type="submit" value="save" class="gradient-button" id="submit6"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field6" class="gradient-button edit">Edit</a></td>
                </tr>

                <tr>
                    <th>Postal Code:</th>
                    <td>@(Model.Address != null ? Model.Address.PostalCode : "No address given")
                        <!--edit fields-->
                        <div class="edit_field" id="field7">
                            <label for="new_zip">New Postal Code:</label>
                            @Html.TextBoxFor(x => x.Address.PostalCode, new { id = "new_zip" })
                            <input type="submit" value="save" class="gradient-button" id="submit7"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field7" class="gradient-button edit">Edit</a></td>
                </tr>

                <tr>
                    <th>First Admin Division (State):</th>
                    <td>@(Model.Address != null ? Model.Address.City.FirstAdminDivision.Name : "No address given")
                        <!--edit fields-->
                        <div class="edit_field" id="field8">
                            <label for="new_state">New First Admin Division (State):</label>
                            <input type="text" id="new_state"/>
                            <input type="submit" value="save" class="gradient-button" id="submit8"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field8" class="gradient-button edit">Edit</a></td>
                </tr>

                <tr>
                    <th>Country:</th>
                    <td>@(Model.Address != null ? Model.Address.City.FirstAdminDivision.Country.Name : "No address given")
                        <!--edit fields-->
                        <div class="edit_field" id="field9">
                            <label for="new_country">New Country:</label>
                            <input type="text" id="new_country"/>
                            <input type="submit" value="save" class="gradient-button" id="submit9"/>
                            <a href="#">Cancel</a>
                        </div>
                        <!--//edit fields-->
                    </td>
                    <td><a href="#field8" class="gradient-button edit">Edit</a></td>
                </tr>
            </table>

        </article>
    }

And the two controller actions:

public ActionResult MyProfile()
        {
            IAccountService accountSvc = GetService<IAccountService>();
            UserProfileServiceRequest request = new UserProfileServiceRequest();

            int userId = int.Parse(SessionMgr.GetInstance().GetSessionValue(SessionTypes.UserId).ToString());

            if (userId == 0)
            {
                return RedirectToAction("Login", "Account");
            }
            else
            {
                request.UserId = userId;
                UserProfileServiceResponse response = accountSvc.GetUserProfile(request);
                UserProfileViewModel model = AutoMapper.Mapper.Map<UserProfileViewModel>(response.UserProfile);

                ICommonService commonSvc = GetService<ICommonService>();
                GetCitiesServiceRequest citiesRequest = new GetCitiesServiceRequest { FirstAdminDivisionId = model.Address.City.FirstAdminDivision.FirstAdminDivisionId };
                KeyValuePairServiceResponse citiesResponse = commonSvc.GetCitiesForSelect(citiesRequest);
                model.CitySelectList = citiesResponse.KeyValuePairs.ToSelectList();

                List<RoleViewModel> roles = (List<RoleViewModel>)SessionMgr.GetInstance().GetSessionValue(SessionTypes.Roles);
                RoleViewModel consumerRole = roles.FirstOrDefault(x => x.Meaning.Equals(DataEnumerations.GetRoleMeaning(DataEnumerations.Role.Consumer)));

                if (consumerRole != null)
                {
                    GetConsumerTripsServiceRequest consumerTripsRequest = new GetConsumerTripsServiceRequest() { UserId = userId };
                    GetConsumerTripsServiceResponse consumerTripsResponse = commonSvc.GetConsumerTrips(consumerTripsRequest);
                    model.ConsumerTrips = AutoMapper.Mapper.Map<List<TripViewModel>>(consumerTripsResponse.Trips);

                    GetUserReviewsServiceRequest userReviewsRequest = new GetUserReviewsServiceRequest() { UserId = userId };
                    GetUserReviewsServiceResponse userReviewsResponse = commonSvc.GetUserReviews(userReviewsRequest);
                    model.UserReviews = AutoMapper.Mapper.Map<List<UserReviewViewModel>>(userReviewsResponse.UserReviews);
                }

                return View(model);
            }
        }

[HttpPost]
        public ActionResult MyProfile(UserProfileViewModel model)
        {
            UpdateUserProfileServiceRequest request = AutoMapper.Mapper.Map<UpdateUserProfileServiceRequest>(model);

            IAccountService accountSvc = GetService<IAccountService>();
            accountSvc.UpdateUserProfile(request);

            return RedirectToAction("MyProfile", "Account");

        }

The entire AddressViewModel class:

public class AddressViewModel
    {
        public Int32 AddressId { get; set; }
        public String Address1 { get; set; }
        public String Address2 { get; set; }
        public Int32 CityId { get; set; }
        public String PostalCode { get; set; }

        public CityViewModel City { get; set; }

    }

And the entire UserProfileViewModel:

public class UserProfileViewModel : BaseViewModel
    {
        public int UserId { get; set; }
        public string Username { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string DisplayName { get; set; }
        public string CellPhone { get; set; }
        public string HomePhone { get; set; }
        public int AddressId { get; set; }

        public AddressViewModel Address { get; set; }
        public MembershipViewModel Membership { get; set; }

        public List<TripViewModel> ConsumerTrips { get; set; }

        public List<UserReviewViewModel> UserReviews { get; set; }

        public IEnumerable<SelectListItem> CitySelectList { get; set; }
        public int SelectedCityId { get; set; }
    }

Upvotes: 1

Views: 5167

Answers (1)

user3559349
user3559349

Reputation:

Your form includes a hidden input for the property in addition to a dropdown list

@Html.HiddenFor(x => x.Address.CityId)
....
@Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList, ..)

In effect you are passing back 2 values for the same property. The DefaultModelBinder reads the first (from the hidden input which is the original value) and sets the value of Address.CityId. Any subsequent values for the same property (from the dropdown list) are ignored.

Remove the hidden input and you model will be correctly bound with the selected value.

Upvotes: 2

Related Questions