【问题标题】:Model Values Getting Lost During PostBack模型值在回发期间丢失
【发布时间】:2014-11-04 10:14:29
【问题描述】:

我正在尝试将我的模型传回控制器。我已经在表单中包含了模型的所有字段,但是模型的两个部分一旦返回控制器Employees 和EmployeesInMultipleCompanies 就会丢失,它们都是IList 类型。我已经验证了这些字段在传递给视图时存在,它们只是没有返回给控制器。

.cshtml

@using (Html.BeginForm("PostEmail","Import",FormMethod.Post))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>EmailViewModel</legend>
        <p>
            <input type="email" name="EmailAddresses" value=" " required="required" />
            <span class="btn_orange"><a href="#" class="remove_field" >x</a></span>
        </p>
        <p>
            <span class="btn_orange"><a class="add_email_button" href="#">Add Another Email</a></span>
        </p>
        @Html.HiddenFor(m=> Model.Employees)
        @Html.HiddenFor(m=> Model.CompanyId)
        @Html.HiddenFor(m=> Model.CompanyName)
        @Html.HiddenFor(m=> Model.PayFrequency)
        @Html.HiddenFor(m=> Model.FirstPayPeriodBeginDate)
        @Html.HiddenFor(m=> Model.LastPayPeriodEndDate)
        @Html.HiddenFor(m=> Model.NumberPayPeriods)
        @Html.HiddenFor(m=> Model.ValidEmployees)
        @Html.HiddenFor(m=> Model.InvalidEmployees)
        @Html.HiddenFor(m=> Model.EmployeesInMultipleCompanies)
        @Html.HiddenFor(m=> Model.TotalEmployeeCount)
        <p>
            <input type="submit" value="Send Email" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Cancel", "Continue", "Import")
</div>

<script type="text/javascript">
    $(document).ready(function () {
        $('body').on('click', '.remove_field', function () {
            $(this).closest('p').remove();
        });
        $('.add_email_button').closest('p').click(function () {
            var html = '<p><input type="email" required="required" name="EmailAddresses" /><span class="btn_orange"><a href="#" class="remove_field">x</a></span></p>';
            $(html).insertBefore($(this));
        });

        $(body).on('click', 'submit', function() {
            $('email').attr('required', true);
        });
    });
</script>

控制器.cs

public ActionResult SendEmail(ImportViewModel model)
        {
            var editedEmployees = model.EmployeesInMultipleCompanies;
            var importModel = TempData["ImportModel"] as ImportViewModel;

            //for each employee who is in multiple companies, set user-chosen company id and the COHD related to that company
            var importService = new ImportService();
            importService.UpdateEmployeesInMultipleCompanies(editedEmployees, importModel.Employees);
            TempData["ImportModel"] = importModel;

            return this.RazorView("SendEmail", importModel);
        }


        [HttpPost]
        public ActionResult PostEmail(ImportViewModel model)
        {
            IEmailer emailer = new Emailer();
            emailer.SendEmail(model.EmailAddresses, model.Employees);
            var employees =
                model.Employees.Where(e => !string.IsNullOrWhiteSpace(e.ValidationErrorOrException)).ToList();
            var emailViewModel = new EmailViewModel(employees);
            return this.RazorView("Continue", emailViewModel);
        }

【问题讨论】:

  • 我会考虑不将它们呈现在单个隐藏中,而是呈现具有相同名称和不同值的多个输入。 DefaultModelBinder 将看到这一点并将其解释为集合类型,它应该可以正常工作。或者,您可以创建一个自定义模型绑定器,将存储在单个隐藏中的值拆分。
  • 还要小心 TempData,因为它在幕后使用会话(这不一定是坏事,除非您在网络农场中)并且应该只在重定向时使用:stackoverflow.com/questions/1500402/…。还有很多人会考虑使用 TempData 代替强类型的 ViewModel/其他持久性机制来成为代码异味:stackoverflow.com/questions/386681/…
  • @xDaevax 感谢您的通知。我将来会考虑它,因为现在它已经在服务器其他视图中我只是在我的使用它。

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


