MVC4将复杂对象从视图传递到控制器,但新视图没有渲染。

5

我对MVC非常陌生,在尝试将一个现有网站移植到MVC4时遇到了这个问题。

我们使用的模型很多数据都是通过服务调用填充的,因此显然我们希望将调用保持最少。问题在于当我尝试将模型传回控制器时,模型内的复杂对象总是变成null。我已经能够通过ajax回调将数据持久化到控制器上,然而,我需要动作返回一个新的视图,并且在动作完成后,视图的代码执行了,但没有重定向(我认为这就是ajax的目的,我想我要求的是一种可以以相同方式持久化数据却实际重定向的解决方案)。

这是我的模型:

public class DistributionModel
{
    public string typeOfDistribution { get; set; }
    public Document document { get; set; }
    public string thumbnailUrl { get; set; }
    public MergeFieldModel mergeFields { get; set; }
}

public class MergeFieldModel
{
    public MergeFields documentMergeFields { get; set; }
}

以下是我使用的控制器动作:

        public ActionResult Index(DistributionModel distributionModel)
    {
        distributionModel.mergeFields = new MergeFieldModel() { documentMergeFields = MergeFieldsHelper.GetDocumentMergeFields(distributionModel.document.Id) };
        return View("Index", distributionModel);
    }

我尝试使用 href=@Url.Action("Index", Model) 替换下面的按钮来调用控制器并执行重定向(重定向本身是有效的,但我随后必须在控制器内执行另一个服务调用以检索与来自调用视图的相同文档一起使用的 Document 对象,因为该对象在模型中返回为 NULL)。以下是调用控制器并实际返回完整模型的视图部分:我认为我要找的是一种无需 ajax 就能实现这一点的方法,以便我可以获得重定向到 Distribution/Index 页面(这是从 Distribution/DocumentDetails 页面触发的)。
        <button id="EmailDistribution" data-corners="false" data-theme="a">EMAIL</button>

         $('#EmailDistribution').click(function () {
              var model = @Html.Raw(Json.Encode(Model));
              $.ajax({
              url: '@Url.Action("Index", "Distribution")',
              type: 'POST',
              contentType: 'application/json; charset=utf-8',
              data: JSON.stringify(model),     
              processData: false,                 
              });                
         });

谢谢,非常感谢您的帮助。


为什么你可以在Distribution/DocumentDetails操作的最后一行只写return RedirectToAction("Index", distributionModel); - Suhas
DocumentDetails操作返回DocumentDetails视图。此外,我尝试从控制器方法中进行RedirectToAction操作,并发现它将Document对象作为NULL传递给其他操作。 - gutsmania
1个回答

8
我不确定我是否完全理解了你的问题,但我可以告诉你,你需要将模型的每个值都放在一个表单中,然后将其发布到你想要不是空的控制器操作中。
这正是你在ajax调用中所做的:你目前将整个模型转换为json,并使用jQuery的能力再次转换为发布数据。假设你有以下模型:
public class TestModel {
    public string A { get; set; }
    public string B { get; set; }
}

您的JavaScript代码将创建类似于{ A: 'A的值', B: 'B的值' }的字符串,该字符串将使用jQuery转换为HTTP POST请求:
POST /Controller/Index HTTP/1.1
Host: demo.loc
User-Agent: Mozilla/5.0 whatever
Content-Type: application/x-www-form-urlencoded; charset=utf-8

A=Value+for+a&B=Value+for+B

作为结果,您的Index操作将被调用,并且DefaultModelBinder绑定值到您的模型属性。这适用于像整数这样的基本类型,也适用于像集合这样的复杂类型。 DefaultModelBinder处理这些类型的转换。
让我们看一个更复杂的模型:
public class ComplexSubModel {
    public ICollection<string> StringList { get; set; }
}

public class ComplexModel {
    public ComplexSubModel SubModel { get; set; }
}

DefaultModelBinder 还可以绑定以下类型的模型:

POST /Controller/Index HTTP/1.1
Host: demo.loc
User-Agent: Mozilla/5.0 whatever
Content-Type: application/x-www-form-urlencoded; charset=utf-8

ComplexModel.SubModel.StringList[0]=First+entry&ComplexModel.SubModel.StringList[1]=Second+entry&ComplexModel.SubModel.StringList[2]=Third+entry

这将导致一个新的ComplexModel实例,其SubModel属性设置为ComplexSubModel的一个新实例,该实例的StringList属性设置为包含三个字符串First entrySecond entryThird entry的新的System.Collection.Generic.List<string>实例。

现在你需要将你的模型属性呈现到隐藏字段中,以便它们包含在postback中:

@using (Html.BeginForm()) {
    @Html.HiddenFor(m => m.SubModel.StringList[0])
    @Html.HiddenFor(m => m.SubModel.StringList[1])
    @Html.HiddenFor(m => m.SubModel.StringList[2])
}

每一个在postback中包含的属性将不会为空,可能被用户伪造,因为它们只是被重新发送到服务器上,并且假设它们是在隐藏字段中呈现的。实际上,你无法确定重新发送的值是否就是之前通过服务调用所获取的值。

另一种可能性是将服务调用的结果保存在TempData字典中,该字典实际上将这些值存储在用户会话中,并且在它们在postback操作中被重新读取或直接将这些值存储在会话中时销毁它们:

public ActionResult Index() {
    // Do service calls

    #region Variant a
    TempData["ServiceResultA"] = foo;
    TempData["ServiceResultB"] = bar;
    #endregion

    #region Variant b
    Session["ServiceResultA"] = foo;
    Session["ServiceResultB"] = bar;
    #endregion

    var model = new DistributionModel();
    // Set properties and stuff

    return View("Index", model);
}

[HttpPost]
public ActionResult Index(DistributionModel model) {
    // Read "cached" service calls

    #region Variant a
    var foo = (TResultA)TempData["ServiceResultA"];
    var bar = (TResultB)TempData["ServiceResultB"];
    #endregion

    #region Variant b
    var foo = (TResultA)Session["ServiceResultA"];
    var bar = (TResultB)Session["ServiceResultB"];
    #endregion

    // Do stuff

    return RedirectToAction(...);
}

两种方法都有优缺点,例如在一个浏览器会话中浏览两个选项卡可能会有问题,或者需要在使用会话状态服务器时使类可序列化。然而,流程始终相同:您必须

  • 每次从服务中获取数据(成本高)或
  • 将它们保存在服务器上的任何位置(TempData、Session等)或
  • 通过表单提交它们(用户可以伪造,不总是容易)。

选择你的毒药。;-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接