I'm new to web app development, and I wonder if somebody could point me in the right direction with this? I think I know what the problem is, but I don't know how to fix it.
What I have is a complex model where a header can contain a child, and each child can itself have its own children (of different types).
My demo code shown here has a header, and two lists. If I click the button to add a child item to either the first list or a second list, the initial problem I have is that the items don't appear. The other problem is that I don't appear to be able to keep on adding items to either list.
To try to see what is going on, I added a section to show the innerHTML to see what the data is - and this illustrates that if I click to add to the first list, some data appears for the first list, but if I click to add to the second list, the new data disappears from the first list whilst the new item appears as expected in the second list.
I think the problem is that the Model isn't updated to reflect the new items - but I understand that we shouldn't be able to do that with Javascript anyway. So, I think I need to separate all my partial views, so I have a main partial view containing lots of partial views, and when my Ajax call is successful, refresh the MAIN partial view.
My PageModel
namespace ParentChildDemo.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
[BindProperty]
public Header MyHeader { get; set; } = new Header();
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
MyHeader.Id = 1;
MyHeader.MyHeaderProperty = "HeaderTest1";
MyHeader.ChildOfHeader.Add(new ChildOfHeader());
for (int i = 1; i <= 3; i++)
{
var childOfChild = new ChildOfChild()
{
Id = i,
HeaderId = MyHeader.Id,
MyChildProperty = $"FirstChildTest{i}"
};
MyHeader.ChildOfHeader[0].MyFirstChildList.Add(childOfChild);
}
for (int i = 1; i <= 2; i++)
{
var childOfChild = new ChildOfChild()
{
Id = i,
HeaderId = MyHeader.Id,
MyChildProperty = $"SecondChildTest{i}"
};
MyHeader.ChildOfHeader[0].MySecondChildList.Add(childOfChild);
}
}
public PartialViewResult OnPostAddNewFirstListChildItem([FromBody] Header myHeader)
{
if (myHeader.ChildOfHeader[0].MyFirstChildList == null)
myHeader.ChildOfHeader[0].MyFirstChildList = new List<ChildOfChild>();
var childId = myHeader.ChildOfHeader[0].MyFirstChildList.Count + 1;
myHeader.ChildOfHeader[0].MyFirstChildList.Add(new ChildOfChild
{
Id = childId,
HeaderId = myHeader.Id,
MyChildProperty = $"FirstChildTest{childId}"
});
var partialView = "_ListPartialView";
var data = new IndexModel(_logger);
data.MyHeader = myHeader;
var myViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { { partialView, myHeader } };
myViewData.Model = data;
var partialViewResult = new PartialViewResult()
{
ViewName = partialView,
ViewData = myViewData,
};
return partialViewResult;
}
public PartialViewResult OnPostAddNewSecondListChildItem([FromBody] Header myHeader)
{
if (myHeader.ChildOfHeader[0].MySecondChildList == null)
myHeader.ChildOfHeader[0].MySecondChildList = new List<ChildOfChild>();
var childId = myHeader.ChildOfHeader[0].MySecondChildList.Count + 1;
myHeader.ChildOfHeader[0].MySecondChildList.Add(new ChildOfChild
{
Id = childId,
HeaderId = myHeader.Id,
MyChildProperty = $"SecondChildTest{childId}"
});
var partialView = "_ListPartialView";
var data = new IndexModel(_logger);
data.MyHeader = myHeader;
var myViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { { partialView, myHeader } };
myViewData.Model = data;
var partialViewResult = new PartialViewResult()
{
ViewName = partialView,
ViewData = myViewData,
};
return partialViewResult;
}
}
public class Header
{
public int Id { get; set; }
public string MyHeaderProperty { get; set; }
public List<ChildOfHeader> ChildOfHeader { get; set; } = new List<ChildOfHeader>();
}
public class ChildOfHeader
{
public List<ChildOfChild> MyFirstChildList { get; set; } = new List<ChildOfChild>();
public List<ChildOfChild> MySecondChildList { get; set; } = new List<ChildOfChild>();
}
public class ChildOfChild
{
public int Id { get; set; }
public int HeaderId { get; set; }
public string MyChildProperty { get; set; }
}
}
My Index page
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div>
<a class="btn btn-sm btn-info text-white" onclick="AddListItem(1)">Add Child to First list</a>
<a class="btn btn-sm btn-info text-white" onclick="AddListItem(2)">Add Child to Second list</a>
</div>
<br />
<div>
<div>MyHeaderProperty value: @Model.MyHeader.MyHeaderProperty</div>
<br />
<div class="container">
<div class="row">
<div class="col-6 font-weight-bold">First List</div>
<div class="col-6 font-weight-bold">Second List</div>
</div>
<div class="ListPartialView">
<partial name="_ListPartialView" model="@Model" />
</div>
</div>
<br />
<div class="bg-warning" id="HtmlContent"></div>
@Html.AntiForgeryToken()
</div>
<script type="text/javascript">
function AddListItem(listNumber) {
var model = @Json.Serialize(Model.MyHeader);
var handler;
var partialView;
if (listNumber == 1) {
handler = "?handler=AddNewFirstListChildItem";
partialView = "#ListPartialView";
}
else {
handler = "?handler=AddNewSecondListChildItem";
partialView = "#ListPartialView";
}
$.ajax({
type: "POST",
url: handler,
data: JSON.stringify(model),
dataType: "html",
contentType: "application/json",
headers: {
RequestVerificationToken: $('input:hidden[name="__RequestVerificationToken"]').val()
},
success: function (result) {
document.getElementById("HtmlContent").innerHTML = result.toString();
$(partialView).html(result);
},
failure: function (result) {
alert("Failed");
}
});
}
</script>
My Main partial view (_ListPartialView)
@model IndexModel
@for (int i = 0; i < Model.MyHeader.ChildOfHeader.Count; i++)
{
ViewData["ChildIndex"] = i;
<div class="row">
<div id="FirstListPartial" class="col-6">
<partial name="_FirstListItemPartial" model="@Model" view-data="ViewData" />
</div>
<div id="SecondListPartial" class="col-6">
<partial name="_SecondListItemPartial" model="@Model" view-data="ViewData" />
</div>
</div>
}
My My first list partial view (_FirstListItemPartial)
@model IndexModel
@{
var indexId = (int)ViewData["ChildIndex"];
}
<table>
@foreach (var myChildItem in Model.MyHeader.ChildOfHeader[indexId].MyFirstChildList)
{
<tr>
<td>@myChildItem.Id</td>
<td>@myChildItem.MyChildProperty</td>
</tr>
}
</table>
My second list partial view (_SecondListItemPartial)
@model IndexModel
@{
var indexId = (int)ViewData["ChildIndex"];
}
<table>
@foreach (var myChildItem in Model.MyHeader.ChildOfHeader[indexId].MySecondChildList)
{
<tr>
<td>@myChildItem.Id</td>
<td>@myChildItem.MyChildProperty</td>
</tr>
}
</table>
What I have tried:
In my < script > I think I need to refer to my page, and not my Model every time one of the buttons is pressed. I have tried to serialize my partial view (ListPartialView), and I have changed my parameter type to 'dynamic' and removed the [FromBody], but my object content is is always {object} - not the page data (from what I can make out).