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

我有一个非常简单的课程:

public class FilterItem { public Dictionary ItemsDictionary { get; set; } public FilterItem() { ItemsDictionary = new Dictionary(); } } 

我想在客户端上填充字典中的数据,然后将其作为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上阅读了这张票,并认为这种方法可行,但事实并非如此。

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

汉塞尔曼谈到这个:

来源: 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" } 

UPDATE

基于Jeroen的博客文章(请参阅下面的答案,链接),以及重新审核我的代码之后的大脑闪存,我更新了ExtendedJsonValueProviderFactory,以便它始终正确地为顶级创建BackingStore通过JSON提交的字典。

该代码可在GitHub上获得, url为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 backingStore, string prefix, object value) { IDictionary d = value as IDictionary; if (d != null) { foreach (KeyValuePair 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 backingStore, KeyValuePair source) { var d = source.Value as IDictionary; var dictionary = new Dictionary(); foreach (KeyValuePair 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 backingStore = new Dictionary(StringComparer.OrdinalIgnoreCase); AddToBackingStore(backingStore, String.Empty, jsonData); return new DictionaryValueProvider(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类型的Dictionary构建一个条目的扩展。 您还应该注意到, 为了作为字典处理,元素必须具有以“Dictionary”结尾的名称 (虽然我认为这是一个重要的代码味道,但我现在想不到另一种选择……我愿意接受建议)。

接下来,将以下内容添加到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具有将发送数据转换为强类型对象的内置function。 但是我们发送的数据必须以正确的方式准备,因此默认数据绑定器可以填充控制器操作参数对象的属性。 问题是提供JSON对象到jQuery.ajax()函数调用不起作用。 完全没有。 数据不会在服务器上获取数据绑定,因此控制器操作参数的默认值可能无效。 问题是JSON对象被jQuery转换为请求查询字符串,第二级属性值被破坏成Asp.net MVC默认模型绑定器不理解的forms…