Underfaker
Underfaker

Reputation: 97

Create more than one object of the same type in the same view

in my create view I want to give the user the possibility to create a list of objects (of the same type). Therefore I created a table in the view including each inputfield in each row. The number of rows respective "creatable" objects is a fixed number.

Lets say there is a class Book including two properties title and author and the user should be able two create 10 or less books.

How can I do that?

I don't know how to pass a list of objects (that are binded) to the controller. I tried:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ICollection<Book> bookList)
    {
        if (ModelState.IsValid)
        {
            foreach(var item in bookList)
                db.Books.Add(item);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(articlediscounts);
    }

And in the view it is:

<fieldset>
    <legend>Book</legend>

    <table id="tableBooks" class="display" cellspacing="0" width="100%">
            <thead>
                <tr>
                    <th>Title</th>
                    <th>Author</th>
                </tr>
            </thead>
            <tbody>
                @for (int i = 0; i < 10 ;i++ )
                {
                    <tr>
                        <td>
                            <div class="editor-field">
                                @Html.EditorFor(model => model.Title)
                                @Html.ValidationMessageFor(model => model.Title)
                            </div>
                        </td>
                        <td>
                            <div class="editor-field">
                                @Html.EditorFor(model => model.Author)
                                @Html.ValidationMessageFor(model => model.Author)
                            </div>
                        </td>
                    </tr>
                }
            </tbody>
        </table>

    <p>
        <input type="submit" value="Create" />
    </p>
</fieldset>

As booklist is null, it doesn't work and I don't know how to put all created objects in this list.

If you have any suggestions I would be very thankful.

Upvotes: 5

Views: 1405

Answers (3)

fdomn-m
fdomn-m

Reputation: 28611

Scott Hanselman has some details on passing arrays to MVC control binding: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

Which is essentially: ensure your controls have the correct names: using an index for lists

Change your for loop to something like:

@for (int i = 0; i < 10 ; i++)
{
    <tr>
        <td>
            <div class="editor-field">
                <input type="text" name="book[" + i + "].Title" />
            </div>
        </td>
        <td>
            <div class="editor-field">
                <input type="text" name="book[" + i + "].Author" />
            </div>
        </td>
    </tr>
}

this will then bind to your post action automatically.

[HttpPost]
public ActionResult Create(IList<Book> bookList)

You can then show/hide these as required or use js/jquery to add them dynamically

Edit: As correctly observed by Stephen Muecke, the above answer only regards the binding from the form+fields to the HttpPost, which appears to be the emphasis of the question.

The post action in the original post is not compatible with the view. There's quite a bit of missing code in the OP that may or may not be relevant, but worth observing that if your view is for a single model, then your fail code on ModelState.IsValid needs to return a single model or your view needs to be for an IList (or similar), otherwise you won't get server-side validation (but you can still get client-side validation if you manually add it to the <input>s)

Upvotes: 2

user3559349
user3559349

Reputation:

The fact you use @Html.EditorFor(model => model.Title) suggests that you have declared the model in the view as

@model yourAssembly.Book

Which allows to to post back only one Book so the POST method would need to be

public ActionResult Create(Book model)

Note that you current implementation create inputs that look like

<input id="Title" name="Title" ... />

The name attributes do not have indexers (they would need to be name="[0].Title", name="[1].Title" etc.) so cannot bind to a collection, and its also invalid html because of the duplicate id attributes.

If you want to create exactly 10 books, then you need initialize a collection in the GET method and pass the collection to the view

public ActionResult Create()
{
  List<Book> model = new List<Book>();
  for(int i = 0; i < 10;i++)
  {
    model.Add(new Book());
  }
  return View(model);
}

and in the view

@model yourAssembly.Book
@using (Html.BeginForm())
{
  for(int i = 0; i < Model.Count; i++)
  {
    @Html.TextBoxFor(m => m[i].Title)
    @Html.ValidationMessageFor(m => m[i].Title)
    .... // ditto for other properties of Book
  }
  <input type="submit" .. />
}

which will now bind to your collection when you POST to

public ActionResult Create(List<Book> bookList)

Note the collection should be List<Book> in case you need to return the view.

However this may force the user to create all 10 books, otherwise validation may fail (as suggested by your use of @Html.ValidationMessageFor()). A better approach is to dynamically add new Book items in the view using either the BeginCollectionItem helper method (refer example) or a client template as per this answer.

Upvotes: 0

T McKeown
T McKeown

Reputation: 12847

You'd need to send a JSON object that has the list of books in it. So the first thing is to create a Model class like this:

public class SavedBooks{
    public List<Book> Books { get; set; }
}

Then the Book class would have to have these 2 props:

public class Book {
   public string Title { get; set; }
   public string Author { get; set; }
}

Next, change your controller to use this model:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(SavedBooks model)

Then create a javascript method (using jQuery) to create a JSON object that matches the structure of the controllers SavedBooks class:

var json = { Books: [ { Title: $('#title_1').val(), Author: $('#Author_1').val() } ,
                      { as many items as you want }
                    ]
           };
$.ajax(
{
    url: "/Controller/Create",
    type: "POST",
    dataType: "json",
    data: json
});

Upvotes: 0

Related Questions