Mvc Form Not Able to Post List of Objects

MVC Form not able to post List of objects

Your model is null because the way you're supplying the inputs to your form means the model binder has no way to distinguish between the elements. Right now, this code:

@foreach (var planVM in Model)
{
@Html.Partial("_partialView", planVM)
}

is not supplying any kind of index to those items. So it would repeatedly generate HTML output like this:

<input type="hidden" name="yourmodelprefix.PlanID" />
<input type="hidden" name="yourmodelprefix.CurrentPlan" />
<input type="checkbox" name="yourmodelprefix.ShouldCompare" />

However, as you're wanting to bind to a collection, you need your form elements to be named with an index, such as:

<input type="hidden" name="yourmodelprefix[0].PlanID" />
<input type="hidden" name="yourmodelprefix[0].CurrentPlan" />
<input type="checkbox" name="yourmodelprefix[0].ShouldCompare" />
<input type="hidden" name="yourmodelprefix[1].PlanID" />
<input type="hidden" name="yourmodelprefix[1].CurrentPlan" />
<input type="checkbox" name="yourmodelprefix[1].ShouldCompare" />

That index is what enables the model binder to associate the separate pieces of data, allowing it to construct the correct model. So here's what I'd suggest you do to fix it. Rather than looping over your collection, using a partial view, leverage the power of templates instead. Here's the steps you'd need to follow:

  1. Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates).
  2. Create a strongly-typed view in that directory with the name that matches your model. In your case that would be PlanCompareViewModel.cshtml.

Now, everything you have in your partial view wants to go in that template:

@model PlanCompareViewModel
<div>
@Html.HiddenFor(p => p.PlanID)
@Html.HiddenFor(p => p.CurrentPlan)
@Html.CheckBoxFor(p => p.ShouldCompare)
<input type="submit" value="Compare"/>
</div>

Finally, your parent view is simplified to this:

@model IEnumerable<PlanCompareViewModel>
@using (Html.BeginForm("ComparePlans", "Plans", FormMethod.Post, new { id = "compareForm" }))
{
<div>
@Html.EditorForModel()
</div>
}

DisplayTemplates and EditorTemplates are smart enough to know when they are handling collections. That means they will automatically generate the correct names, including indices, for your form elements so that you can correctly model bind to a collection.

Unable to Post List of Objects from View to Controller

Turns out it required me to pass through the DocumentDescription tried different constructors on the view model but it always failed binding when there was no Description passed to the controller.

<form asp-action="UploadDocuments" enctype="multipart/form-data">
<input asp-for="EncryptedApplicationId" type="hidden" />
@for (int i = 0; i < Model.Documents.Count; i++)
{
<div class="form-group mt-20">
<input asp-for="Documents[i].DocumentDescription" type="hidden" />
<label class="control-label">@Model.Documents[i].DocumentDescription</label>
<input class="form-control" asp-for="Documents[i].DocumentFile" />
</div>
}
<div class="form-group mt-20">
<a asp-action="Exit" asp-route-applicationId="@Model.EncryptedApplicationId" class="btn btn-info btn-primary btn-rounded text-left" style="position:relative;left:-1em;">Exit</a>
<button type="submit" class="btn btn-info btn-success btn-rounded text-right" style="position:relative;right:-8em;">Continue <i class="ti ti-angle-double-right" style="font-weight:bold;top:1px;right:-5px;position: relative;"></i></button>
</div>
</form>

Ajax.BeginForm Post not able to bind a list of objects within a PartialView

MVC is very particular about its model binding with lists. It uses the variable names passed in the lambda expression to set as the name attribute on each form element, and then tries to match those against the model when passed back to the controller. If you inspect each element, you'll probably see name="item.Select", name="item.ColumnOne", and name="item.ColumnTwo" for every item on the list. This means the controller can't disinguish between them, so no binding is done.

The fix: use a for loop instead of a foreach loop.

@for (var i = 0; i < Model.SearchResultList.Count; i++) // might need to be .Length or .Count() depending on the type of SearchResultList
{
<tr>
<td>
@Html.CheckBoxFor(modelItem => Model.SearchResultList[i].Select)
</td>
<td>
@Html.DisplayFor(modelItem => Model.SearchResultList[i].ColumnOne)
@Html.HiddenFor(modelItem => Model.SearchResultList[i].ColumnOne)
</td>
<td>
@Html.DisplayFor(modelItem => Model.SearchResultList[i].ColumnTwo)
@Html.HiddenFor(modelItem => Model.SearchResultList[i].ColumnTwo)
</td>
<!-- etc. ... -->
</tr>
}

This allows the controller to correctly model bind on POST.

List of objects not binding to the model on post back in asp.net mvc 4

You current implementation is rendering inputs that look like:

<input ... name="[0].Name" .../>
<input ... name="[1].Name" .../>

but in order to bind to to you model they would need to look like this:

<input ... name="SitePackges[0].LstPackageDisplayItems[0].Name" .../>
<input ... name="SitePackges[0].LstPackageDisplayItems[1].Name" .../>
<input ... name="SitePackges[1].LstPackageDisplayItems[0].Name" .../>
<input ... name="SitePackges[1].LstPackageDisplayItems[1].Name" .../>

A: You either need to render the controls in nested for loops

for(int i = 0; i < Model.SitePackges.Count; i++)
{
@Html.HiddenFor(m => m.SitePackges[i].SiteId)
for(int j = 0; j < Model.SitePackges[i].LstPackageDisplayItems.Count; j++)
{
@Html.TextBoxFor(m => m.SitePackges[i].LstPackageDisplayItems[j].Name)
}
}

