Avba
Avba

Reputation: 15286

Binding a ViewModel class that contains nested objects

I've created a class which holds objects of other complex classes.

So for instance

class A {

    public int ID { set; get; }

    public string MyObjectName {set; get; }

    public B ObjectOfTypeB { set; get; }

}

class B {

    public int ID { set; get; }

    public int CategoryNumber { set; get; }

    public string CategoryDescription { set; get; }

}

Now I have a controller method which creates objects of type A. I scaffolded a view for creation of my model (class A). But because A holds a complex type it doesn't know how to create the input fields for the B type.

In my create function

// helper method to get a list of the possible B objects - 
// this method will eventually query a database. for now just static objects

private List<Category> getBobjects()
{
    List<B> bs = new List<B>();
    bs.Add(new B() { ID = 1, CategoryNumber = 2, CategoryDescription = "Config 1" });
    bs.Add(new B() { ID = 2, CategoryNumber = 3, CategoryDescription = "Config 2" });
    return bs;
}

[HttpGet]
public ActionResult Create()
{
    // Adding to the view bags the categories to display
    ViewBag.bs = getBobjects();
    return View();
}

And in the View

@Html.DropDownListFor(model => model.ObjectOfTypeB, 
       new SelectList(ViewBag.bs, "ID", "CategoryDescription"))

I assumed that when submit the form, the data from the ObjectOfTypeB would be binded to the post back object of type A. But this isn't happening.

Can anybody explain how I can pass a list of possible values for a particular field to the view and have the postback bind the value onto the returned object?

[Bug Print Screen][1]

Upvotes: 2

Views: 1519

Answers (4)

Avba
Avba

Reputation: 15286

I created a viewmodel for that complex class type model

had a key for each of the nested objects and for those keys used a selectlist from the referenced object type for the text to display.

After that on the post back I translated the viewmodel back to the type of the model

Upvotes: 0

Rashedul.Rubel
Rashedul.Rubel

Reputation: 3584

  private IList getBobjects()
        {
        List<B> bs = new List<B>();
        bs.Add(new B() { ID = 1, CategoryNumber = 2, 
             CategoryDescription = "Config 1" });
        bs.Add(new B() { ID = 2, CategoryNumber = 3, 
             CategoryDescription = "Config 2" });
        return bs;
        }

    public IList<SelectListItem> GetDropdownlistItems()
    {
        IList<SelectListItem> ilSelectList = new List<SelectListItem>();

        IList objlist = getBobjects();
        foreach (object[] arrobject in objlist)
        {
            SelectListItem selectListItem = new SelectListItem();
            selectListItem.Value = arrobject[0].ToString();
            selectListItem.Text = arrobject[1].ToString();
            ilSelectList.Add(selectListItem);
        }

        return ilSelectList;
    }

    public ActionResult Create()
    {
        IList<SelectListItem> ilSelectListItems = GetDropdownlistItems();
        ViewBag.bs = ilSelectListItems;
        return View();
    } 

and in view:

@Html.DropDownList("bs", new SelectList(ViewBag.bs, "Text", "Value"))

Hope this helps, thanks.

Upvotes: 0

Felipe Oriani
Felipe Oriani

Reputation: 38618

You have to fill the ViewBag again to return the bind to your View. You could create a method that fill this ViewBag for you and use it on the get/post methods add/edit as well. Try to keep your ViewModels (I like to call as InputModel) only with primitive types (string, int, double, bool) in other words, change the B complex type to a int. Try something like this:

private void SetViewBagCombo(int selectedValue = 0)
{
   ViewBag.bs = new SelectList(getBobjects(), "Id", "CategoryDescription", selectedValue);
}

public ActionResult Create()
{
   SetViewBagCombo(); 
   return View();   
}

[HttpPost]
public ActionResult Create(A input)
{
   if (ModelState.IsValid)
   {
      // persist it
      return RedirectToAction("Index");
   }

   SetViewBagCombo(input.B.Id); 
   return View();   
}

And in your View:

@Html.DropDownListFor(model => model.IdOfB, (SelectList)ViewBag.bs, "Select...")

Upvotes: 0

Eli Gassert
Eli Gassert

Reputation: 9763

Your binding is not correct. If you're trying to fill the ID property of your ObjectOfTypeB property, then your DropDownListFor should bind to model => model.ObjectOfTypeB.ID

If you view source, your source should look something like <select name="ObjectOfTypeB.ID"> if you expect it to auto-fill that property on postback.

In this case I'd probably use a ViewModel (VM) to represetn your model. You're not REALLY filling out a full object B, as a B contains a category and description related to the ID selected -- in other words, the three properties are bound together and not separately fillable.

I'd do something like:

class AViewModel
{
    public int ID {set;get;}

    public string MyObjectName {set; get;}

    public int IDofB { set; get; }
}

To make your life easier of translating between A and AViewModel, look at tools like AutoMapper.

Upvotes: 1

Related Questions