通过Ajax和Partial Views动态修改嵌套集合的CreateModel



我有一个非常基本的案例,我有一种解决方案,但我认为这种方式不是最干净/高效/可移植的。

当然,为了便于阅读,我简化了模型,实际情况要复杂得多。

型号:

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和类似程序的附加代码是不相关的。

因此,我有一个Razor页面,它的CreateModel类型为Refereral,我预加载了一个空的子级,以便在页面上有Refereral所需的字段,以及第一个子级所需的域:

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);
}
}

创建剃刀页面(再次简化为严格相关的部分(:

@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>

正如您所看到的,子级是由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>

所以现在的想法是,用户可以点击";添加一个子";;数据将用一个处理程序"发布在创建页面上;PartialChildren";,如果需要,添加一个子项,并返回_AddChildren部分视图。对于子添加,没有问题:;儿童";块被创建,然后当发布时,我的CreateModel包含两个子,这就是规则。

但是,在保存数据之前,用户应该能够删除其中一个创建的子项。我在OnPostPartialChildrenAsync函数中尝试了很多服务器端代码来实现这一点,比如Input.Childrens.removeAt()ModelState.Clear(),但都没有成功。

我无法摆脱的典型行为是,我删除了一个孩子,我的post函数删除了我的createModel的孩子并将其发回;孩子不见了。然后我再次发布(验证器,添加一个新的子项,不管怎样(,删除的子项再次弹出(因为发布了一次,仍然在modelState或的某个地方(。

我试图禁用链接到已删除子块的所有字段,但如果用户删除第一个字段,因为表单不包含任何Input.Childrens[0]表单元素,我的所有子块都将丢失。

唯一有效的想法是在创建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的特定子级(所以所有的Input 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-复杂集合

最新更新