B: or use custom EditorTemplates for your model types

Views/Shared/EditorTemplates/SitePackage.cshtml

@model SitePackage
@Html.HiddenFor(m => m.SiteId)
@Html.EditorFor(m => m.LstPackageDisplayItems)

Views/Shared/EditorTemplates/PackageDisplayItem.cshtml

@model PackageDisplayItem
@Html.TextBoxFor(m => m.Name)

and in the main view

@model SubscriptionModel
@using (@Html.BeginForm())
{
@Html.HiddenFor(m => m.MemberId)
@Html.EditorFor(m => m.SitePackges)
<input type="submit" />
}

Bind List of objects to a form

Use the for loop to generate the elements, as it would add indexing to the property names which is used by model binder to bind to List on the post which does not work with the foreach:

@for (int i=0; i< Model.Signatures.Count; i++)
{
<div class="text-center">
<label asp-for="@Model.Signatures[i].Title"></label>
<input asp-for="@Model.Signatures[i].Title" />
<label asp-for="@Model.Signatures[i].Email"></label>
<input asp-for="@Model.Signatures[i].Email" />
<label asp-for="@Model.Signatures[i].Date"></label>
<input disabled asp-for="@Model.Signatures[i].Date">
</div>
}

Now the elements would be rendered with names like Signatures[0].Title, Signatures[1].Title and the model binder can bind it to the model on post.

Hope it helps.

List T within the model not reaching controller when form is submitted

Cause:

No variable named "Items" is being sent to the server since the form does not include an input named "Items". Instead an array named "item.IsSelected" is being sent by the checkbox inputs.

In your view the following line: (showing the important part)

<input asp-for="@item.IsSelected" 

is scaffolding a HTML input field that looks like this: (showing the important part)

<input type="checkbox" name="item.IsSelected" value="true">

With Google Chrome, press F12 before you submit the form. Look at the Network tab > Headers > Form data, you can confirm that it submitted the following field for each checked item.

item.IsSelected:true

So the controller is not receiving an input named "Items". it is receiving a string array called "item.IsSelected" with the default value of "true".

Working towards a solution:

You can override the default name by providing the optional 'name' parameter to the input tag.

<input asp-for="@item.IsSelected" name="Items" 

You are also setting the Id attribute. In this context, the Id is used for HTML and is not submitted with the form. Only the value of the checkbox is submitted.

id="@item.ItemId" /> 

If you want to submit the checked IDs back as an array, set it as the value, not the id.

value="@item.ItemId" />            

If you prefer to use the ItemName as the value:

value="@item.ItemName" />

However the HTML checkbox input will not allow you to pass both ItemName and Id vales as it only passes a single value for selected items.

The MVC controller will not be able to map the string[] called "Items" to your SectionItemCollector class as the types do not match. It may detect the count of Items in the array, but the values will remain null.

With that limitation, it may be best to serialize the form data into a JSON object and post via AJAX.

Solving it with a Form Post

To solve with a Form Post, you will need to make a few changes to bind and process the form data:

Add the following properties to your class SectionVM:

public string[] CheckedItems { get; set; }

public string[] CheckedValues { get; set; }

Update Create.cshtml to pass the isChecked and ID as part of the checkbox value. Add a hidden input to pass the ItemName.

<div class="form-group">
@{
foreach (var item in Model.Items)
{
<div class="row">
<div class="col-sm-1">
@* If checked, the value will be posted as the CheckedItems string[] *@
<input asp-for="@item.IsSelected" name="CheckedItems" type="checkbox" class="form-check-input" value="@item.ItemId" />

@* posting a hidden value as the CheckedValues string[] *@
<input name="CheckedValues" type="hidden" value="@item.ItemName" />
</div>
<div class="col-sm-4">@item.ItemName</div>
</div>
}
}

</div>

Finally, update your controller action to collect the checkbox and hidden values and re-combine them into the expected object:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Description, Items, CheckedItems, CheckedValues")] SectionVM menuSection)
{
// convert CheckedItems and CheckedValues into List<SectionItemCollector>
var selectedItems = new List<SectionItemCollector>();
for (int i = 0; i < menuSection.CheckedItems.Length; i++)
{
// if the checked ID is number, find the related ItemName.
long id;
if (long.TryParse(menuSection.CheckedItems[i], out id))
{
var item = new SectionItemCollector
{
ItemId = id,
// inferred as HTML only posts checkboxes that are selected.
IsSelected = true,
// get the ItemName with the same index.
ItemName = menuSection.CheckedValues[i]
};
selectedItems.Add(item);
}
}
var viewmodel = new SectionVM()
{
Items = selectedItems
};

// TODO: Set the breakpoint to the next line and inspect viewmodel to see your result.
return View(viewmodel);
}

Maybe Asp.Net has another way to abstract away the complexity of submitting a checkbox with multiple properties. If so, I hope someone submits that answer too.

MVC Controller List in model not binding

Like @Dabbas said, change the ajax method to POST for sending big data (max url length is 255 characters), and don't use JSON to bind data, it's not necessary.

Try that, if didn't work, i'll remove :

var obj = {
"header":"Order type",
"enumLocalizations":[
{
"key":1,
"value":"Customer order"
},
{
"key":2,
"value":"Webshop"
},
{
"key":4,
"value":"Service order"
}
]
};

let ajaxSettings = {
type: 'POST',
xhrFields: { responseType: 'blob' },
data: obj,
success: (data) => {
... on success
}
}
};


Related Topics



Leave a reply



Submit