How to Post a List of Items in MVC

How can I post a list of items in MVC

I wish I could see more of your classes and code, because you don't have something set up right.

I recreated something from what you did provide, which works. I created an MVC 3 project for this sample.

Views/Shared/_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
</head>

<body>
@RenderBody()
</body>
</html>

Views/Shared/_Partial.cshtml

@model RazorListTest.Models.AnswerScheme 

<table>
@for (int i = 0; i < Model.Answers.Count; i++) {
<tr>
<td>
@Html.HiddenFor(model => Model.Answers[i].IsMissing)
@Html.TextBoxFor(model => Model.Answers[i].Value, new { @class = "inputValue" })
</td>
<td>
@Html.TextBoxFor(model => Model.Answers[i].Text, new { @class = "inputAnswer" })
</td>
<td><span class="span-delete" data-answer-scheme-id="@Model.Id" data-answer-id="@Model.Answers[i].Id" >x</span></td>
</tr>
}
</table>

Models/AnswerDisplayItem.cs

using System.Collections.Generic;

namespace RazorListTest.Models
{
public class AnswerDisplayItem
{
public bool IsMissing { get; set; }
public string Text { get; set; }
public string Value { get; set; }
public string Id { get; set; }
}

public class AnswerScheme
{
public List<AnswerDisplayItem> Answers { get; set; }
public string Id { get; set; }

public AnswerScheme()
{
Answers = new List<AnswerDisplayItem>();
}
}
}

Home/Index.cshtml

@model RazorListTest.Models.AnswerScheme

@using (Html.BeginForm(null, null, FormMethod.Get, new { name="formAnswerScheme", id = "formAnswerScheme"}))
{
{Html.RenderPartial("_Partial");}

<div>
<input type="button" value="Click me" id="btnClick"/>
</div>

<div id="divAnswerSchemeContainer">

</div>
}

<script type="text/javascript">
$("#btnClick").click(function () {

$.ajax({
url: 'Home/AddAnswer',
type: 'POST',
dataType: 'json',
data: $("#formAnswerScheme").serialize(),
success: function (data) {
console.log(data);
$("#divAnswerSchemeContainer").html(data);
},
error: function (xhr, textStatus, exceptionThrown) { alert(JSON.parse(xhr.responseText)); }
});
});
</script>

Controllers/HomeController.cs

using System.Collections.Generic;
using System.Web.Mvc;
using RazorListTest.Models;

namespace RazorListTest.Controllers
{
public class HomeController : Controller
{

public ActionResult Index()
{
AnswerScheme a = new AnswerScheme();

a.Id = "1cd14b08-ce3b-4671-8cf8-1bcf69f12b2d";

List<AnswerDisplayItem> adi = new List<AnswerDisplayItem>();
AnswerDisplayItem a1 = new AnswerDisplayItem();
a1.IsMissing = false;
a1.Text = "Ja";
a1.Value = "0";
a1.Id = "1234";
AnswerDisplayItem a2 = new AnswerDisplayItem();
a2.IsMissing = false;
a2.Text = "Nein";
a2.Value = "1";
a2.Id = "5678";
adi.Add(a1);
adi.Add(a2);
a.Answers = adi;
return View(a);
}

[HttpPost]
public JsonResult AddAnswer(AnswerScheme answerScheme)
{
return Json("the list is in the Model.");
}
}
}

How to post only selected items from list in MVC

Good question, I might implement this on my projects as well.

I could only think of one way-- using javascript, when form is submitted, delete the other form input fields first then resubmit the form.

First is we need to put the input fields inside a parent div with class input-container, so we could quickly delete all the fields by just deleting the entire div. I also added a class targetCheckbox to your input field so we could attach an event to it;

@using (Html.BeginForm())
{
for (int i = 0; i < Model.Count(); i++)
{
<div class="input-group">
@Html.CheckBoxFor(m => Model[i].IsSelected, new { @class="targetCheckbox" })
//few other controls as
<div class="input-group">
}
<input type="submit" value="Submit Selection" >
}

We'll need to bind an event to to your form. On form submit, we need to identify which targetCheckbox are not checked, then delete the div that contains them. We also need to replace the indexes of the input fields because ASP.NET MVC model binding must start with 0 and should not skip. After all that resubmit the form;

<script>
$(document).ready(function(){
$("form").submit(function(e){
e.preventDefault();

var index = 0;
// loop through all the checkbox
$(".targetCheckbox").each(function(){
if($(this).is(":checked")){
// get the parent
var parent = $(this).closest(".input-container");

// loop through all the input fields inside the parent
var inputFieldsInsideParent = $(parent).find(":input");

// change the index inside the name attribute
$(inputFieldsInsideParent).each(function(){
var name = $(this).attr("name");
var firstBracket = name.IndexOf("[");
var secondBracket = name.IndexOf("]");

if(firstBracket != null && secondBracket != null){
// check if this is a valid input field to replace

var newName = name.substring(0,firstBracket)+index+name.substring(secondBracket);
// result should be IntputFieldName[newIndex].Property

// assign the new name
$(this).attr("name",newName);
}
});

index++;
}else{
// empty the parent
$(this).closest(".input-container").html("");
}
});

// submit the form
$(this).submit();
});

});
</script>

