2011-08-12 20 views
6

Ho una classe molto semplice:DefaultModelBinder non può deserializzare. NET Oggetto del dizionario passato a un'azione come oggetto JSON?

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

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

voglio per popolare i dati presenti nel dizionario del client e quindi passarlo alla mia azione di controllo come un oggetto JSON. Tuttavia, qualunque cosa provi sul client, DefaultModelBinder non sembra in grado di deserializzarlo.

Ecco un esempio di codice javascript per chiamare la mia azione:

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

E qui è una versione semplificata del mio metodo di azione:

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

prega di notare che le opere opposte. Quando viene passato come JsonResult, l'oggetto FilterItem viene serializzato correttamente e passato come oggetto JSON al client. Tuttavia, provare a fare il contrario non funziona.

Ho letto the ticket on Connect e ho pensato che il lavoro intorno avrebbe funzionato ma non è così.

E 'possibile deserializzare un dizionario .NET utilizzando DefaultModelBinder in ASP.NET MVC 3?

+0

questa discussione può aiutare - http://stackoverflow.com/questions/4789481/post-an-array-of-objects-via-json-to-asp-net-mvc3 – Jason

+0

Per quanto ne so , .Net non ama serializzare/deserializzare i dizionari. Potrebbe essere necessario convertirlo in un oggetto IEnumerable > e quindi utilizzarlo nel costruttore di un dizionario. –

+0

Deserializza/serializza solo 'Dictionary '. – Gabe

risposta

0

Hai provato quanto segue?

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

UPDATE

Sulla base del post sul blog di Jeroen (vedere la sua risposta qui sotto, con il link), e un flash del cervello che avevo dopo la ri-esaminato il mio codice, ho aggiornato il ExtendedJsonValueProviderFactory in modo che crei sempre correttamente un BackingStore per un dizionario di livello superiore inviato tramite JSON.

Il codice è disponibile su GitHub allo https://github.com/counsellorben/ASP.NET-MVC-JsonDictionaryBinding e un esempio di lavoro è http://oss.form.vu/json-dictionary-example/.


Rimuovendo la corrente JsonValueProviderFactory e sostituendo uno in grado di gestire la creazione del dizionario, è possibile associare il dizionario. Innanzitutto, come ha sottolineato Keith, nel tuo Javascript, assicurati di avvolgere il tuo dizionario all'interno di "filterItem", poiché questo è il nome della variabile del modello nell'azione del controller e, per JSON, il nome della variabile nell'azione del controller deve corrispondere al nome dell'elemento Json restituito. Inoltre, quando si passa una classe, qualsiasi elemento nidificato deve corrispondere ai nomi delle proprietà nella classe.

Successivamente, creare una classe ExtendedJsonValueProviderFactory, come segue:

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

Si può notare che questa classe è quasi identica alla classe JsonValueProviderFactory standard, ad eccezione per l'estensione di costruire un ingresso nel DictionaryValueProvider di tipo Dictionary<string,string> . Si dovrebbe anche notare che, per essere elaborato come dizionario, un elemento deve avere un nome che termina con "Dizionario" (e mentre penso che questo sia un odore significativo del codice, non riesco a pensare ad un'altra alternativa in questo momento. .. Sono aperto a suggerimenti).

Avanti, aggiungere il seguente al 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()); 

Questo eliminerà lo standard JsonValueProviderFactory, e sostituirlo con la nostra classe estesa.

Passaggio finale: godetevi la bontà.

+0

Non funziona per me, il mio dizionario è sempre vuoto – jjxtra

0

Ieri ho avuto esattamente lo stesso problema durante il tentativo di pubblicare un dizionario JavaScript (JSON) su un metodo di azione del controller. Ho creato un modello personalizzato che elabora dizionari generici con argomenti di tipo diverso, sia direttamente (in un parametro del metodo di azione) o contenuti in una classe del modello. Ho testato solo in MVC 3.

Per i dettagli delle mie esperienze e il codice sorgente del modello personalizzato legante, si prega di vedere il mio post sul blog a http://buildingwebapps.blogspot.com/2012/01/passing-javascript-json-dictionary-to.html

0

legante modello predefinito non può gestire la lista. ho risolto questo problema nel mio progetto open source: http://jsaction.codeplex.com e scritto un articolo su questo problema: qui hanno un letto http://jsaction.codeplex.com/wikipage?title=AllFeatures&referringTitle=Documentation

... Asp.net MVC ha funzionalità integrate di trasformare i dati inviati a forti digita oggetti. Ma i dati che inviamo devono essere preparati nel modo giusto, in modo che il raccoglitore di dati predefinito possa compilare e popolare le proprietà degli oggetti dei parametri di azione del controllore. Il problema è che fornire l'oggetto JSON alla chiamata alla funzione jQuery.ajax() non funziona. Affatto. I dati non rilevano dati vincolati sul server, quindi i parametri di azione del controller hanno i loro valori predefiniti che sono probabilmente comunque non validi. Il problema è che oggetto JSON ottenuto convertito dalla jQuery per richiedere query string e valori di proprietà di secondo livello ma ho storpiato in una forma che Asp.net MVC legante modello predefinito non capisce ...

4

Hanselman parla di questo :

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

Il DefaultModelBinder si aspetta un po 'di meno-che-ottimale la sintassi per i dizionari. Prova a utilizzare questo tipo di sintassi:

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

È un po 'ingombrante ma si lega. Anche il seguente funziona, ma personalmente preferisco quanto sopra; è più breve

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