MVC 嵌套集合模型,其中子项具有自己类型的子项

MVC Nested Collection Models Where Child has Child of Its Own Type

我正在处理一个非常复杂的模型和绑定。到目前为止,我已经能够使用代码设置 here 来使事情正常进行。但是,其中一个嵌套模型可以将自身作为嵌套模型。当我在编辑器模板中放置相同类型的设置时,它会创建一个无限循环并导致堆栈溢出错误。由于隐私问题,我无法详细介绍我的实际代码,但我的模型设置如下:

父对象:

字段 1

字段 2

子对象集合

子对象:

字段 A

字段 B

子对象集合

两个对象中的集合都可以为空。这是 "Parent" 视图的简化示例:

@using (Html.BeginForm("LetterEntry", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

        <div class="editor-label">
            @Html.LabelFor(model => model.Field1)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Field1)
            @Html.ValidationMessageFor(model => model.Field1)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Field2)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Field2)
            @Html.ValidationMessageFor(model => model.Field2)
        </div>

        <div>
            <div class="editor-label" id="ChildFieldsLabel">
                @Html.Label("Has child fields?")
            </div>
            <div class="editor-field" id="ChildFieldsField">
                @Html.CheckBox("ChildFieldsCheckBox", new { @onclick = "ChildFieldsChecked(this)" })
            </div>
            <div class="display-none" id="outer-ChildFieldsDiv">
                <div id="ChildFieldsDiv">
                    @* individual child field entries go here *@
                </div>
                @Html.AddLink("Add Child Field", "#ChildFieldsDiv", ".child-field-single-div", "ChildFields", typeof(Project.Datalayer.ChildFields))
            </div>
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
}

以及子编辑器模板:

<div class="required-field-single-div">
    <div class="editor-label">
        @Html.LabelFor(model => model.FieldA)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FieldA)
        @Html.ValidationMessageFor(model => model.FieldA)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.FieldB)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FieldB)
        @Html.ValidationMessageFor(model => model.FieldB)
    </div>

    <div>
        <div class="editor-label" id="ChildFieldsLabel">
            @Html.Label("Has sub-requirements?")
        </div>
        <div class="editor-field-noborder" id="ChildFieldsField">
            @Html.CheckBox("Sub-ChildFieldsCheckBox", new { @onclick = "Sub-ChildFieldsChecked(this)" })
        </div>
        <div class="display-none" id="outer-Sub-ChildFieldsDiv">
            <div id="Sub-ChildFieldsDiv">
                @* individual child entries go here *@
            </div>
            @Html.AddLink("Add Sub-Child Field", "#Sub-ChildFieldsDiv", ".child-field-single-div", "ChildFields.ChildFields", typeof(Project.Datalayer.ChildFields))
        </div>
    </div>
    
</div>

我没有包括复选框的 Javascript,因为它所做的只是显示隐藏的 div。一切正常,直到我在子编辑器模板中添加 AddLink 行。

确切的错误是:{无法计算表达式,因为当前线程处于堆栈溢出状态。}

我已经检查了 AddLink 代码,并确定错误发生在此处的 "Editor For(nestedObject)" 部分:

public static IHtmlString AddLink<TModel>(this HtmlHelper<TModel> htmlHelper, string linkText, string containerElement, string counterElement, string collectionProperty, Type nestedType)
        {
            var ticks = DateTime.UtcNow.Ticks;
            var nestedObject = Activator.CreateInstance(nestedType);
            var partial = htmlHelper.EditorFor(x => nestedObject).ToHtmlString().JsEncode();
            partial = partial.Replace("id=\\"nestedObject", "id=\\"" + collectionProperty + "_" + ticks + "_");
            partial = partial.Replace("name=\\"nestedObject", "name=\\"" + collectionProperty + "[" + ticks + "]");
            var js = string.Format("javascript:addNestedForm('{0}','{1}','{2}','{3}');return false;", containerElement, counterElement, ticks, partial);
            TagBuilder tb = new TagBuilder("a");
            tb.Attributes.Add("href", "#");
            tb.Attributes.Add("onclick", js);
            tb.InnerHtml = linkText;
            var tag = tb.ToString(TagRenderMode.Normal);
            return MvcHtmlString.Create(tag);
        }

我进行了大量搜索以尝试找到答案,但随着您深入了解,我只能找到有关具有更多列表的嵌套模型的信息。我还没有发现任何有关模型中的子对象何时可以拥有自己类型的子对象的信息。