MVC Page Model's List Items on Submit to use in Post Method

According to the doc,

If you want to accept the list, the request data should be like DocumentListing[0],DocumentListing[1]....

An example below:

<form method="post">

<input asp-for="FileID" type="hidden" />

@for (int i = 0; i < Model.DocumentListing.Count(); i++)
{
<input asp-for="DocumentListing[i]" type="text">
}
<input type="submit" value="submit" class="btn btn-danger" />
</form>

Controller:

public IActionResult Index()
{
var test = new Test
{
FileID = 1,
DocumentListing = new List<string> { "A", "B", "C" }
};

return View(test);
}

[HttpPost]
public IActionResult Index(Test test)
{

return View(test);
}

Result:

Sample Image

ASP.NET MVC send ListFoo collection from my View to a Controller

So your main problem is that you are trying to use an action (which is a link, an anchor tag) to pass a list to the controller. But an anchor tag is a "Get" request and you normally don't pass lists via Gets (yes, it is possible, but generally not advised). It would be better for you to use a form post for this. Here is a basic outline:

<!-- red cars -->
<form action="/bar/index/red" method="post">

@foreach(var car in Model.Where(c => c.Color == "red"))
{
<input type="hidden" name="carnames" value="@car.CarName" />
}
<button class="btn btn-primary" type="submit">Red Cars</button>

</form>

<!-- green cars -->
<form action="/bar/index/green" method="post">

@foreach(var car in Model.Where(c => c.Color == "green"))
{
<input type="hidden" name="carnames" value="@car.CarName" />
}
<button class="btn btn-primary" type="submit">Green Cars</button>

</form>

<!-- blue cars -->
<form action="/bar/index/blue" method="post">

@foreach(var car in Model.Where(c => c.Color == "blue"))
{
<input type="hidden" name="carnames" value="@car.CarName" />
}
<button class="btn btn-primary" type="submit">Blue Cars</button>

</form>

<!-- yeller cars -->
<form action="/bar/index/yellow" method="post">

@foreach(var car in Model.Where(c => c.Color == "yellow"))
{
<input type="hidden" name="carnames" value="@car.CarName" />
}
<button class="btn btn-primary" type="submit">Yellow Cars</button>

</form>

This creates a form for each color so when you click the submit button, only the cars from that one form are sent in the post. Note that the input name is always the same. That is how you get them wrapped together in a list.

In your controller, use something like this:

[HttpPost, Route("bar/index/{color}")]
public IActionResult Index(string color, List<string> carNames)
{
// do stuff...

return View();
}

The color variable will get picked up from the url and the carNames will be pulled in from the post.

EDIT:
In the comments the added question was essentially "what if I want the car name and the color on the object, so a List<Foo>, to be posted?

Posting a list of complex objects is a bit messier but here is what you need in the view:

<!-- red cars -->
<form action="/stuff/cars/red" method="post">
@{
var cars = Model.Where(c => c.Color == "red").ToList();
for (var i = 0; i < cars.Count; i++)
{
<text>
<input type="hidden" name="cars.Index" value="@i" />
<input type="hidden" name="cars[@i].CarName" value="@cars[i].CarName" />
<input type="hidden" name="cars[@i].Color" value="@cars[i].Color" />
</text>
}
}
<button class="btn btn-primary" type="submit">Red Cars</button>

</form>

Switching to a regular for loop gives us an index variable which we use to tell the form which pair of values belongs together. Notice also that I actually created a temp var cars = ... above it so that I can loop through the smaller list. Now just change the other colors to match this code and change your controller to accept string color, List<Foo> cars and you are all set!

Edit 2:
If you want to do this in ajax instead then create an object in the javascript:

var cars = [
{ 'CarName': 'name', 'Color': 'color' },
{ 'CarName': 'name', 'Color': 'color' },
{ 'CarName': 'name', 'Color': 'color' },
{ 'CarName': 'name', 'Color': 'color' },
{ 'CarName': 'name', 'Color': 'color' }
];

You can populate the actual name and color various ways using jQuery. Then use ajax post:

$(document).ready(function()
{
$('#ajaxPost').click(function ()
{
$.ajax(
{
type: 'POST',
url: '/stuff/cars',
contentType: "application/json",
data: JSON.stringify(cars),
success: function (data)
{
// do stuff
},
error: function (e)
{
alert(e);

}

});
});
});

Note: This will be a different endpoint in the controller from the previous one. Depending on which version of .net you are using things will act slightly differently.

New controller action:

[HttpPost, Route("stuff/cars/")]
public IActionResult Cars2([FromBody] List<FooViewModel> cars)
{
// do stuff...

return View();
}

Notice the [FromBody] tag, that is necessary in .net Core, as is the JSON.stringify in the ajax call. If you aren't sure which version you are on, alternate adding/removing each of those. Core can be a little finicky...

