我有一个非常简单的类:
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 形式......