Sun
Sun

Reputation: 4718

MVC ViewModel collection null on postback

I have a ViewModel that contains a List of SelectListItems called Types. When I go into the Edit page the types are populated and the dropdownlist contains the items as expected. When I post the changes back the list of SelectListItems is null. i.e. 'model.Types' is null.

Why is this and how do I fix it?

I've seen this question asked a few times but they all have different answer and I've tried a few and they don't work.

I'm new to MVC so please help.

Thanks

My ViewModel:

public class StaticItemViewModel
{
    public Static_Item StaticItem { get; set; }
    public List<SelectListItem> Types { get; set; }     
}

My Controller:

public class StaticItemController : Controller
{
    private DB db = new DB();

    public ActionResult Edit(int id)
    {            
        Static_Item staticItem = db.Static_Item.Find(id);

        if (staticItem == null)
        {
            return HttpNotFound();
        }

        StaticItemViewModel staticItemViewModel = new StaticItemViewModel()
        {
            StaticItem = staticItem,
            Types = LoadStaticItemTypes(staticItem.Type)
        };

        return View(staticItemViewModel);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include="StaticItem")] StaticItemViewModel model)                    
    {
        if (ModelState.IsValid)
        {
            model.StaticItem.Changed_Date = DateTime.Now;
            db.Entry(model.StaticItem).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(model);
    }

    List<SelectListItem> LoadStaticItemTypes(string selectedType = "")
    {
        List<SelectListItem> items = new List<SelectListItem>();

        foreach (var item in db.Static_Item.Select(s => s.Type).Distinct().OrderBy(s => s))
        {
            items.Add(new SelectListItem { Text = item, Value = item, Selected = item == selectedType });
        }

        return items;
    }
}

My View:

@model StaticItemViewModel

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

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

    <div class="form-horizontal">
        <h4>@Model.StaticItem.Description</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.StaticItem.UN_Static_Item)

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

        <div class="form-group">
            @Html.LabelFor(model => model.StaticItem.Type, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DropDownListFor(model => model.StaticItem.Type, Model.Types, new { @class="form-control" })
                @Html.ValidationMessageFor(model => model.StaticItem.Type, "", new { @class = "text-danger" })
            </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>
}

Upvotes: 1

Views: 1061

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239440

That's because Types just holds the list of options, and the actual options themselves will never be posted back. You need a property like:

public List<int> SelectedTypes { get; set; }

And then bind to that in your view:

@Html.ListBoxFor(m => m.SelectedTypes, Model.Types)

That's assuming your option values are just the type's PK, you could just as well bind to a List<string>, etc., but it must be a primitive type (string, int, etc.). You can't post back the full Type objects. If you need those, you'll need to use the list of ids, or whatever, to look them up from the database.

Upvotes: 4

Related Questions