2011-08-12 16 views
6

tengo una clase muy simple:DefaultModelBinder no puede deserializar el objeto .NET Dictionary pasado a una acción como un objeto JSON?

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

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

Quiero llenar los datos en el diccionario en el cliente y luego pasarlo a mi acción del controlador como un objeto JSON. Sin embargo, no importa lo que intente con el cliente, DefaultModelBinder no parece poder deserializarlo.

Aquí se muestra un ejemplo de código javascript para llamar a mi acción:

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

Y aquí es una versión simplificada de mi método de acción:

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

Tenga en cuenta que las obras opuestas. Cuando se pasa como JsonResult, el objeto FilterItem se serializa con éxito y se pasa como un objeto JSON al cliente. Sin embargo, intentar ir al revés no funciona.

leí the ticket on Connect y pensaron que el trabajo en torno a funcionaría pero no es así.

¿Es posible en absoluto para deserializar un diccionario .NET utilizando el DefaultModelBinder en ASP.NET MVC 3?

+0

este hilo puede ayudar - http://stackoverflow.com/questions/4789481/post-an-array-of-objects-via-json-to-asp-net-mvc3 – Jason

+0

Por lo que yo sé .Net no le gusta serializar/deserializar diccionarios. Puede que tenga que convertirlo en un IEnumerable > y luego usarlo en el constructor de un diccionario. –

+0

Se deserializará/serializará 'Dictionary ' only. – Gabe

Respuesta

0

¿Ha intentado lo siguiente?

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

ACTUALIZACIÓN

En base a la entrada de blog por Jeroen (ver su respuesta a continuación, con el enlace), y un destello del cerebro que había después de volver a revisar mi código, he actualizado el ExtendedJsonValueProviderFactory para que siempre cree correctamente un BackingStore para un diccionario de nivel superior enviado a través de JSON.

El código está disponible en GitHub en https://github.com/counsellorben/ASP.NET-MVC-JsonDictionaryBinding, y un ejemplo de trabajo está en http://oss.form.vu/json-dictionary-example/.


Mediante la eliminación de la corriente JsonValueProviderFactory y sustituyendo uno que puede manejar la creación de diccionario, puede vincular a su diccionario. Primero, como señaló Keith, en su Javascript, asegúrese de ajustar su diccionario dentro de "filterItem", ya que este es el nombre de la variable de modelo en su acción de controlador, y para JSON, el nombre de la variable en la acción del controlador debe coincidir con el nombre del elemento Json que se devuelve. Además, al aprobar una clase, los elementos anidados deben coincidir con los nombres de las propiedades de la clase.

A continuación, cree una clase ExtendedJsonValueProviderFactory, de la siguiente manera:

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

Usted puede notar que esta clase es casi idéntica a la clase JsonValueProviderFactory estándar, a excepción de la extensión para construir una entrada en el DictionaryValueProvider de tipo Dictionary<string,string> . También debe notar que, con el fin de ser procesado como un diccionario, un elemento debe tener un nombre que termina en "Diccionario" (y al mismo tiempo creo que esto es un olor código significativa, no puedo pensar en otra alternativa en este momento. .. Estoy abierto a sugerencias).

A continuación, añada lo siguiente a Application_Start en 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()); 

Esto eliminará la norma JsonValueProviderFactory, y reemplazarlo con nuestra clase extendida.

Paso final: disfrute de la bondad.

+0

No funciona, mi diccionario está siempre vacío – jjxtra

0

Ayer tuve exactamente el mismo problema al intentar publicar un diccionario de JavaScript (JSON) en un método de acción de controlador. Creé un archivador de modelo personalizado que procesa diccionarios genéricos con diferentes argumentos de tipo, tanto directamente (en un parámetro de método de acción) como en una clase de modelo. Yo sólo lo he probado en MVC 3.

Para los detalles de mis experiencias y el código fuente de la carpeta de modelo personalizado, por favor ver mi post blog en http://buildingwebapps.blogspot.com/2012/01/passing-javascript-json-dictionary-to.html

0

ligante modelo predeterminado no puede manejar la lista. resolví este problema en mi proyecto de código abierto: http://jsaction.codeplex.com y escribí un artículo sobre este tema: Tiene una lectura aquí http://jsaction.codeplex.com/wikipage?title=AllFeatures&referringTitle=Documentation

... Asp.net MVC tiene capacidades incorporadas de transformar los datos enviados a la fuerte tipo objetos Pero los datos que estamos enviando tienen que estar preparados de la manera correcta, de modo que el enlazador de datos predeterminado puede hacerlo y rellenar las propiedades de los objetos de los parámetros de la acción del controlador. El problema es que proporcionar el objeto JSON a la llamada a la función jQuery.ajax() no funciona. En absoluto. Los datos no se unen a datos en el servidor, por lo que los parámetros de acción del controlador tienen sus valores predeterminados que probablemente no sean válidos. El problema es que objeto JSON quedó convertida por jQuery para solicitar cadena de consulta y segundos valores de propiedad de nivel consiguió destrozado en una forma que Asp.net MVC ligante modelo por defecto no entiende ...

4

Hanselman habla de esto :

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

El DefaultModelBinder espera que algunos menos que óptimo sintaxis para los diccionarios. Intente utilizar este tipo de sintaxis:

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

Es un poco voluminoso pero se une. Lo siguiente también funciona, pero personalmente prefiero lo anterior; es más corto.

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