【问题标题】:Partial within a partial null exception部分在部分空异常中
【发布时间】:2015-09-04 03:14:44
【问题描述】:

我有一个 MVC 表单,它比我所有的其他表单都复杂,使用了三个模型。

Company -> Base_IP -> RequestedIP ViewModel -> Partial1 -> Partial2

我正在使用 BeginCollectionItem,因为每个模型都有一个模型的属性列表。 IE - Company 有一个名为 baseIps 的属性,BaseIp 类有一个名为 requestedIps 的属性,返回 null 的 requestedIps,页面渲染时有计数,但提交时没有。

在postCreate() 中提交到数据库时,'requestedIps' 属性为空,这是为什么呢?

我在下面添加了有问题的控制器和部分代码示例,而不是全部,因为它是大量/冗余的 - 有任何问题,请告诉我。

控制器 - [HttpGet]Create()

public ActionResult Create()
        {
            var cmp = new Company
            {
               contacts = new List<Contact>
                {
                    new Contact { email = "", name = "", telephone = "" }
                }, pa_ipv4s = new List<Pa_Ipv4>
                {
                    new Pa_Ipv4 
                    { 
                        ipType = "Pa_IPv4", registedAddress = false, existingNotes = "", numberOfAddresses = 0, returnedAddressSpace = false, additionalInformation = "",
                        requestedIps = new List<IpAllocation>
                        {
                            new IpAllocation { allocationType = "Requested", cidr = "", mask = "", subnet  = "" }
                        }
                    }
                }
            };
            return View(cmp);
        }

