5

I have got this problem that I am having a difficulty to solve. I am creating a page where the user will be presented with a list of items (Product Types). Each item will have a dropdown list next to it so that the user can make appropriate selection to create a mapping. After making selection then the user submits the form, and the value will be written to the database.

The problem is that when it is submitted, I am not getting any values back. Specifically, 'Mappings' is empty in the model that is returned by the POST action. The GET action works fine. The following is the essence of what I have written:

Model:

public class ProductTypeMappingViewModel
{
    //this is empty in the POST object
    public List<ProductTypeMapping> Mappings { get; set; }

    public ProductTypeMappingViewModel()
    {
        Mappings = new List<ProductTypeMapping>();
    }

    public ProductTypeMappingViewModel(string db)
    {
        //use this to populate 'Mappings' for GET action
        //works fine
    }

    public void UpdateDB()
    {
        //to be called on the object
        //returned from POST action
        foreach(var mapping in Mappings)
        {
        //Mappings is always empty after POST
        //Suppose to add to db
        }
    }
}

public class ProductTypeMapping
{
    public string ProductTypeName { get; set; }
    public int SelectedStandardProductTypeKey { get; set; }
    public SelectList StandardProductTypes { get; set; }

    public ProductTypeMapping() 
    {
        StandardProductTypes = new SelectList(new List<SelectListItem>());
    }

    public int GetSelectedProductTypeKey() { //return selected key}

    public string GetSelectedProductTypeName() { //return selected name} 
}

View:

@model CorporateM10.Models.ProductTypeMappingViewModel

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

    <div class="form-horizontal">

        @Html.ValidationSummary(true)

        <table class="table">
            @foreach (var dept in Model.Mappings)
            {
            <tr>
                <td>
                    @Html.DisplayFor(model => dept.ProductTypeName, new { })
                </td>
                <td>
                    @Html.DropDownListFor(model => dept.SelectedStandardProductTypeKey, dept.StandardProductTypes, "(Select Department)", new { })
                </td>
            </tr>
            }
        </table>

        <div>
            <input type="submit" value="Save" class="btn btn-default" />
        </div>
    </div>
}

Any insight will be greatly appreciated.

2 Answers 2

9

foreach here causes select element in final HTML to have incorrect name attribute. Thus nothing is posted to the server. Replace this with for loop:

<table class="table">
    @for (int i=0; i<Model.Mappings.Count; i++)
    {
    <tr>
        <td>
            @Html.DisplayFor(model => model.Mappings[i].ProductTypeName, new { })
        </td>
        <td>
            @Html.DropDownListFor(model => model.Mappings[i].SelectedStandardProductTypeKey, model.Mappings[i].StandardProductTypes, "(Select Department)", new { })
        </td>
    </tr>
    }
</table>
Sign up to request clarification or add additional context in comments.

1 Comment

I have been searching for this fix for so long! Thank you! It's crazy that the foreach loop would decouple a property from the model like that, naturally I would have assumed if it was within the form it it would be moved back to the server on the post action.
3

As @Andrei said the problem relies on the name attribute. But to add a little bit to his answer, here's the parameter names in the request that the default model binder expects for your case.

Mappings[0].SelectedStandardProductTypeKey
Mappings[1].SelectedStandardProductTypeKey
Mappings[2].SelectedStandardProductTypeKey

...

Without any breaks in the numbering, i.e.:

Mappings[0].SelectedStandardProductTypeKey
Mappings[2].SelectedStandardProductTypeKey

Won't work because of the missing Mapping[1]...

When you use the dropdown helper like this: @Html.DropDownListFor(model => dept.SelectedStandardProductTypeKey, dept.StandardProductTypes, "(Select Department)", new { })

It generates an input with name="SelectedStandardProductTypeKey" (you need it to be Mappings[0].SelectedStandardProductTypeKey)

If you use a for loop and use the dropdown helper like this: @Html.DropDownListFor(model => model.Mappings[i].SelectedStandardProductTypeKey
You'll get the input with the correct name.

Any parameter in the request for which the model binder cannot find a property in the model, it will ignore, that's why the Mappings property is null in your case.

Here are two great resource that explain all this (and that provide alternative ways to represent collections that might be useful if you can't a the for loop to generate a numbered index without breaks):
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

1 Comment

Thanks for sharing the knowledge here with full references. It helps me understand why.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.