在表单发布期间绑定复杂对象

时间:2014-01-21 18:06:00

标签: c# javascript asp.net-mvc

我需要在一些页面上添加一些功能,所以我将其作为局部视图实现。

在这个局部视图中,我有一个下拉列表,一个添加按钮和一个“项目”,每个项目都包含一个删除按钮。功能很明显。单击任何删除按钮将删除关联的项目,从下拉列表中选择一个项目,然后单击添加添加该项目。

复杂性是这需要完全在javascript中发生 - 对项目列表的更改需要完全在客户端发生,并且在提交表单之前服务器上没有任何事情发生。 (也就是说,我们不希望在每次更改时通过ajax更新服务器,我们希望收集更改并在表单提交时提交它们。)

第二个复杂性是这个表单需要相当多的插入,对父视图的要求尽可能少。

所以我为partial创建了一个viewmodel:

public class ItemsModel
{
    // The list to be displayed in the dropdown
    public List<KeyValuePair<string, string>> itemsList { get; set; }
    // The list of selected items
    public List<string> items { get; set; }
    public string itemsJson
    {
        get { return JsonConvert.SerializeObject(this.items); }
        set { this.items = JsonConvert.DeserializeObject<List<string>>(value);
    }

    public ItemsModel()
    {
        this.itemsList = new List<KeyValuePair<string, string>>();
        this.items = new List<string>();
    }
}

页面的viewmodel包含以下实例:

public class MyViewModel
{
    // Assorted stuff
    public ItemsModel itemsModel;
}

当Controller构建模型时,在HttpGet期间,它使用两个列表填充ItemsModel对象。页面视图包含partial,传递itemsModel:

@{Html.RenderPartial("_itemsList", Model.itemsModel);

在部分内容中,我构建了下拉列表:

@Html.DropDownList("itemsList", new SelectList(Model.itemsList, "Key", "Value")

我用javascript填充:

var items = $.parseJSON('@Html.Raw(Model.itemsJson)');
var itemsUl = $('#itemsUl');
itemsUl.empty();
var iTemplate = $('#itemTemplate').html();
for (var i=0; i<items.length; i++)
{
    var template = iTemplate.nformat("{item}": items[i]);
    itemsUl.append($(template));
}

就我而言,这就是我的意思。我的意图是添加javascript来处理插入和删除,但此时没有意义。因为当我使用未修改的列表提交页面时,MyViewModel.itemsModel为null。在网上浏览,我看过很多关于MVC如何将Request项绑定到复杂列表的帖子,但是没有一个与我的问题相关,因为没有任何东西可以让MVC受到约束。

我在Fiddler看过,正在发送的请求包括“......&amp; itemsList =&amp; ...” - 它根本没有发送任何数据。

所以我想知道我是否正在追逐错误的道路。在表单提交中包含复杂数据的常规方法是什么?我已经读过FormData(),但这似乎只适用于Ajax风格的发送,它不会影响正常的表单提交。

有什么想法吗?

1 个答案:

答案 0 :(得分:0)

像往常一样,在这些问题中,我只是没有考虑事情。

如果我希望在表单提交期间将值包含在请求中,我需要输入表单元素。

首先,我删除了并行json属性 - 内联序列化/反序列化很容易,它使得更明显的是发生了什么。所以我的模型变成了:

public class ItemsModel
{
    // The list to be displayed in the <select>
    public List<string> itemsList { get; set; }

    // The list of selected items
    public List<string> items { get; set; }

    public ItemsModel()
    {
        this.itemsList = new List<string>();
        this.items = new List<string>();
    }
}

该列表仅发送到浏览器,它不需要返回,因此可以直接注入javascript变量:

var itemsSelect = $('#itemsSelect');
itemsSelect.find('option').remove();

var itemsList = 
    $.parseJSON('@Html.Raw(JsonConvert.SerializeObject(Model.itemsList))');

for (i = 0; i < itemsList.length; i++)
{
    var item = itemsList[i];
    itemsSelect
        .append($("<option>", { value: item })
        .text(item ? item : "Select Item"));
}

然而,这些物品需要回来。这意味着json需要作为隐藏元素注入页面:

@Html.Hidden("itemsJson", JsonConvert.SerializeObject(Model.items))

然后在javascript中,我们将数据提取到数组中:

var items = JSON.parse($('#itemsJson').val());

然后我从这些元素构建&lt; li&gt;并将它们添加到我的&lt; ul&gt;。然后,用户从&lt; select&gt;中选择一个项目。然后点击“添加”按钮,或点击其中一个&lt; li&gt;中的“删除”按钮,然后删除&lt; option&gt;从下拉列表中添加&lt; li&gt;到&lt; ul&gt;,或者删除&lt; li&gt;来自&lt; ul&gt;并添加&lt;选项&gt;到&lt; select&gt;。在任何一种情况下,我都需要重新序列化数组并更新隐藏的元素:

$('#itemsJson').val(JSON.stringify(items));

然后,当我提交表单时,浏览器将在请求中包含一个名为“itemsJson”的JSON字符串。唯一剩下的问题是如何将其纳入HttpPost行动。有三种可能性:

  1. 我可以从Request [“itemsJson”]中提取值。我不喜欢那样。
  2. 我可以构建一个自定义绑定来反序列化JSON并更新模型。这似乎是不必要的工作。
  3. 或者,我可以在我的Action中添加另一个参数,并在那里反序列化。
  4. 我选择了选项3:

    [Authorize]
    [HttpPost]
    public ActionResult Perishable(MyViewModel model, string itemsJson)
    {
        if (ModelState.IsValid)
        {
            model.itemsModel= new ItemsModel
            {
                items= JsonConvert.DeserializeObject<List<string>>(itemsJson)
            };
    
            ...
        }
    }