控制器 - [HttpPost]Create()

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Company cmp) // does not contain properties assigned/added to in view render
        {
            if (ModelState.IsValid)
            {
                db.companys.Add(cmp);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(cmp);
        }

创建视图

@model Company
@using (Html.BeginForm())
{
            <div id="editorRowsAsn">
                @foreach (var ip in Model.pa_ipv4s)
                {
                    @Html.Partial("Pa_IPv4View", ip)
                }
            </div>
            <br />
            <div data-role="main" class="ui-content">
                <div data-role="controlgroup" data-type="horizontal">
                    <input type="submit" class="ui-btn" value="Create" />
                </div>
            </div>
}

Pa_Ipv4 视图

@model Pa_Ipv4
@using (Html.BeginCollectionItem("pa_ipv4s"))
{
    @Html.AntiForgeryToken()

    <div id="editorRowsRIpM">
        @foreach (var item in Model.requestedIps)
        {
            @Html.Partial("RequestedIpView", item)
        }
    </div>
    @Html.ActionLink("Add", "RequestedManager", null, new { id = "addItemRIpM", @class = "button" }

}

RequestedIpView

@model IpAllocation
<div class="editorRow">
    @using (Html.BeginCollectionItem("requestedIps"))
    {
        <div class="ui-grid-c ui-responsive">
            <div class="ui-block-a">
                <span>
                    @Html.TextBoxFor(m => m.subnet, new { @class = "checkFiller" })
                </span>
            </div>
            <div class="ui-block-b">
                <span>
                    @Html.TextBoxFor(m => m.cidr, new { @class = "checkFiller" })
                </span>
            </div>
            <div class="ui-block-c">
                <span>
                    @Html.TextBoxFor(m => m.mask, new { @class = "checkFiller" })
                    <span class="dltBtn">
                        <a href="#" class="deleteRow"><img src="~/Images/DeleteRed.png" style="width: 15px; height: 15px;" /></a>
                    </span>
                </span>
            </div>
        </div>
    }
</div>

【问题讨论】:

  • 这是对对象的 html 请求反序列化的一个非常常见的问题。这对于这两个级别的层次结构来说是不平凡的。您需要查看 html 请求,查看请求变量是如何命名的。它们是否符合预期的默认绑定行为?
  • 或者,构建一个 json 对象并发送它
  • 我并没有真正使用过 JSON,你有没有链接让我看看如何在这种情况下使用它?默认绑定行为,即如果我只使用 Model -> partial 与 BCI,一切都很好 - 这就是你的意思吗?

标签: c# asp.net-mvc asp.net-mvc-4


【解决方案1】:

您首先(外部)部分将生成与您的模型相关的正确名称属性(您的代码在Pa_Ipv4.cshtml 视图中没有显示任何控件,但我假设您确实有一些),例如

<input name="pa_ipv4s[xxx-xxx].someProperty ...>

但是内部部分不会因为@using (Html.BeginCollectionItem("requestedIps"))会生成

<input name="requestedIps[xxx-xxx].subnet ...>
<input name="requestedIps[xxx-xxx].cidr ...>

他们应该在哪里

<input name="pa_ipv4s[xxx-xxx].requestedIps[yyy-yyy].subnet ...>
<input name="pa_ipv4s[xxx-xxx].requestedIps[yyy-yyy].cidr ...>

通常您可以使用附加视图数据将前缀传递给部分(请参阅this answer 示例),但不幸的是,您无权访问由BeginCollectionItem 帮助器生成的Guid,因此这是不可能的正确地为name 属性添加前缀。

文章here 和here 讨论了创建自己的帮助器来处理嵌套集合。

其他选项包括使用嵌套的for 循环并包括集合索引器的隐藏输入,这将允许您从集合中删除项目,并且在您提交表单时仍然能够绑定到您的模型。

for (int i = 0; i < Model.pa_ipv4s.Count; i++)
{
  for(int j = 0; j < Model.pa_ipv4s[i].requestedIps.Count; j++)
  {
    var name = String.Format("pa_ipv4s[{0}].requestedIps.Index", i);
    @Html.TextBoxFor(m => m.pa_ipv4s[i].requestedIps[j].subnet)
    @Html.TextBoxFor(m => m.pa_ipv4s[i].requestedIps[j].cidr)
    ...
    <input type="hidden" name="@name" value="@j" />
  }
}

但是,如果您还需要动态添加新项目,则需要使用 javascript 来生成 html(参考示例 here 和 here)

【讨论】:

  • 好吧,这可能比我需要的更复杂,如果我从pa_ipv4s 中删除List&lt;&gt; 元素,所以它是pa_ipv4,那么我是否能够解决m.pa_ipv4.requestedIps[x].subnet在第二部分使用 BCI?
  • 不,因为pa_ipv4 是一个集合属性,所以name 属性需要一个索引器,否则它不会绑定到您的模型(请参阅我的第三个代码 sn-p 中的所需名称)-和如果它不应该有领先的m.(你的模型没有一个名为m的属性)
  • 谢谢,在这种情况下查看您对前缀的回答,我认为对于我的需要有不同的解决方法。只需要一个 pa_ipv4,因此可以阻止它成为一个集合属性,所以会有company.pa_ipv4.requestedIps[x].subnet。或者 BCI 绝对不会以这种方式从部分工作?即ViewModel.PartialModel.Partial2Models[x].Property ?
  • 首先不是company.pa_ipv4.requestedIps[x].subnet,而是pa_ipv4.requestedIps[x].subnet(你没看懂我最后的评论吗?- 你没有一个模型的属性名为company)。而且,是的,如果 requestedIps 是单个对象(不是集合属性),您可以使其工作。删除第一个部分,并在第二个部分使用@using (Html.BeginCollectionItem("ipv4.requestedIps")) {。尽管如果是这种情况,您确实应该使用视图模型来更好地表示您想要显示/编辑的需求
  • 嗨,斯蒂芬,我一直在研究您建议的 article,这正是我想要的 pa_ipv4s[xxx-xxx].requestedIps[yyy-yyy].subnet 所需的结果。我已经像提到的那样修改了文件(在 BCI 方法中更改代码)并且所有编译为现在引用的 dll,但是 html 没有正确呈现/调试时对象仍然是null - 如果我提出关于这个主题的另一个问题,你能看看吗?
【解决方案2】:

如果您查看您的最终标记,您可能会输入类似

的名称
input name="subnet" 
input name="cidr" 
input name="mask" 

这是表单发布时表单集合的显示方式。不幸的是,这不会绑定到您的公司模型。

您的字段需要改为这样

input name="Company.pa_ipv4s[0].subnet"
input name="Company.pa_ipv4s[0].cidr"
input name="Company.pa_ipv4s[0].mask"

input name="Company.pa_ipv4s[1].subnet"
input name="Company.pa_ipv4s[1].cidr"
input name="Company.pa_ipv4s[1].mask"

【讨论】:

  • 是的,没错(输入标记),以这种方式使用模型时如何解决这个问题?我只创建第二个模型之一(第一个部分),所以如果我使用 Html.BeginForm() 而不是 BCI 会起作用吗?
  • 您可能需要找到一个好的 html 助手,它可以呈现部分视图并接受字段前缀参数并将其应用于字段名称 stackoverflow.com/questions/4898321/….. 同时您需要将您的 foreach 循环更改为 for 循环以解决命名问题 c-sharpcorner.com/UploadFile/4b0136/…
  • 谢谢。要更新的多个记录很好,这是导致问题的第二个模型的模型列表属性。我正在查看this 你是第一个链接,因为这看起来很相似。然而,这似乎非常复杂,因为我认为会更简单,来自 Web 表单背景。
【解决方案3】:

有多种方法可以“解决”这个问题,每种方法都有自己的注意事项。

一种方法是设置“编辑器”视图(通常在 ~/Views/Shared/EditorTemplates/ClassName.cshtml 中),然后使用 @Html.EditorFor(x => x.SomeEnumerable) . 这不适用于您需要能够从集合中间删除任意项目的情况;尽管您仍然可以通过您设置的额外属性(例如 ItemIsDeleted)来处理这些情况(例如通过 javascript)。

在这里设置一个完整的例子会很长,但你也可以参考这个教程:http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/

首先,您将创建一个简单的模板,例如

~/Views/Share/EditorTemplates/Contact.cshtml:

 @model yournamespace.Contact
 <div>
     @Html.LabelFor(c => c.Name)
     @Html.TextBoxFor(c => c.Name)
     @Html.ValidationMessageFor(c => c.Name)
</div>
<div>
     @Html.LabelFor(c => c.Email)
     @Html.TextBoxFor(c => c.Email)
     @Html.ValidationMessageFor(c => c.Email)
</div>
... other simple non-enumerable properties of `Contact` ...
@Html.EditorFor(c => c.pa_ipv4s) @* uses ~/Views/Shared/EditorTemplates/pa_ipv4s.cshtml *@

在您编辑/创建Company 的视图中,您可以将其调用为

@Html.EditorFor(company => company.Contacts)

(就像公司的 EditorTemplate 调用 EditorFor pa_ipv4s。)

当您以这种方式使用 EditorFor 时,MVC 将自动为您处理索引。 (您如何处理添加新联系人/IPv4/等。这里有点高级,但这应该可以帮助您入门。)


MVCContrib 也有一些您可以使用的辅助方法,但据我回忆,它并不是特别简单,并且可能会将您绑定到特定的 MVC 版本。

【讨论】:

  • 这不起作用,因为 OP 希望能够删除集合中的项目,这意味着集合将使用非连续索引器回发,并且绑定将失败。
  • 如果你绑定到 IEnumerable 而不是 IList 你可以有非连续的索引。删除项目的另一种选择是使用隐藏字段,如 Item.State,并使用带有可能状态的 enum,如 Added Updated Deleted NoChange。这可以使后端的处理更容易(也可以在提交表单之前轻松“取消删除”某些内容)。
  • 不,你不能。 DefaultModelBinder 只会绑定索引器从零开始且连续的集合,除非您包含特殊字段 &lt;input name="Index" value="xx" &gt;,其中 xx 是索引器的值。而且你不能使用EditorTemplate 来做到这一点(因为模板不知道它在集合中的索引)
  • 嗯。好的,我知道我有使用非连续索引的代码,但我检查了一下,它确实有索引输出。所以我会更新答案。
猜你喜欢
  • 2012-12-08
  • 1970-01-01
  • 2012-11-16
  • 1970-01-01
  • 2014-05-19
  • 2023-04-09
  • 2017-11-01
  • 2021-04-24
  • 1970-01-01
相关资源
最近更新 更多