DefaultModelBinder 无法反序列化作为 JSON 对象传递给操作的 .NET 字典对象



我有一个非常简单的类:

public class FilterItem
{
    public Dictionary<string, string> ItemsDictionary { get; set; }
    public FilterItem()
    {
        ItemsDictionary = new Dictionary<string, string>();
    }
}

我想在客户端上填充字典中的数据,然后将其作为 JSON 对象传递给我的控制器操作。但是,无论我在客户端上尝试什么,DefaultModelBinder似乎都无法反序列化它。

下面是一个调用我的操作的示例 JavaScript 代码:

var simpleDictionary = {"ItemsDictionary": {"1": "5", "2": "7"}};
$.ajax({ cache: false, type: "POST", data: JSON.stringify(simpleDictionary),
contentType: "application/json; charset=utf-8", 
url: "/Catalog7Spikes/GetFilteredProductsJson", success: function (data) {...});

这是我的操作方法的简化版本:

[HttpPost]
public ActionResult GetFilteredProductsJson(FilterItem filterItem)
{   
    ProductsModel productsModel = new ProductsModel();
    return View("SevenSpikes.Nop.UI.Views.Products", productsModel);
}

请注意,相反的情况。当作为 JsonResult 传递时,FilterItem 对象将成功序列化并作为 JSON 对象传递给客户端。然而,试图反其道而行之是行不通的。

我在Connect上阅读了票证,并认为解决方法会起作用,但事实并非如此。

是否可以使用 MVC 3 中的 DefaultModelBinder 反序列化 .NET 字典 ASP.NET?

Hanselman 谈到了这一点:

源:http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

DefaultModelBinder 预计字典的语法会有些不太理想。尝试使用以下语法:

 {
 "dictionary[0]":{"Key":"a", "Value":"b"},
 "dictionary[1]":{"Key":"b", "Value":"b"}
 }

它有点笨重,但它可以绑定。以下内容也有效,但我个人更喜欢上述内容;它更短。

 {
 "dictionary[0].Key":"a",
 "dictionary[0].Value":"b",
 "dictionary[1].Key":"b"
 "dictionary[1].Value":"b"
 }

更新

根据Jeroen的博客文章(见下面的回答,带有链接(,以及我在重新审查我的代码后脑洞大开,我更新了ExtendedJsonValueProviderFactory,以便它始终为通过JSON提交的顶级字典正确创建BackingStore。

该代码可在 GitHub 上找到 https://github.com/counsellorben/ASP.NET-MVC-JsonDictionaryBinding,一个工作示例在 http://oss.form.vu/json-dictionary-example/上。


通过删除当前JsonValueProviderFactory并替换可以处理字典创建的,您可以绑定字典。 首先,正如Keith指出的那样,在你的Javascript中,一定要把你的字典包装在"filterItem"中,因为这是控制器操作中模型变量的名称,对于JSON,控制器操作中的变量名称必须与返回的Json元素的名称匹配。 此外,传递类时,任何嵌套元素都必须与类中属性的名称匹配。

接下来,创建一个ExtendedJsonValueProviderFactory类,如下所示:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Web.Script.Serialization;
public sealed class ExtendedJsonValueProviderFactory : ValueProviderFactory
{
    private void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
    {
        IDictionary<string, object> d = value as IDictionary<string, object>;
        if (d != null)
        {
            foreach (KeyValuePair<string, object> entry in d)
            {
                if (entry.Key.EndsWith("Dictionary", StringComparison.CurrentCulture))
                    CreateDictionary(backingStore, entry);
                else
                    AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
            }
            return;
        }
        IList l = value as IList;
        if (l != null)
        {
            for (int i = 0; i < l.Count; i++)
            {
                AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
            }
            return;
        }
        // primitive
        backingStore[prefix] = value;
    }
    private void CreateDictionary(Dictionary<string, object> backingStore, KeyValuePair<string, object> source)
    {
        var d = source.Value as IDictionary<string, object>;
        var dictionary = new Dictionary<string, string>();
        foreach (KeyValuePair<string, object> entry in d)
            dictionary.Add(entry.Key, entry.Value.ToString());
        AddToBackingStore(backingStore, source.Key, dictionary);
        return;
    }
    private static object GetDeserializedObject(ControllerContext controllerContext)
    {
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
        {
            // not JSON request
            return null;
        }
        StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
        string bodyText = reader.ReadToEnd();
        if (String.IsNullOrEmpty(bodyText))
        {
            // no JSON data
            return null;
        }
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        object jsonData = serializer.DeserializeObject(bodyText);
        return jsonData;
    }
    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        object jsonData = GetDeserializedObject(controllerContext);
        if (jsonData == null)
        {
            return null;
        }
        Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        AddToBackingStore(backingStore, String.Empty, jsonData);
        return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
    }
    private static string MakeArrayKey(string prefix, int index)
    {
        return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
    }
    private static string MakePropertyKey(string prefix, string propertyName)
    {
        return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
    }
}

您可能会注意到,此类与标准的 JsonValueProviderFactory 类几乎相同,除了在类型为 Dictionary<string,string> 的 DictionaryValueProvider 中构建条目的扩展。 您还应该注意到,为了作为字典进行处理,元素的名称必须以"字典"结尾(虽然我认为这是一个重要的代码气味,但我现在想不出另一种选择......我愿意接受建议(。

接下来,将以下内容添加到Global.asax.cs中的Application_Start

var j = ValueProviderFactories.Factories.FirstOrDefault(f => f.GetType().Equals(typeof(JsonValueProviderFactory)));
if (j != null)
    ValueProviderFactories.Factories.Remove(j);
ValueProviderFactories.Factories.Add(new ExtendedJsonValueProviderFactory());

这将删除标准的 JsonValueProviderFactory,并将其替换为我们的扩展类。

最后一步:享受美好。

你试过以下吗?

var simpleDictionary = {"ItemsDictionary": {"1": "5", "2": "7"}};
$.ajax({ cache: false, type: "POST", data: {filterItem : JSON.stringify(simpleDictionary)},
contentType: "application/json; charset=utf-8", 
url: "/Catalog7Spikes/GetFilteredProductsJson", success: function (data) {...});

昨天我在尝试将 JavaScript (JSON( 字典发布到控制器操作方法时遇到了完全相同的问题。我创建了一个自定义模型绑定器,用于处理具有不同类型参数的通用字典,这些参数可以直接(在操作方法参数中(处理,也可以包含在模型类中。我只在 MVC 3 中测试过它。

有关我的经验和自定义模型绑定器的源代码的详细信息,请参阅我的博客文章 http://buildingwebapps.blogspot.com/2012/01/passing-javascript-json-dictionary-to.html

默认模型绑定器无法处理列表。我在我的开源项目中解决了这个问题:http://jsaction.codeplex.com 并写了一篇关于这个问题的文章:请阅读这里http://jsaction.codeplex.com/wikipage?title=AllFeatures&referringTitle=Documentation

。Asp.net MVC 具有将发送的数据转换为强类型对象的内置功能。但是,我们必须以正确的方式准备要发送的数据,以便默认数据绑定器可以填充控制器操作参数对象的属性。 问题是向jQuery.ajax((函数调用提供JSON对象不起作用。完全。数据不会在服务器上绑定数据,因此控制器操作参数的默认值可能无论如何都无效。 问题是 JSON 对象被 jQuery 转换为请求查询字符串,二级属性值被篡改成 MVC 默认模型绑定器无法理解 Asp.net 形式......

相关内容

  • 没有找到相关文章

最新更新