【解决方案1】:

问题不在于控制器,而在于值在客户端上的存储方式。 DefaultModelBinder 非常聪明,但有时也不是那么聪明。只要属性匹配,它就可以处理某些情况下的接口、复杂类型的集合和许多其他事情。有关该行为的详细信息,请参阅这篇文章:http://msdn.microsoft.com/en-us/magazine/hh781022.aspx

如果您调试控制器并检查 Request.Form 中的值,您将看到您的数据存储为单个值。如果模型绑定器会说话,他们会说:

Request.Form["EmployeesInMultipleCompanies"] 与我的模型的 EmployeesInMultipleCompanies 属性无关,因为我的模型元数据 (ModelMetaDataProvider) 告诉我应该寻找集合类型 (IList一个未知的泛型类型),这显然是一个单一的字符串值,所以我不会绑定这个值。

要解决这个问题,您必须为 DefaultModelBinder 提供足够的提示以使其完成工作。这样做的一种方法是呈现隐藏字段,使其具有与之关联的索引器。这是模型绑定器按照您期望的方式运行所需的线索。

如果你都这样渲染:

@for(var i = 0; i < Model.Employees.Count; i++)
{
  Html.Hiddenfor(m => m.Employees[i])
}
@for(var i = 0; i < Model.EmployeesInMultipleCompanies.Count; i++)
{
  Html.Hiddenfor(m => m.EmployeesInMultipleCompanies[i])
}

然后再次单步调试您的调试器,您现在会在 Request.Form 中看到这个:

Request.Form["EmployeesInMultipleCompanies[0]"]
Request.Form["EmployeesInMultipleCompanies[1]"]

等等……

现在模型绑定器可以工作了。

或者,您可以创建一个自定义模型绑定器,该绑定器知道如何从单个输入中读取字段。


我创建了一个 Gist 来演示在此处使用通用 IList 执行此类 ModelBinding 时可能出现的不同类型的结果:https://gist.github.com/xDaevax/bd35b5d88fed03709d30

【讨论】:

  • 那些应该是for循环而不是foreach?
  • 这种方法是否仍需要我使用 List 而不是 IList?
  • @AntarrByrd 不,你不应该这样做:stackoverflow.com/questions/653514/…
  • 它现在仍然不适合我。我会继续前进,谢谢
  • @AntarrByrd 唯一的例外是EmployeesInMultipleCompanies 是非原始类型(用户定义)。例如,如果您的 IListstringGuid,那么您会没事的。如果它类似于 IList,您可能会遇到问题。如果您想进一步讨论这个问题,我们可以在聊天中。
【解决方案2】:

将其替换为以下内容:

@foreach(var i = 0; i < Model.Employees.Count; i++)
{
  Html.Hiddenfor(m => m.Employees[i])
}

EmployeesInMultipleCompanies 也应该这样做

【讨论】:

    【解决方案3】:

    插入隐藏字段的值被序列化为字符串以执行此操作。

    IList&lt;T&gt; 不可序列化,因为它是一个接口,因此不能存储在隐藏字段中。

    有关更多详细信息,请参阅this 问题

    您如何将您的IList&lt;T&gt; 转换为常规列表?

    【讨论】:

    • 这是因为,但序列化我的 EmailAddresses 也没有问题,这也是 IList
    • 即使更改为常规列表后,我也会遇到同样的问题
    • IList 应该没问题,因为 DefaultModelBinder 很智能并且不会序列化 IList,而是 IList 的类型参数T。唯一可能成为问题的情况是 IList 具有不是具体类型(抽象/接口)的泛型类型约束。您提到的问题与ModelBinder无关。
    • 我想出了另一种方法。我没有意识到 Employee 数据已经存在于控制器中的 TempData 中。
    • 正如其他人所说,只要底层类型也是可序列化的,使用foreach 循环为集合中的每个项目创建一个隐藏字段就可以正常工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-29
    • 1970-01-01
    • 1970-01-01
    • 2011-08-23
    相关资源
    最近更新 更多