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

发布于 2024-11-29 07:49:49 字数 1309 浏览 1 评论 0 原文

我有一个非常简单的类:

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 字典?

I have a very simple class:

public class FilterItem
{
    public Dictionary<string, string> ItemsDictionary { get; set; }

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

I want to populate the data in the dictionary on the client and then pass it to my controller action as a JSON object. However no matter what I try on the client, the DefaultModelBinder does not seem to be able to deserialize it.

Here is an example javascript code to call my action:

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) {...});

And here is a simplified version of my action method:

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

Please note that the opposite works. When passed as a JsonResult the FilterItem object is successfully serialized and passed as a JSON object to the client. However trying to go the other way round does not work.

I read the ticket on Connect and thought that the work around would work but it does not.

Is it possible at all to deserialize a .NET dictionary using the DefaultModelBinder in ASP.NET MVC 3?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

忆梦 2024-12-06 07:49:49

汉塞尔曼谈到了这一点:

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

Hanselman talks about this:

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

The DefaultModelBinder expects some less-than-optimal syntax for dictionaries. Try using this kind of syntax:

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

It's kind of bulky but it binds. The following works as well, but I personally prefer the above; it's shorter.

 {
 "dictionary[0].Key":"a",
 "dictionary[0].Value":"b",
 "dictionary[1].Key":"b"
 "dictionary[1].Value":"b"
 }
表情可笑 2024-12-06 07:49:49

更新

根据 Jeroen 的博客文章(请参阅下面他的答案,以及链接),以及重新审查代码后我的大脑闪现,我更新了 ExtendedJsonValueProviderFactory 这样它将始终为通过 JSON 提交的顶级字典正确创建 BackingStore。

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


通过删除当前的 JsonValueProviderFactory 并替换为可以处理字典创建的 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< 类型的 DictionaryValueProvider 条目的扩展之外。字符串,字符串>。您还应该注意到,为了作为字典进行处理,元素必须具有以“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,并将其替换为我们的扩展类。

最后一步:享受美好。

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.

千鲤 2024-12-06 07:49:49

您尝试过以下方法吗?

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) {...});

Have you tried the following?

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) {...});
山有枢 2024-12-06 07:49:49

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

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

Yesterday I had exactly the same problem while trying to post a JavaScript (JSON) dictionary to a controller action method. I created a custom model binder that processes generic dictionaries with different type arguments, both directly (in a action method parameter) or contained in a model class. I have only tested it in MVC 3.

For the details of my experiences and the source code of the custom model binder, please see my blog post at http://buildingwebapps.blogspot.com/2012/01/passing-javascript-json-dictionary-to.html

任性一次 2024-12-06 07:49:49

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

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

Default model binder cannot handle list.
I resolved this issue in my open source project: http://jsaction.codeplex.com and wrote an article about this issue: have a read here
http://jsaction.codeplex.com/wikipage?title=AllFeatures&referringTitle=Documentation

...Asp.net MVC has built-in capabilities of transforming sent data to strong type objects. But data we're sending has to be prepared in the right way so default data binder can it and populate controller action parameters objects' properties.
The problem is that providing JSON object to jQuery.ajax() function call doesn't work. At all. Data doesn't get data bound on the server so controller action parameters have their default values that are probably invalid anyway.
The problem is that JSON object got converted by jQuery to request query string and second level property values got mangled into a form that Asp.net MVC default model binder doesn't understand...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文