Post Form MVC with List of List

Its fairly simple - you just need to nest 2 for loops in the view (don't use foreach)

ie change to:

@Html.BeginForm(){

@for(int j = 0; j < Model.Items.Count; j++)
{
<p>@Model.Items[j].ItemAId</p>
@Html.HiddenFor(m => m.Items[j].ItemAId)
@* (don't forget this!) *@

for (int i = 0; i < Model.Items[j].ItemBList.Count; i++)
{
@Html.HiddenFor(m => m.Items[j].ItemBList[i].ItemBId )

@Html.TextBoxFor(m => m.Items[j].ItemBList[i].NewInput)

}

}
<button type="submit">Submit</button>
}

The MVC Model Binder requires form fields representing properties in lists to be named something like [#].property1name, [#].property2name in order to properly associate them to each other during binding after postback. Same principle applies to list properties of listitems. You need to use a for loop rather than a foreach loop to get the right form field names when using HTML helpers!

Post list of list of model object to Controller in ASP.NET MVC

Your generating inputs with name attributes that have no relationship to your model, therefore cannot be bound by the DefaultModelBinder. You need to start by generating the data in the controller (not in the view) so that you can bind to your model.

The following assumes you change your SizeColorQuantities property to List<SizeColorQuantityViewModel>

public ActionResult Create()
{
var colors = new List<string>(){ "Red", "Blue" };
var sizes = new List<string>(){ "S", "M", "L", "XL" };

var model = new ProductViewModel()
{
Product = "My product",
SizeColorQuantities = new List<SizeColorQuantityViewModel>
};
foreach(var color in colors)
{
var child = new SizeColorQuantityViewModel()
{
ColorId = color,
SizeAndQuantities = new List<SizeAndQuantity>
};
model.SizeColorQuantities.Add(child);
foreach(var size in sizes)
{
child.SizeAndQuantities.Add(new SizeAndQuantity()
{
SizeId = size // assumes SizeId is changed to string, not int
});
}
}
return View(model);
}

You now have a correctly populated view model that will be passed to the view which you can bind to with strongly typed HtmlHelpers inside nested for loops

@model ProductViewModel
....
@using (Html.BeginForm())
{
....
@for(int i = 0; i < Model.SizeColorQuantities.Count; i++)
{
@Html.TextBoxFor(m => m.SizeColorQuantities[i].ColorId, new { @class = "form-control", @readonly = "readonly" })
for (int j = 0; j < Model.SizeColorQuantities[i].SizeAndQuantities .Count; j++)
{
@Html.TextBoxFor(m => m.SizeColorQuantities[i].SizeAndQuantities[j].SizeId, new { @class = "form-control", @readonly = "readonly" })
@Html.TextBoxFor(m => m.SizeColorQuantities[i].SizeAndQuantities[j].Quantity, new { @class = "form-control" })
}
}
<input type="submit" ... />
}

Note: Always use the strongly typed ***For() HtmlHelper methods and never attempt to set the value (or name) attribute when using a HtmlHelper method.

Your POST method now needs to be

[HttpPost]
public ActionResult Create(ProductViewModel model)
{
....
}

Note you can also use custom EditorTemplate's for your types as explained in HTML Table to ADO.NET DataTable, which also explains how your name attributes must be generated in order to bind to collections. In your case, for example your Quantity inputs need to be (compare that to what your currently generating)

<input name="SizeColorQuantities[0].SizeAndQuantities[0].Quantity" ... />
<input name="SizeColorQuantities[0].SizeAndQuantities[1].Quantity" ... />
....
<input name="SizeColorQuantities[1].SizeAndQuantities[0].Quantity" ... />
<input name="SizeColorQuantities[1].SizeAndQuantities[1].Quantity" ... />
....

MVC Save Page Model's List of Custom Items on Submit to use in Post Method

Firsly,you need know that for each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.

In your code,you have DocumentListing and DocumentListing[i] and DocumentListing[i].DocName in your razor view.But DocumentListing and DocumentListing[i] is invalid that model binding could not look for the corresponding property for them.

Be sure remove invalid inputs and post valid properties to the backend:

@model FileModel
@{int DocumentsCount = Model.DocumentListing.Count(); }
<form asp-action="Create">
@*<input asp-for="DocumentListing" type="hidden">*@ //remove this

@for (int i = 0; i < DocumentsCount; i++)
{
Model.DocumentListing.Add(Model.DocumentListing.ElementAtOrDefault(i));

@*<input asp-for="DocumentListing[i]" type="hidden">*@ //remove this
<input asp-for="DocumentListing[i].DocName" type="hidden">
}
<input type="submit" value="create" />
</form>

Update:

Controller:

[HttpPost]
public IActionResult Create([Bind(Prefix = "DocumentListing")]List<DocumentDTO> model)
{
//...
return View();
}

Or:

[HttpPost]
public IActionResult Create(FileModel model)
{
//...
return View();
}

Check your html in frontend should be like below:

Sample Image

Update2:

Sample Image



Related Topics



Leave a reply



Submit