I am trying to use Steve Sanderson's blog post regarding binding collection items to a model. However, I'm seeing some weird behavior that I can't find an answer for in the post or other discussions.
In my model BankListMaster
, I have an ICollection
object of a separate model BankAgentId
. BankListMaster
and BankListAgentId
have a one-to-many relationship in our SQL database.
I am running into the issue on the Edit
page. When the page loads, the three agent Ids we currently have associated with the BankListMaster
item I'm working with load properly. However, if I hit "Save", I see that the ICollection
object (bankListAgentId
) has a count of three items, but each respective field contains a null
value.
If I select the Add another
, then, following the instructions on the blog post, the Ajax calls a partial view which is appended to the table properly.
Now if I hit "Save", I see that the ICollection
object count has increased by one item to a count of 4. All the items that were originally loaded with the GET
again contain null
field values, but the AgentId
and StateCode
fields for the newly appended item contain the correct information (all other fields for the new item are null
, however).
Still relatively new to ASP MVC, so I'm not sure what is going on or what direction to look.
Here is the form from the main view. I've tried this with and without the @Html.Hidden
items and received the same results.
@model Monet.Models.BankListMaster
@{
ViewBag.Title = "Edit";
}
<fieldset>
<legend>Stat(s) Fixed</legend>
<table id="fixedRows">
<thead>
<tr>
<th>State Code</th>
<th>Agent ID</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@for (int i = 0; i < Model.Fixed.Count; i++)
{
using (Html.BeginCollectionItem("BankListAgentId"))
{
@Html.HiddenFor(m => Model.Fixed[i].BankID)
@Html.HiddenFor(m => Model.Fixed[i].TableId)
@Html.HiddenFor(m => Model.Fixed[i].FixedOrVariable)
<tr>
<td>
@Html.DropDownListFor(m => Model.Fixed[i].StateCode,
(SelectList)ViewBag.StateCodeList, Model.Fixed[i].StateCode)
</td>
<td>
@Html.TextBoxFor(m => Model.Fixed[i].AgentId)
@Html.ValidationMessageFor(m => Model.Fixed[i].AgentId)
</td>
<td>
<a href="javascript:void(0)" class="deleteRow">delete</a>
</td>
@*<td><a href="#" onclick="$('#[email protected]').parent().remove();" style="float:right;">Delete</a></td>*@
</tr>
}
}
</tbody>
</table>
<br />
<a href="javascript:void(0)" class="addFixed">Add Another</a>
</fieldset>
Here is the partial view. Again, I've tried with and without the @Html.Hidden
items and received the same result.
@model Monet.Models.BankListAgentId
@{
Layout = null;
}
@using (Html.BeginCollectionItem("BankListAgentId"))
{
@Html.HiddenFor(model => model.TableId)
@Html.HiddenFor(model => model.BankID)
@Html.HiddenFor(model => model.FixedOrVariable)
<tr>
<td>
@Html.DropDownListFor(model => model.StateCode,
(SelectList)ViewBag.StateCodeList, Model.StateCode)
</td>
<td>
@Html.EditorFor(model => model.AgentId)
@Html.ValidationMessageFor(model => model.AgentId)
</td>
<td>
<a href="javascript:void(0)" class="deleteRow">delete</a>
</td>
@*<td><a href="#" onclick="$('#[email protected]').parent().remove();" style="float:right;">Delete</a></td>*@
</tr>
}
Here is the Ajax call
$(document).ready(function () {
$(".addFixed").click(function () {
$.ajax({
url: '@Url.Action("BlankFixedRow", "BankListMaster")',
dataType: 'html',
cache: false,
success: function (html) {
$("#fixedRows > tbody").append('<tr>' + html + '</tr>');
}
});
});
});
Here is the controller method that calls the partial view
public ViewResult BlankFixedRow()
{
SelectList tmpList = new SelectList(new[] { "AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NA", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR", "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "US", "VT", "VI", "VA", "WA", "WV", "WI", "WY" });
ViewBag.StateCodeList = tmpList;
return View("FixedPartialView", new BankListAgentId());
}
This is the BankListMaster
model
public partial class BankListMaster
{
public BankListMaster()
{
this.BankListAttachments = new HashSet<BankListAttachments>();
this.BankListAgentId = new HashSet<BankListAgentId>();
}
public int ID { get; set; }
public string BankName { get; set; }
public string LastChangeOperator { get; set; }
public Nullable<System.DateTime> LastChangeDate { get; set; }
public virtual ICollection<BankListAttachments> BankListAttachments { get; set; }
public virtual ICollection<BankListAgentId> BankListAgentId { get; set; }
}
And this is the BankListAgentId
model
public partial class BankListAgentId
{
public string AgentId { get; set; }
public int BankID { get; set; }
public string FixedOrVariable { get; set; }
public string StateCode { get; set; }
public int TableId { get; set; }
public virtual BankListMaster BankListMaster { get; set; }
}
Here is the what is sent from the form on the post back to the controller via fiddler. The items that are indexed 1, 2, and 3 are the items originally pulled from the database. The last item was added via the jQuery/Ajax call to the partial view.
My solution was: everything used in the html.beginCollectionItem has to be in a partial view. So your solution could be something like this:
Main view
@model Monet.Models.BankListMaster
@{
ViewBag.Title = "Edit";
}
<fieldset>
<legend>Stat(s) Fixed</legend>
<table id="fixedRows">
<thead>
<tr>
<th>State Code</th>
<th>Agent ID</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@for (int i = 0; i < Model.Fixed.Count; i++)
{
@Html.Partial("name", item)
}
</tbody>
</table>
<br />
<a href="javascript:void(0)" class="addFixed">Add Another</a>
</fieldset>
Partial view "name"
@model Monet.Models.BankListMaster
using (Html.BeginCollectionItem("BankListAgentId"))
{
@Html.HiddenFor(m => Model.Fixed[i].BankID)
@Html.HiddenFor(m => Model.Fixed[i].TableId)
@Html.HiddenFor(m => Model.Fixed[i].FixedOrVariable)
<tr>
<td>
@Html.DropDownListFor(m => Model.Fixed[i].StateCode,
(SelectList)ViewBag.StateCodeList, Model.Fixed[i].StateCode)
</td>
<td>
@Html.TextBoxFor(m => Model.Fixed[i].AgentId)
@Html.ValidationMessageFor(m => Model.Fixed[i].AgentId)
</td>
<td>
<a href="javascript:void(0)" class="deleteRow">delete</a>
</td>
@*<td><a href="#" onclick="$('#[email protected]').parent().remove();" style="float:right;">Delete</a></td>*@
</tr>
}
It's not the complete solution, but you have to do it like this, it worked for me. Hope this helps out!
I got stuck on a similar issue for several hours. My items collection (Choices) would return null when it'd contain more than one item. However, just like you, my data seemed to be rendered just fine:
{
"QuestionTemplateId":"1",
"Position":"0",
"CardId":"1",
"Label":"Question#1",
"AdminComments":"",
"Type":"ComboBox",
"ModelType":"MyProject.Areas.DGM.Models.ViewModels.Controls.ComboBoxViewModel",
"ComboQuestionId":"1",
"Choices.Index": ["dc0e6eea-5a8e-4971-8f9f-4d6e1c290300","52f2b780-c21e-4633-b880-bdff5d815eaf"],
"Choices[dc0e6eea-5a8e-4971-8f9f-4d6e1c290300].Label":"Choice #1",
"Choices[dc0e6eea-5a8e-4971-8f9f-4d6e1c290300].Score":"4",
"Choices[52f2b780-c21e-4633-b880-bdff5d815eaf].Label":"Choice #2",
"Choices[52f2b780-c21e-4633-b880-bdff5d815eaf].Score":"7"
}
I then realized that my issue may be connected to the data POST. When the user submits the form, I'd make the following AJAX
call:
$("#questionForm").on('submit', function () {
if ($(this).valid()) {
var data = $(this).serializeObject(); // from here: http://stackoverflow.com/a/1186309/2835243
$.ajax({
type: 'POST',
url: this.action,
contentType: 'application/json',
data: JSON.stringify(data) // this is what generated the above JSON
});
}
}
What seemed to fix it for me is to send data as x-www-form-urlencoded
(the default contentType
for jQuery's AJAX) instead of json
. So I changed my AJAX call for the following:
$.ajax({
type: 'POST',
url: this.action,
data: $(this).serialize()
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With