DefaultModelBinder cannot deserialize a .NET Dictionary object passed into action as a JSON object?

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 fill in 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 do on the client, DefaultModelBinder cannot seem to deserialize it.

Here is a sample 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); } 

Note that the opposite works. When it is passed as a JsonResult, the FilterItem object is successfully serialized and passed to the client as a JSON object. However, trying to switch the other way does not work.

I read the Connect ticket and thought that work would work, but it is not.

Is it possible to deserialize a .NET dictionary altogether with DefaultModelBinder in ASP.NET MVC 3?

+6
source share
5 answers

Hansel says this:

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

DefaultModelBinder expects slightly less optimal syntax for dictionaries. Try using the following syntax:

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

It's kind of cumbersome, but it communicates. The following works, but I personally prefer this; he is shorter.

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

UPDATE

Based on the Jeroen blog post (see his answer below, with a link) and the brain flash I received after re-examining my code, I updated ExtendedJsonValueProviderFactory so that it always correctly creates a BackingStore for the 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 http://oss.form.vu/json-dictionary-example/ .


By removing the current JsonValueProviderFactory and replacing the one that can handle dictionary creation, you can link your dictionary. First, as Kate pointed out, be sure to close your dictionary inside “filterItem” in your Javascript, since this is the name of the model variable in your controller’s action, and for JSON, the name of the variable in the controller’s action must match the name of the returned Json element. In addition, when passing a class, any nested elements must match the property names in the class.

Then create the 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 of the JsonValueProviderFactory class, with the exception of the extension for creating an entry in DictionaryValueProvider of type Dictionary<string,string> . You should also notice that for processing as a dictionary, the element must have a name ending in "Dictionary" (and although I think this is a significant smell of code, I can’t think of an alternative alternative at this time ... I am open to suggestions).

Then 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.

The final step: enjoy kindness.

+1
source

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

Yesterday I had the same problem when trying to publish a JavaScript dictionary (JSON) in a controller action method. I created a custom connecting device that processes common dictionaries with different type arguments, both directly (in the parameter of the action method) and contained in the model class. I tested it only in MVC 3.

For more information about my experience and custom model binding source code, see my blog post http://buildingwebapps.blogspot.com/2012/01/passing-javascript-json-dictionary-to.html

0
source

The default mediator cannot process the list. I solved this problem in my open source project: http://jsaction.codeplex.com and wrote an article about this problem: read here http://jsaction.codeplex.com/wikipage?title=AllFeatures&referringTitle=Documentation

... Asp.net MVC has built-in capabilities to convert sent data to objects with a strong type. But the data that we send must be prepared in the right direction, so that by default the data binder can be used and fill the properties of the controller parameter objects. The problem is that providing a JSON object to the jQuery.ajax () function call does not work. At all. Data does not receive data on the server, so the controller action parameters have default values, which are probably invalid. The problem is that the JSON object was converted by jQuery to query the query string, and the values ​​of the second-level properties were distorted into a form that does not understand ASP.NET MVC default model binding ...

0
source

Source: https://habr.com/ru/post/894922/


All Articles