简体   繁体   English

绑定 Model 未按预期返回

[英]Binding Model not returning as expected

Project is an ASP.NET Core 6 webapp.项目是一个 ASP.NET Core 6 webapp。 I have a property that has decorated with [BindProperty] with some object arrays inside of it.我有一个用[BindProperty]装饰的属性,里面有一些 object arrays。 I have a page that has a form that displays all the properties of the model including the properties of the nested objects in the arrays. I have some js functions that add and remove form controls that correspond to the properties of my model bound object for the purposes of changing the amount of objects in the arrays. For example, I have a Sources array and the client decides to add a device, they push a plus button and boom new device object appears on the page.我有一个页面有一个表单,显示 model 的所有属性,包括 arrays 中嵌套对象的属性。我有一些 js 函数添加和删除对应于我的 model 绑定 object 的属性的表单控件更改 arrays 中对象数量的目的。例如,我有一个Sources数组,客户端决定添加一个设备,他们按下加号按钮,新设备 object 出现在页面上。 If I then post this object back to the PageModel the model bound property now has this new object in the device array with all the fields and comes up valid.如果我随后将此 object 发布回PageModel ,则 model 绑定属性现在在包含所有字段的设备数组中具有此新的 object 并且有效。 HOWEVER, this does not work in the opposite.然而,这并不适用于相反的情况。 If the client wants to remove a device, the java-script function removes the HTML element but when it's posted, the ModelState.MyProperty.Sources still shows the device there in the array with all of it's properties as null.如果客户端想要删除设备,java 脚本 function 会删除 HTML 元素,但在发布时, ModelState.MyProperty.Sources仍会在数组中显示该设备,其所有属性均为 null。

It seems like whatever the model looks like going to the client, it comes back the same way?似乎无论 model 去客户端是什么样子,它都会以同样的方式返回吗? I see the ModelState dictionary that is generated still has entries for Config.Sources[<deletedindex>].Id when the HTML coming back to the server doesn't have any attributes that call for it.我看到生成的ModelState字典仍然有Config.Sources[<deletedindex>].Id的条目,当返回服务器的 HTML 没有任何需要它的属性时。

As a note, HttpContext.Request.Form.Keys does NOT have entries for the removed array objects but Model.Config.Sources does.请注意, HttpContext.Request.Form.Keys没有已删除数组对象的条目,但Model.Config.Sources有。

