具有由 Ajax 和部分视图动态修改的嵌套集合的 CreateModel
CreateModel with nested collection dynamically modified by Ajax and Partial Views
我有一个非常基本的案例,我有一个解决方案,但我认为这种方式不是最干净/高效/便携的。
当然,为了方便阅读,我简化了模型,实际情况要复杂得多。
型号:
public partial class Referal
{
public Referal()
{
Childrens = new List<Children>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Children> Childrens { get; set; }
}
public partial class Children
{
public int Id { get; set; }
public string Firstname { get; set; }
public bool Vaccinated { get; set; }
public int ReferalId { get; set; }
public virtual Referal Referal { get; set; }
}
我认为 ModelBuilder 等的附加代码片段不相关。
所以我有一个带有 Referal 类型 CreateModel 的 Razor 页面,我预加载了一个空的 children 以在页面上包含 Referal 的必填字段,以及第一个 [=] 的必填字段60=]仁:
public class CreateModel : PageModel
{
[BindProperty]
public Referal Input { get; set; }
public async Task OnGetAsync()
{
Input = new() { Childrens = new() { new() } };
}
public async Task<PartialViewResult> OnPostPartialChildrenAsync(int? id)
{
if (id == null)
{
Input.Childrens.Add(new());
}
return Partial("_AddChildren", this);
}
}
创建 Razor 页面(再次简化到严格的相关部分):
@page "{handler?}/{id?}"
@model CreateModel
<section class="extended">
<form method="post" id="souscription">
<div class="form-group">
<label asp-for="Input.Name" class="control-label">
</label><input asp-for="Input.Name" class="form-control" />
</div>
<div id="NestedChildrens">
@Html.EditorFor(Model => Model.Input.Childrens)
</div>
<button type="button" class="cgicon-register intermediary" data-nested="childrens">Add a child</button>
<button type="submit" class="cgicon-send mainbutton">Create Now</button>
</form>
</section>
如您所见,childrens 由 EditorTemplate 加载。
@model Models.Children
<div class="children">
<div class="form-group-header">
Add child
<button type="button" data-nested="childrens" data-removal="@Model.NId"></button>
</div>
<div class="form-group">
<label asp-for="Firstname" class="control-label">
</label><input asp-for="Firstname" class="form-control" />
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="Vaccinated" /> Got his fix already
</label>
</div>
</div>
所以现在的想法是,用户可以点击“添加 child”;数据将在创建页面上使用处理程序“PartialChildren”编辑 post,如果需要,添加 children,return _AddChildren
部分看法。对于 child 添加,没问题:创建了“children”块,然后当 posted 我的 CreateModel
包含两个 children 时,这就是规则。
但用户应该能够在保存数据之前删除其中一个已创建的 child。我在 OnPostPartialChildrenAsync
函数中尝试了很多 server-side 代码来做到这一点,例如 Input.Childrens.removeAt()
和 ModelState.Clear()
,但没有任何效果。
我无法摆脱的典型行为是我删除了一个 child,我的 post 函数删除了我的 createModel 的 child 并将其发回; child 消失了。然后我再次 post (验证器,添加一个新的 child,随便什么),然后删除的 child 再次弹出(因为 posted 一次,仍在 modelState 或 某处).
我试图禁用链接到已删除 children 块的所有字段,但是如果用户删除第一个,因为表单不包含任何 Input.Childrens[0]
表单元素,我的所有childrens 丢失了。
唯一可行的方法是在创建 Razor 页面上使用此 Javascript :
<script>
manageChildren(document);
function manageChildren(childContainer) {
[].forEach.call(childContainer.querySelectorAll("button[data-nested='childrens']"), function (button, index) {
button.addEventListener("click", function () {
let id = "";
if (!$(this).hasClass("cgicon-register")) {
var brotherHood = childContainer.querySelectorAll(".children").length;
if(brotherHood<=1) return false;
[].forEach.call(childContainer.querySelectorAll("[name^='Input.Childrens[" + index+ "]'"), function (input) {
input.setAttribute("disabled", "true");
});
for (i = id + 1; i < brotherHood; i++) {
[].forEach.call(childContainer.querySelectorAll("[name^='Input.Childrens[" + i + "]'"), function (input) {
input.setAttribute("name", input.getAttribute("name").replace("Input.Childrens[" + i + "]", "Input.Childrens[" + (i - 1) + "]"));
});
}
id = index;
}
$.ajax({
async: true,
data: $("#souscription").serialize(),
type: "POST",
url: "/Create/PartialChildren/" + id,
success: function (nestedList) {
$("#NestedChildrens").html(nestedList);
manageChildren(document.getElementById("NestedChildrens"));
}
});
});
});
}
</script>
所以我删除了所有名为 [Input.Childrens[index]*
的字段,然后 我重命名 每个索引较高的字段减去 1.
即使它运作良好,即使没有数据丢失并且用户可以真正安全地使用它,我也不喜欢通过客户端脚本责任来取代输入模型机制。此外,实际上不可能将那段代码移植到其他实体的其他场景中。
我很好奇我是否错过了一种让 C# 在保存之前删除我的 createModel 的特定 child 的神奇方法(所以所有输入 Children 都有一个 id = 0 ), 没有这个 js 客户端处理。
感谢阅读和帮助。
发布表单时有两种绑定集合的方法。一种是您当前正在执行的操作,即使用从 0 开始并为集合中的每个元素递增 1 的顺序索引。
另一种是使用显式索引,它可以是任何不需要顺序的值。它甚至不需要是数字。这种方法需要为每个项目添加一个隐藏字段,名为 [property].Index
,表示项目的索引。
<input type="hidden" name="Input.Children.Index" value="A" />
<input type="text" name="Input.Children[A].FirstName" />
然后,如果您从集合中删除元素,则不需要重新索引剩余的元素。
有关详细信息,请参阅此内容:https://www.learnrazorpages.com/razor-pages/model-binding#binding-complex-collections
我有一个非常基本的案例,我有一个解决方案,但我认为这种方式不是最干净/高效/便携的。
当然,为了方便阅读,我简化了模型,实际情况要复杂得多。
型号:
public partial class Referal
{
public Referal()
{
Childrens = new List<Children>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Children> Childrens { get; set; }
}
public partial class Children
{
public int Id { get; set; }
public string Firstname { get; set; }
public bool Vaccinated { get; set; }
public int ReferalId { get; set; }
public virtual Referal Referal { get; set; }
}
我认为 ModelBuilder 等的附加代码片段不相关。
所以我有一个带有 Referal 类型 CreateModel 的 Razor 页面,我预加载了一个空的 children 以在页面上包含 Referal 的必填字段,以及第一个 [=] 的必填字段60=]仁:
public class CreateModel : PageModel
{
[BindProperty]
public Referal Input { get; set; }
public async Task OnGetAsync()
{
Input = new() { Childrens = new() { new() } };
}
public async Task<PartialViewResult> OnPostPartialChildrenAsync(int? id)
{
if (id == null)
{
Input.Childrens.Add(new());
}
return Partial("_AddChildren", this);
}
}
创建 Razor 页面(再次简化到严格的相关部分):
@page "{handler?}/{id?}"
@model CreateModel
<section class="extended">
<form method="post" id="souscription">
<div class="form-group">
<label asp-for="Input.Name" class="control-label">
</label><input asp-for="Input.Name" class="form-control" />
</div>
<div id="NestedChildrens">
@Html.EditorFor(Model => Model.Input.Childrens)
</div>
<button type="button" class="cgicon-register intermediary" data-nested="childrens">Add a child</button>
<button type="submit" class="cgicon-send mainbutton">Create Now</button>
</form>
</section>
如您所见,childrens 由 EditorTemplate 加载。
@model Models.Children
<div class="children">
<div class="form-group-header">
Add child
<button type="button" data-nested="childrens" data-removal="@Model.NId"></button>
</div>
<div class="form-group">
<label asp-for="Firstname" class="control-label">
</label><input asp-for="Firstname" class="form-control" />
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="Vaccinated" /> Got his fix already
</label>
</div>
</div>
所以现在的想法是,用户可以点击“添加 child”;数据将在创建页面上使用处理程序“PartialChildren”编辑 post,如果需要,添加 children,return _AddChildren
部分看法。对于 child 添加,没问题:创建了“children”块,然后当 posted 我的 CreateModel
包含两个 children 时,这就是规则。
但用户应该能够在保存数据之前删除其中一个已创建的 child。我在 OnPostPartialChildrenAsync
函数中尝试了很多 server-side 代码来做到这一点,例如 Input.Childrens.removeAt()
和 ModelState.Clear()
,但没有任何效果。
我无法摆脱的典型行为是我删除了一个 child,我的 post 函数删除了我的 createModel 的 child 并将其发回; child 消失了。然后我再次 post (验证器,添加一个新的 child,随便什么),然后删除的 child 再次弹出(因为 posted 一次,仍在 modelState 或 某处).
我试图禁用链接到已删除 children 块的所有字段,但是如果用户删除第一个,因为表单不包含任何 Input.Childrens[0]
表单元素,我的所有childrens 丢失了。
唯一可行的方法是在创建 Razor 页面上使用此 Javascript :
<script>
manageChildren(document);
function manageChildren(childContainer) {
[].forEach.call(childContainer.querySelectorAll("button[data-nested='childrens']"), function (button, index) {
button.addEventListener("click", function () {
let id = "";
if (!$(this).hasClass("cgicon-register")) {
var brotherHood = childContainer.querySelectorAll(".children").length;
if(brotherHood<=1) return false;
[].forEach.call(childContainer.querySelectorAll("[name^='Input.Childrens[" + index+ "]'"), function (input) {
input.setAttribute("disabled", "true");
});
for (i = id + 1; i < brotherHood; i++) {
[].forEach.call(childContainer.querySelectorAll("[name^='Input.Childrens[" + i + "]'"), function (input) {
input.setAttribute("name", input.getAttribute("name").replace("Input.Childrens[" + i + "]", "Input.Childrens[" + (i - 1) + "]"));
});
}
id = index;
}
$.ajax({
async: true,
data: $("#souscription").serialize(),
type: "POST",
url: "/Create/PartialChildren/" + id,
success: function (nestedList) {
$("#NestedChildrens").html(nestedList);
manageChildren(document.getElementById("NestedChildrens"));
}
});
});
});
}
</script>
所以我删除了所有名为 [Input.Childrens[index]*
的字段,然后 我重命名 每个索引较高的字段减去 1.
即使它运作良好,即使没有数据丢失并且用户可以真正安全地使用它,我也不喜欢通过客户端脚本责任来取代输入模型机制。此外,实际上不可能将那段代码移植到其他实体的其他场景中。
我很好奇我是否错过了一种让 C# 在保存之前删除我的 createModel 的特定 child 的神奇方法(所以所有输入 Children 都有一个 id = 0 ), 没有这个 js 客户端处理。
感谢阅读和帮助。
发布表单时有两种绑定集合的方法。一种是您当前正在执行的操作,即使用从 0 开始并为集合中的每个元素递增 1 的顺序索引。
另一种是使用显式索引,它可以是任何不需要顺序的值。它甚至不需要是数字。这种方法需要为每个项目添加一个隐藏字段,名为 [property].Index
,表示项目的索引。
<input type="hidden" name="Input.Children.Index" value="A" />
<input type="text" name="Input.Children[A].FirstName" />
然后,如果您从集合中删除元素,则不需要重新索引剩余的元素。
有关详细信息,请参阅此内容:https://www.learnrazorpages.com/razor-pages/model-binding#binding-complex-collections