ASP.NET MVC4 Model's Child Collection Drop Dow

2019-08-09 05:10发布

问题:

I'm having the same issue as this here I believe, but the workaround is not working for me. My issue is that I have a child collection of models inside my main view's ViewModel. They contain data to be displayed in two fields, a dropdownlist and a password field. Each dropdownlist selection must be unique. Everything is saving and being sent to the view properly, however the dropdownlist are not binding to the selected values when the view is called but the password field is. They all default to the first selection, even though the property they are suppose to bind to is unique and only one can be the first value. Any help or insight is appreciated. Thanks.

Here is the part in my view where the issue is occurring. I have commented out my efforts and tried the above link's workaround to no avail:

@functions {
private IEnumerable<SelectListItem> Mark(IEnumerable<SelectListItem> items, object Id)
{
    foreach (var item in items)
        if (string.CompareOrdinal(item.Value, Convert.ToString(Id)) == 0)
            item.Selected = true;
    return items;
}
}



@for (int j = 0; j < Model.PasswordResetQuestionUserAnswers.Count(); j++)
{
@Html.Hidden("PasswordResetQuestionUserAnswers.Index", j)
@Html.HiddenFor(p => Model.PasswordResetQuestionUserAnswers[j].Id)
@Html.HiddenFor(p => Model.PasswordResetQuestionUserAnswers[j].UserId)
<div class="form-group">
<label class="col-md-2 control-label">Password Reset Question @(j+1)</label>
<div class="col-md-6">
@*@Html.DropDownList("PasswordResetQuestionUserAnswers[" + j + "].PasswordResetQuestionId", Model.PasswordResetQuestionList, new { @class = "form-control passwordQuestion" })*@
@*@Html.DropDownListFor(x => Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, Model.PasswordResetQuestionList, new { @class = "form-control passwordQuestion" })*@
@Html.DropDownListFor(x => Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, Mark(Model.PasswordResetQuestionList, Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId))
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Password Reset Answer @(j+1)</label>
<div class="col-md-6">
@Html.Password("PasswordResetQuestionUserAnswers[" + j + "].Answer", Model.PasswordResetQuestionUserAnswers[j].Answer, new { @class = "form-control passwordQuestionUserAnswer" })
@*@Html.PasswordFor(x => Model.PasswordResetQuestionUserAnswers[j].Answer, new { @class = "form-control passwordQuestionUserAnswer" })*@
</div>
</div>
}

回答1:

I just had this same problem. This syntax works for me:

 @Html.DropDownListFor(x => x.ChildCollection[i].ChildID, new SelectList(ViewBag.ChildCollectionSelect as SelectList, "Value", "Text", Model.ChildCollection[i].ChildID))

Define the SelectList as new, then specifically set the selected value from the model.



回答2:

This is an adaption to the example of above, for what worked for me in a similar scenario, where itm represents the child object in the collection. I'm not exactly sure what all is going on in that example -- too many "Questions", "Users", and "Answers", but say if you wanted a dropdown of users and it to be filled with the particular one that had been assigned to that child item:

foreach (var itm in Model.PasswordResetQuestionUserAnswers)
{
    @Html.DropDownListFor(modelItem => itm.UserId,
                          new SelectList( (IEnumerable<SelectListItem>)ViewData["users"], "Value", "Text", itm.UserId),
                          htmlAttributes: new { @class = "form-control" }
                        )
}

Where you'd fill ViewData["users"] like this in the Controller method that renders the view:

var usersList = GetUsersList();
ViewData["users"] = usersList;

and have these supporting functions:

private static SelectListItem[] _UsersList;

/// <summary>
/// Returns a static category list that is cached
/// </summary>
/// <returns></returns>
public SelectListItem[] GetUsersList()
{
    if (_UsersList == null)
    {
        var users = repository.GetAllUsers().Select(a => new SelectListItem()
         {
             Text = a.USER_NAME,
             Value = a.USER_ID.ToString()
         }).ToList();
         users.Insert(0, new SelectListItem() { Value = "0", Text = "-- Please select your user --" });

        _UsersList = users.ToArray();
    }

    // Have to create new instances via projection
    // to avoid ModelBinding updates to affect this
    // globally
    return _UsersList
        .Select(d => new SelectListItem()
    {
         Value = d.Value,
         Text = d.Text
    })
     .ToArray();
}

Repository.cs

My Repository function GetAllUsers() for the function, above:

Model1 db = new Model1(); // Entity Framework context

// Users
public IList<USERS> GetAllUsers()
{
    return db.USERS.OrderBy(e => e.USER_ID).ToList();
}

Users.cs

public partial class USERS
{
    [Key]
    public int USER_ID { get; set; }

    [Required]
    [StringLength(30)]
    public string USER_NAME { get; set; }
}

Edit

After re-reading the question, it seems it was about posting password reset questions.

foreach (var itm in Model.PasswordResetQuestionUserAnswers)
{
    @Html.DropDownListFor(modelItem => itm.PasswordResetQuestionId,
                          new SelectList( (IEnumerable<SelectListItem>)ViewData["pwordResetQuestions"], "Value", "Text", itm.PasswordResetQuestionId),
                          htmlAttributes: new { @class = "form-control" }
                        )
}

And you'd have to have a ViewData["pwordResetQuestions"] filled like this in the controller method that renders that view:

var questionsList = GetQuestionsList();
ViewData["questions"] = questionsList;

and these supporting functions/objects:

private SelectListItem[] _QuestionsList;
public SelectListItem[] GetQuestionsList()
{
    if (_QuestionsList == null)
    {
        var questions = PasswordResetQuestionUserAnswers.Select(a => new SelectListItem()
        {
            Text = a.Answer, //? I didn't see a "PasswordResetQuestionText" call in your example, so...
            Value = a.PasswordResetQuestionId.ToString()
        }).ToList();
        questions.Insert(1, new SelectListItem() { Value = "1", Text = "Mother's Maiden Name" });
        questions.Insert(2, new SelectListItem() { Value = "2", Text = "Elementary school attended" });

        _QuestionsList = questions.ToArray();
    }

    // Have to create new instances via projection
    // to avoid ModelBinding updates to affect this
    // globally
    return _QuestionsList
         .Select(d => new SelectListItem()
    {
         Value = d.Value,
         Text = d.Text
    })
    .ToArray();
}

I hard-coded some questions in there - I kinda doubt you'd have a table for them, usually companies only have less than 10. But you could always do that database call like I did for the Users table if they were using a database table - which is why I left that example there.



回答3:

I was having the same issue and fighting with it. The examples above got me over the hump, but I was able to simplify using the code the way you have it, with one modification:

Original Code

@Html.DropDownListFor(x => Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, Model.PasswordResetQuestionList, new { @class = "form-control passwordQuestion" })

Update Code: (Wrap it with a new SelectList)

@Html.DropDownListFor(x => Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, new SelectList(Model.PasswordResetQuestionList, "Value", "Text", Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, new { @class = "form-control passwordQuestion" })

This eliminates the need for the ViewBag or ViewData.