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:
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:
Update2:
Related Topics
Appsettings Get Value from .Config File
Is Async Await Keyword Equivalent to a Continuewith Lambda
What Is the Real Overhead of Try/Catch in C#
Built in .Net Algorithm to Round Value to the Nearest 10 Interval
Convert Array of Bytes to Bitmapimage
How to Export a Jqgrid Data to Excel Using C#
Zoom and Translate an Image from the Mouse Location
Mirroring Console Output to a File
How to Update Values into Appsetting.JSON
How to Programmatically Modify Wcf App.Config Endpoint Address Setting
Failed to Serialize the Response in Web API
Windows Shell Extension with C#
Observablecollection<> VS. List<>
.Net Core 2.2 Can't Be Selected in Visual Studio Build Framework
How to Split a String by Strings and Include the Delimiters Using .Net