6

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

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 上阅读了票,并认为解决方法可以解决问题,但事实并非如此。

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

4

5 回答 5

4

汉塞尔曼谈到了这一点:

来源: 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"
 }
于 2012-07-06T16:00:31.837 回答
2

UPDATE

Based upon the blog post by Jeroen (see his answer below, with the link), and a brain flash I had after re-reviewing my code, I have updated the ExtendedJsonValueProviderFactory so that it will always properly create a BackingStore for a top-level dictionary submitted via JSON.

The code is available on GitHub at https://github.com/counsellorben/ASP.NET-MVC-JsonDictionaryBinding, and a working example is at http://oss.form.vu/json-dictionary-example/.


By removing the current JsonValueProviderFactory and substituting one which can handle dictionary creation, you can bind your dictionary. First, as Keith pointed out, in your Javascript, be sure to wrap your dictionary inside of "filterItem", since this is the name of the model variable in your controller action, and for JSON, the name of the variable in the controller action must match the name of the Json element being returned. Also, when passing a class, any nested elements must match the names of the properties in the class.

Next, create an ExtendedJsonValueProviderFactory class, as follows:

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;
    }
}

You may notice that this class is almost identical to the standard JsonValueProviderFactory class, except for the extension to build an entry into the DictionaryValueProvider of type Dictionary<string,string>. You also should notice that, in order to be processed as a dictionary, an element must have a name ending in "Dictionary" (and while I think this is a significant code smell, I cannot think of another alternative at this time ... I am open to suggestions).

Next, add the following to Application_Start in Global.asax.cs:

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

This will remove the standard JsonValueProviderFactory, and replace it with our extended class.

Final step: enjoy the goodness.

于 2011-08-13T05:56:15.057 回答
0

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

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

于 2012-01-19T09:44:00.653 回答
0

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

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

于 2012-04-20T21:07:25.617 回答
0

您是否尝试过以下操作?

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) {...});
于 2011-08-12T20:56:01.537 回答