JS functions in case these are my problem. JS函数以防这些是我的问题。

    const sourceSection = document.querySelector(".source-section");
    const nextCard = document.querySelectorAll(".source-card").length;
    const newDiv = document.createElement("div");
    newDiv.classList.add("col-6");
    newDiv.classList.add("p-4");
    newDiv.classList.add("border");
    newDiv.classList.add("source-card");

    newDiv.innerHTML = ` 
                        <div class="row col">
                            <h3>Source ${nextCard}</h3>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__Id" class="col-6 my-1">Id</label>
                            <input type="text" data-val="true" data-val-required="Source Id field can not be empty" id="Config_Sources_${nextCard}__Id" name="Config.Sources[${nextCard}].Id" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].Id" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__Name" class="col-6 my-1">Name</label>
                            <input type="text" data-val="true" data-val-required="The Name field is required." id="Config_Sources_${nextCard}__Name" name="Config.Sources[${nextCard}].Name" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].Name" data-valmsg-replace="true"></span>
                        <div data-lastpass-icon-root="true" style="position: relative !important; height: 0px !important; width: 0px !important; float: left !important;"></div></div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__Icon" class="col-6 my-1">Icon</label>
                            <input type="text" data-val="true" data-val-required="The Icon field is required." id="Config_Sources_0__Icon" name="Config.Sources[${nextCard}].Icon" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].Icon" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__Position" class="col-6 my-1">Position</label>
                            <input type="text" data-val="true" data-val-required="The Position field is required." id="Config_Sources_${nextCard}__Position" name="Config.Sources[${nextCard}].Position" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].Position" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__Label" class="col-6 my-1">Label</label>
                            <input type="text" data-val="true" data-val-required="The Label field is required." id="Config_Sources_${nextCard}__Label" name="Config.Sources[${nextCard}].Label" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].Label" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__nsMessage" class="col-6 my-1">No Signal Message</label>
                            <input type="text" id="Config_Sources_${nextCard}__nsMessage" name="Config.Sources[${nextCard}].nsMessage" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].nsMessage" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__nsHelpMessage" class="col-6 my-1">No Signal Help Message</label>
                            <input type="text" id="Config_Sources_${nextCard}__nsHelpMessage" name="Config.Sources[${nextCard}].nsHelpMessage" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].nsHelpMessage" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__pMessage" class="col-6 my-1">Presentation Message</label>
                            <input type="text" id="Config_Sources_${nextCard}__pMessage" name="Config.Sources[${nextCard}].pMessage" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].pMessage" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__pIcon" class="col-6 my-1">Presentation Icon</label>
                            <input type="text" id="Config_Sources_0__pIcon" name="Config.Sources[${nextCard}].pIcon" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].pIcon" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__nsIcon" class="col-6 my-1">No Signal Icon</label>
                            <input type="text" id="Config_Sources_${nextCard}__nsIcon" name="Config.Sources[${nextCard}].nsIcon" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].nsIcon" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__subpageId" class="col-6 my-1">Subpage Id</label>
                            <input type="text" id="Config_Sources_${nextCard}__subpageId" name="Config.Sources[${nextCard}].subpageId" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].subpageId" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__HasVideo" class="col-6 my-1">Has Video</label>
                            <input type="checkbox" data-val="true" data-val-required="The Has Video field is required." id="Config_Sources_${nextCard}__HasVideo" name="Config.Sources[${nextCard}].HasVideo" value="false" class="col-6 my-1">
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__HasAudio" class="col-6 my-1">Has Audio</label>
                            <input type="checkbox"  data-val="true" data-val-required="The Has Audio field is required." id="Config_Sources_${nextCard}__HasAudio" name="Config.Sources[${nextCard}].HasAudio" value="false" class="col-6 my-1">
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__VideoRequired" class="col-6 my-1">Video Req.</label>
                            <input type="checkbox" data-val="true" data-val-required="The Video Req. field is required." id="Config_Sources_${nextCard}__VideoRequired" name="Config.Sources[${nextCard}].VideoRequired" value="false" class="col-6 my-1">
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_0__BypassP" class="col-6 my-1">Bypass Presentation</label>
                            <input type="checkbox" data-val="true" data-val-required="The Bypass Presentation field is required." id="Config_Sources_${nextCard}__BypassP" name="Config.Sources[${nextCard}].BypassP" value="false" class="col-6 my-1">
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_0__Disabled" class="col-6 my-1">Disabled</label>
                            <input type="checkbox" data-val="true" data-val-required="The Disabled field is required." id="Config_Sources_${nextCard}__Disabled" name="Config.Sources[${nextCard}].Disabled" value="false" class="col-6 my-1">
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__Pinned" class="col-6 my-1">Pinned</label>
                            <input type="checkbox" data-val="true" data-val-required="The Pinned field is required." id="Config_Sources_${nextCard}__Pinned" name="Config.Sources[${nextCard}].Pinned" value="false" class="col-6 my-1">
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__DefaultMode" class="col-6 my-1">Default Mode</label>
                            <input type="text" data-val="true" data-val-required="The Default Mode field is required." id="Config_Sources_${nextCard}__DefaultMode" name="Config.Sources[${nextCard}].DefaultMode" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[0].DefaultMode" data-valmsg-replace="true"></span>
                        </div>
                        <div class="row seperator">
                            <label for="Config_Sources_${nextCard}__DetectionId" class="col-6 my-1">Detection Id</label>
                            <input type="text" data-val="true" data-val-required="The Detection Id field is required." id="Config_Sources_${nextCard}__DetectionId" name="Config.Sources[${nextCard}].DetectionId" value="" class="col-6 my-1">
                            <span class="field-validation-valid" data-valmsg-for="Config.Sources[${nextCard}].DetectionId" data-valmsg-replace="true"></span>
                        </div>
                            <h4>Route 0</h4>
                            <div class="row seperator">
                                <label for="Config_Sources_${nextCard}__Routes_0__Id" class="col-6 my-1">Id</label>
                                <input type="text" data-val="true" data-val-required="The Id field is required." id="Config_Sources_${nextCard}__Routes_0__Id" name="Config.Sources[${nextCard}].Routes[0].Id" value="" class="col-6 my-1">
                            </div>
                            <div class="row seperator">
                                <label for="Config_Sources_${nextCard}__Routes_0__Input" class="col-6 my-1">Input</label>
                                <input type="text" id="Config_Sources_${nextCard}__Routes_0__Input" name="Config.Sources[${nextCard}].Routes[0].Input" value="" class="col-6 my-1">
                            </div>
                            <div class="row seperator">
                                <label for="Config_Sources_${nextCard}__Routes_0__Output" class="col-6 my-1">Output</label>
                                <input type="text" id="Config_Sources_${nextCard}__Routes_0__Output" name="Config.Sources[${nextCard}].Routes[0].Output" value="" class="col-6 my-1">
                            </div>
                    `;

    sourceSection.appendChild(newDiv);
};

function removeSource() {
    const sourceSection = document.querySelector(".source-section");
    const sourceCards = document.querySelectorAll(".source-card");
    const target = sourceCards[sourceCards.length - 1];
    sourceSection.removeChild(target);
};```

Found the problem.发现了问题。 ASP.NET Core for every checkbox creates a hidden input field to keep track of the value. ASP.NET 每个复选框的核心创建一个隐藏的输入字段来跟踪值。 I don't know why this is the default behavior but it is.我不知道为什么这是默认行为,但确实如此。 So when i removed the HTML element with javascript, it didn't remove the hidden input field which is located near the bottom of the body, so when the data posted back, the HTML still had for="" fields that called out for Config.Sources[<deletedindex>] which caused the model binder to create unwanted objects.所以当我用 javascript 删除 HTML 元素时,它没有删除位于正文底部附近的隐藏输入字段,所以当数据回发时,HTML 仍然有for=""调用Config.Sources[<deletedindex>]的字段Config.Sources[<deletedindex>]导致 model 活页夹创建不需要的对象。 You can stop ASP.NET from generating these hidden fields but adding您可以阻止 ASP.NET 生成这些隐藏字段,但添加

builder.Services.Configure<MvcViewOptions>(options =>
{
    // Disable hidden checkboxes
    options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None;
});

to your Program.cs file.到您的Program.cs文件。 You can read more about that here asp.net mvc: why is Html.CheckBox generating an additional hidden input您可以在此处阅读更多相关信息asp.net mvc:为什么 Html.CheckBox 会生成额外的隐藏输入

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM