Mr Jones
Mr Jones

Reputation: 1198

MVC3- Passing an entire model with dynamic data from view to controller

I have two models: OuterModel and InnerModel. There is a one to many relationship between OuterModel and InnerModel, respectively. To clarify my question, my model is of type IEnumerable<OuterModel>. I'm passing a random number of OuterModels to the view and the user creates any number of InnerModels for each OuterModel. Then on submission, I want the controller to receive the list of OuterModels so that the InnerModels can be added to the database to their intended OuterModels.

I believe I have the naming convention correct to make use of MVC's built in model binding. Here's what that looks like:

OuterModel[i].InnerModel[j].Property

My problem is, I don't really know how to get a list of OuterModels passed to the controller. Here's what I've tried in my View:

    @model IEnumerable<OuterModel>

    @using (Html.BeginForm("Create", "Controller", new { OuterModels = Model }, FormMethod.Post))
    {
       //Code to create the InnerModels here
    }

And here's what I have in my Controller:

    [HttpPost]
    public ActionResult Create(IEnumerable<OuterModel> OuterModels, FormCollection fc)
    {
       String[] keys = fc.AllKeys;
       if(ModelState.IsValid){
          //Add to db
       }
    }

Keys shows that all of my properties are following the naming convention that I specified earlier, but ModelState.IsValid is returning false. It shows that OuterModels' count is 0.

Even though I'm telling the form to submit OuterModels = Model before any InnerModels are created, you would think there would still be data in OuterModels considering it's passed to the view. I am really tired today, so I'm guessing I'm looking over one (or many) small detail(s). Any suggestions?

--EDIT--

Passing a list of OuterModels to the controller may not be the best approach. If anybody has a better suggestion, please share.

Upvotes: 0

Views: 3161

Answers (1)

ek_ny
ek_ny

Reputation: 10243

As long as indexes are used properly, then this should not be an issue. Here is how I would envision the form names.

Model[0].foo

Model[0].Inner[0].bar

Model[0].Inner[1].bar

Where outer model has a property called foo and Outer model has a property called inner which is a collection of inner objects. Inner object has a property called bar. If your form is rendered with the correct indexes then the model binding should work. Things can get tricky if form fields are generated client side. I recommended going back to server in order to manipulate the model. There are some extra round trips, but you can make them via Ajax request.

Here are some more details in a more fleshed out example.

public class InnerModel{
    public string Name{get; set;}
}

public class OuterModel{
   public List<InnerModel> InnerModels{get; set;}
   public string Name{get; set;}
}

Here is what I would envision my view would look like:

@model IEnumerable<OuterModel>

<ul>
  @{int i = 0;}
  @foreach(var item in Model){
     <li>
        Outer Name : @Html.TextBoxFor(m=>Model[i].Name)
        <br />
        @{int j = 0;}
        <ul>
            @foreach(var innerItem in Model[i].InnerModels){
               <li>Inner Name : @Html.TextBoxFor(m=> Model[i].InnerModels[j].Name)</li>
               j++;

            }
        </ul> 
        i++;
     </li>
  }
</ul>

If this is wrapped in a form--- and the controller action looks like this:

public ActionResult Action(List<OuterModel> model){
}

then I would think model would be populated correctly.

I noticed your form.. it doesn't look right to me... I wouldn't think that the passing the OuterModels like that is going to work-- although frankly I might be wrong.

@using (Html.BeginForm("Create", "Controller", new { OuterModels = Model }, FormMethod.Post))
{
   //Code to create the InnerModels here
}

Here is an example I did for the class I teach.. that definitely works..

public class Author
{
    public string Name { get; set; }
}

public class Book
{
    public string Name { get; set; }

    public List<Author> Authors { get; set; }
}

Controller:

public class BookController : Controller
{

    public static List<Book> _model = null;

    public List<Book> Model
    {
        get
        {
            if (_model == null)
            {
                _model = new List<Book>
                {
                    new Book{
                        Name = "Go Dog Go", 
                        Authors = new List<Author>{
                            new Author{Name = "Dr. Seuss"}
                        }},
                    new Book{
                        Name = "All the Presidents Men", 
                        Authors = new List<Author>{
                            new Author{Name = "Woodward"},
                            new Author{Name = "Bernstein"}
                        }},
                    new Book{
                        Name = "Pro ASP.NET MVC Framework", 
                        Authors = new List<Author>{
                            new Author{Name = "Sanderson"},
                            new Author{Name = "Stewart"},
                            new Author {Name = "Freeman"}
                        }}
                };
            }
            return _model;
        }
    }

    public ActionResult Index()
    {
        return View(Model);
    }

    public ActionResult Edit()
    {
        return View(Model);
    }

    [HttpPost]
    public ActionResult Edit(List<Book> books)
    {
        _model = books;
        return RedirectToAction("Index");
        //return View(books);
    }

}

and View:

@model List<AmazonWeb.Models.Book>
@{
    ViewBag.Title = "Index";
}

<div class="content">

@Html.ActionLink("Index", "Index")

@using (Html.BeginForm())
{
  <input type="submit" value="save" />
<ul class="book-list">

@for (var i = 0; i < Model.Count; i++ )
{
    <li>
        <label>Book Name</label> : @Html.TextBoxFor(m => Model[i].Name)
        <ul>
            @for (var j = 0; j < Model[i].Authors.Count; j++ )
            {
                <li><label>Author Name</label> : @Html.TextBoxFor(m => Model[i].Authors[j].Name)</li>
            }
        </ul>
    </li>
}
</ul>
    <input type="submit" value="save" />
}
</div>

Upvotes: 1

Related Questions