此时我完全不知所措,所以任何想法都将不胜感激。

所以在反复试验之后,我创建了一个 JavaScript-only 解决方案来解决我的问题。父视图仍然使用原始代码,但这是新的子编辑器模板:

<div class="required-field-single-div">
    <div class="editor-label">
        @Html.LabelFor(model => model.FieldA)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FieldA)
        @Html.ValidationMessageFor(model => model.FieldA)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.FieldB)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FieldB)
        @Html.ValidationMessageFor(model => model.FieldB)
    </div>

    <div class="single-field">
        <div id="outer-SubChildFieldsDiv">
            <div id="SubChildFieldsDiv">
                @* individual entries go here*@
            </div>
            @{ string jsText = "addNestedForm2('#SubChildFieldsDiv','.sub-child-field-single-div','ChildFields[0].ChildFields1')"; }
            <a href="#" onclick=@jsText>Add Sub-Child Field</a>
        </div>
    </div>
    
</div>

以及 JavaScript 文件中的新函数:

function addNestedForm2(container, counter, collectionProperty) {
    var nextIndex = $(counter).length;
    var nestPattern = new RegExp("nestedObject", "gi");
    var countPattern = new RegExp("ReplaceMeWithCount", "gi");
    var counterPattern = new RegExp("counterClass", "gi");
    var incrementedIdPattern = new RegExp("incrementedId", "gi");
    var counterObj = document.getElementById("counter");
    var incrementedId = Number.parseInt(counterObj.value);
    var counterClass = counter.substring(1);
    var content = ReturnContent().replace(nestPattern, collectionProperty).replace(countPattern, nextIndex)
                                 .replace(counterPattern, counterClass).replace(incrementedIdPattern, incrementedId);
    counterObj.value = incrementedId + 1;
    $(container).append(content);
};

function ReturnContent() {
    var content = "<div class=\"child-field-single-div counterClass\">" +
    "<div class=\"editor-label-noborder\">        " +
    "<label for=\"nestedObject_FieldA\">FieldA</label>    " +
    "</div>    " +
    "<div class=\"editor-field-noborder\">        " +
    "<input class=\"check-box\" id=\"nestedObject_ReplaceMeWithCount__FieldA\" name=\"nestedObject[ReplaceMeWithCount].FieldA\" type=\"checkbox\" value=\"true\" />" +
    "<input name=\"nestedObject[ReplaceMeWithCount].FieldA\" type=\"hidden\" value=\"false\" />        " +
    "<span class=\"field-validation-valid\" data-valmsg-for=\"nestedObject.FieldA\" data-valmsg-replace=\"true\"></span>    " +
    "</div>    " +
    "<div class=\"editor-label-noborder\">        " +
    "<label for=\"nestedObject_FieldB\">FieldB</label>    " +
    "</div>    " +
    "<div class=\"editor-field-noborder\">        " +
    "<input class=\"text-box single-line\" id=\"nestedObject_ReplaceMeWithCount__FieldB\" name=\"nestedObject[ReplaceMeWithCount].FieldB\" type=\"text\" value=\"\" />        " +
    "<span class=\"field-validation-valid\" data-valmsg-for=\"nestedObject.FieldB\" data-valmsg-replace=\"true\"></span>    " +
    "</div>    " +        
    "</div>" +
    "<div id=\"outer-SubChildFieldsDiv\">      " +      
    "<div id=\"SubChildFieldsDiv_incrementedId\">               " +
    "</div\>            " +
    "<a href=\"#\" onclick=addNestedForm2('#SubChildFieldsDiv_incrementedId','.child-field-single-div_incrementedId','nestedObject[ReplaceMeWithCount].ChildFields1')\>Add Sub-Child Field</a\>                        " +
    "</div\>    ";

    return content;
};

ReturnContent 函数中的代码是从 chrome 中的开发人员工具中提取的,是生成内容的编辑版本,但它与子编辑器模板基本相同。代码中的 incrementedId 对象用于确保每个新级别都被唯一标识,并且将 link 到正确的父对象。我还在父视图的开头添加了一个隐藏的输入字段:

<input type="hidden" name="counter" id="counter" value="0">

它不是很容易重用,但我认为它可以很容易地被编辑成更通用的并且有多个 "returnContent" 函数来 return 任何正在使用的编辑器模板的必要内容.

如果其他人遇到与 AddLink 代码相同的问题,希望这会有所帮助。