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

时间:2018-01-26 21:03:42

标签: c# asp.net-mvc model-binding

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

父对象:

第1场

Field 2

儿童对象的集合

子对象:

字段A

B栏

儿童对象的集合

两个对象中的集合可以为空。以下是“父”视图的简化示例:

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

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

我现在完全失去了,所以任何想法都会受到赞赏。

1 个答案:

答案 0 :(得分:0)

经过一系列的反复试验,我为我的问题创建了一个仅限JavaScript的解决方案。父视图仍然使用原始代码,但这是新的子编辑器模板:

&#13;
&#13;
<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>
&#13;
&#13;
&#13;

JavaScript文件中的新功能:

&#13;
&#13;
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;
};
&#13;
&#13;
&#13;

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

&#13;
&#13;
<input type="hidden" name="counter" id="counter" value="0">
&#13;
&#13;
&#13;

它不是那么可重复使用,但我认为它可以很容易地被编辑为更通用,并且有多个&#34; returnContent&#34;函数返回正在使用的编辑器模板的必要内容。

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