POST json dictionary

Estoy intentando lo siguiente: un modelo con un diccionario dentro lo envía en la primera solicitud ajax y luego toma el resultado serializarlo nuevamente y enviarlo de nuevo al controlador.

Esto debería probar que puedo recuperar un diccionario en mi modelo. No funciona

Aquí está mi prueba simple:

public class HomeController : Controller { public ActionResult Index (T a) { return View(); } public JsonResult A(T t) { if (t.Name.IsEmpty()) { t = new T(); t.Name = "myname"; tD = new Dictionary(); tDAdd("a", "a"); tDAdd("b", "b"); tDAdd("c", "c"); } return Json(t); } } //model public class T { public string Name { get; set; } public IDictionary D { get; set; } } 

El javascript:

 $(function () { var o = { Name: 'somename', "D": { "a": "b", "b": "c", "c": "d" } }; $.ajax({ url: actionUrl('/home/a'), contentType: 'application/json', type: 'POST', success: function (result) { $.ajax({ url: actionUrl('/home/a'), data: JSON.stringify(result), contentType: 'application/json', type: 'POST', success: function (result) { } }); } }); }); 

En firebug, el json recibido y el json enviado son idénticos. Solo puedo suponer que algo se pierde en el camino.

¿Alguien tiene una idea de lo que estoy haciendo mal?

Debido a la forma en que se implementa JsonValueProviderFactory, no se admite la vinculación de diccionarios.

Una solución desafortunada:

 data.dictionary = { 'A': 'a', 'B': 'b' }; data.dictionary = JSON.stringify(data.dictionary); . . . postJson('/mvcDictionaryTest', data, function(r) { debugger; }, function(a,b,c) { debugger; }); 

postJSON js lib function (usa jQuery):

 function postJson(url, data, success, error) { $.ajax({ url: url, data: JSON.stringify(data), type: 'POST', contentType: 'application/json; charset=utf-8', dataType: 'json', success: success, error: error }); } 

El objeto ViewModel que se está publicando (presumiblemente tiene mucho más en juego que un diccionario):

 public class TestViewModel { . . . //public Dictionary dictionary { get; set; } public string dictionary { get; set; } . . . } 

El método del controlador se publica en:

 [HttpPost] public ActionResult Index(TestViewModel model) { var ser = new System.Web.Script.Serialization.JavascriptSerializer(); Dictionary dictionary = ser.Deserialize>(model.dictionary); // Do something with the dictionary } 

Hoy me encontré con el mismo problema y se me ocurrió una solución que no requiere nada más que registrar un nuevo modelo de carpeta. Es un poco hacky pero espero que ayude a alguien.

  public class DictionaryModelBinder : IModelBinder { ///  /// Binds the model to a value by using the specified controller context and binding context. ///  ///  /// The bound value. ///  /// The controller context.The binding context. public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException("bindingContext"); string modelName = bindingContext.ModelName; // Create a dictionary to hold the results IDictionary result = new Dictionary(); // The ValueProvider property is of type IValueProvider, but it typically holds an object of type ValueProviderCollect // which is a collection of all the registered value providers. var providers = bindingContext.ValueProvider as ValueProviderCollection; if (providers != null) { // The DictionaryValueProvider is the once which contains the json values; unfortunately the ChildActionValueProvider and // RouteDataValueProvider extend DictionaryValueProvider too, so we have to get the provider which contains the // modelName as a key. var dictionaryValueProvider = providers .OfType>() .FirstOrDefault(vp => vp.ContainsPrefix(modelName)); if (dictionaryValueProvider != null) { // There's no public property for getting the collection of keys in a value provider. There is however // a private field we can access with a bit of reflection. var prefixsFieldInfo = dictionaryValueProvider.GetType().GetField("_prefixes", BindingFlags.Instance | BindingFlags.NonPublic); if (prefixsFieldInfo != null) { var prefixes = prefixsFieldInfo.GetValue(dictionaryValueProvider) as HashSet; if (prefixes != null) { // Find all the keys which start with the model name. If the model name is model.DictionaryProperty; // the keys we're looking for are model.DictionaryProperty.KeyName. var keys = prefixes.Where(p => p.StartsWith(modelName + ".")); foreach (var key in keys) { // With each key, we can extract the value from the value provider. When adding to the dictionary we want to strip // out the modelName prefix. (+1 for the extra '.') result.Add(key.Substring(modelName.Length + 1), bindingContext.ValueProvider.GetValue(key).AttemptedValue); } return result; } } } } return null; } } 

La carpeta está registrada en el archivo Global.asax bajo application_start

  protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); ModelBinders.Binders.Add(typeof(Dictionary), new DictionaryModelBinder()); } 

Lo hice para trabajar con un encuadernador de modelo personalizado y cambiar la forma en que se envían los datos; sin usar Stringify y estableciendo el tipo de contenido.

JavaScript:

  $(function() { $.ajax({ url: '/home/a', type: 'POST', success: function(result) { $.ajax({ url: '/home/a', data: result, type: 'POST', success: function(result) { } }); } }); }); 

Carpeta de modelo personalizado:

 public class DictionaryModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException("bindingContext"); string modelName = bindingContext.ModelName; IDictionary formDictionary = new Dictionary(); Regex dictionaryRegex = new Regex(modelName + @"\[(?.+?)\]", RegexOptions.CultureInvariant); foreach (var key in controllerContext.HttpContext.Request.Form.AllKeys.Where(k => k.StartsWith(modelName + "["))) { Match m = dictionaryRegex.Match(key); if (m.Success) { formDictionary[m.Groups["key"].Value] = controllerContext.HttpContext.Request.Form[key]; } } return formDictionary; } } 

Y al agregar el archivador modelo en Global.asax:

 ModelBinders.Binders[typeof(IDictionary)] = new DictionaryModelBinder(); 

Tome el siguiente paquete NuGet para System.Json , que incluye el nuevo tipo JsonValue . JsonValue es un nuevo tipo de representante JSON flexible que admite totalmente la dinámica C # 4, y también es una IEnumerable> en el caso de que desee tratar una carga útil como una matriz asociativa / de diccionario.

Puedes recoger System.Json (Beta) con NuGet aquí . Parece que System.Json se incluirá de forma nativa en .NET 4.5, como se indica en las páginas de documentación aquí .

También es posible que desee leer el siguiente artículo para ayudar a que los cuerpos HTTP JSON se deserialicen correctamente en objetos JsonValue en los parámetros del método de acción:

JSON, ASP.NET MVC y JQuery – Trabajar con USONped JSON hecho fácil

Las dos piezas de código relevantes del artículo anterior serían DynamicJsonBinder y DynamicJsonAttribute, replicadas aquí para la posteridad:

 public class DynamicJsonBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (!controllerContext.HttpContext.Request.ContentType.StartsWith ("application/json", StringComparison.OrdinalIgnoreCase)) { // not JSON request return null; } var inpStream = controllerContext.HttpContext.Request.InputStream; inpStream.Seek(0, SeekOrigin.Begin); StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); string bodyText = reader.ReadToEnd(); reader.Close(); if (String.IsNullOrEmpty(bodyText)) { // no JSON data return null; } return JsonValue.Parse(bodyText); } } public class DynamicJsonAttribute : CustomModelBinderAttribute { public override IModelBinder GetBinder() { return new DynamicJsonBinder(); } } 

Un caso de uso de muestra relevante sería:

 public class HomeController : Controller { public ActionResult Index (T a) { return View(); } public JsonResult A([DynamicJson] JsonValue value) { dynamic t = value.AsDynamic(); if (t.Name.IsEmpty()) { t = new // t is dynamic, so I figure just create the structure you need directly { Name = "myname", D = new // Associative array notation (woot!): { a = "a", b = "b", c = "c" } }; } return Json(t); } } 

Solo use un mejor deserializador. Esa primera línea donde establezco la posición es porque JsonValueProvider deja la secuencia al final. Más MS JSON falla.

 Request.InputStream.Position = 0; var reader = new StreamReader(Request.InputStream); var model = Newtonsoft.Json.JsonConvert.DeserializeObject(reader.ReadToEnd()); 

Entonces, en algún lugar de ese gráfico de objeto CreativeUploadModel hay un elemento como este:

 public Dictionary Assets { get; set; } 

Que está deserializado de (por ejemplo):

 "assets":{"flash":{"type":"flash","value":"http://1234.cloudfront.net/1234.swf","properties":"{\"clickTag\":\"clickTAG\"}"} 

Newtonsoft JSON es el proveedor predeterminado de JSON para WebAPI … por lo que no va a ninguna parte.

Aquí está mi solución a un problema similar:

 using System.Collections.Generic; using System.IO; using System.Web.Mvc; using System.Web.Script.Serialization; namespace Controllers { public class DictionaryModelBinder : IModelBinder { public object BindModel(ControllerContext context, ModelBindingContext bindingContext) { context.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin); using (TextReader reader = new StreamReader(context.HttpContext.Request.InputStream)) { string requestContent = reader.ReadToEnd(); var arguments = new JavaScriptSerializer().Deserialize>(requestContent); return arguments[bindingContext.ModelName]; } } } } using Controllers; using Moq; using NUnit.Framework; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using System.Web; using System.Web.Mvc; namespace ControllersTest { [TestFixture] public class DictionaryModelBinderTest { private ControllerContext controllerContext; [Test] public void ReturnsDeserializedPrimitiveObjectsAndDictionaries() { string input = @"{ arguments: { simple: 1, complex: { a: 2, b: 3 }, arrayOfSimple: [{ a: 4, b: 5 }], arrayOfComplex: [{ a: 6, b: 7 }, { a: 8, b: 9 }]}, otherArgument: 1 }"; SetUpRequestContent(input); var binder = new DictionaryModelBinder(); var bindingContext = new ModelBindingContext(); bindingContext.ModelName = "arguments"; var model = (Dictionary)binder.BindModel(controllerContext, bindingContext); Assert.IsFalse(model.ContainsKey("otherArgument")); Assert.AreEqual(1, model["simple"]); var complex = (Dictionary)model["complex"]; Assert.AreEqual(2, complex["a"]); Assert.AreEqual(3, complex["b"]); var arrayOfSimple = (ArrayList)model["arrayOfSimple"]; Assert.AreEqual(4, ((Dictionary)arrayOfSimple[0])["a"]); Assert.AreEqual(5, ((Dictionary)arrayOfSimple[0])["b"]); var arrayOfComplex = (ArrayList)model["arrayOfComplex"]; var complex1 = (Dictionary)arrayOfComplex[0]; var complex2 = (Dictionary)arrayOfComplex[1]; Assert.AreEqual(6, complex1["a"]); Assert.AreEqual(7, complex1["b"]); Assert.AreEqual(8, complex2["a"]); Assert.AreEqual(9, complex2["b"]); } private void SetUpRequestContent(string input) { var stream = new MemoryStream(Encoding.UTF8.GetBytes(input)); stream.Seek(0, SeekOrigin.End); var controllerContextStub = new Mock(); var httpContext = new Mock(); httpContext.Setup(x => x.Request.InputStream).Returns(stream); controllerContextStub.Setup(x => x.HttpContext).Returns(httpContext.Object); this.controllerContext = controllerContextStub.Object; } } } using Controllers; using PortalApi.App_Start; using System.Collections.Generic; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; namespace PortalApi { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); RouteConfig.RegisterRoutes(RouteTable.Routes); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); ModelBinders.Binders.Add(typeof(Dictionary), new DictionaryModelBinder()); } } } 

¡Que te diviertas! 😛 Saludos Łukasz Duda

Coloque el objeto complejo como una cadena y deserialice en el otro extremo. Sin embargo, no hay seguridad de tipo para esto. Aquí hay un diccionario con clave de cadena y valores de matriz de cadena.

js:

 var data = { 'dictionary': JSON.stringify({'A': ['a', 'b'] }) }; $.ajax({ url: '/Controller/MyAction', data: JSON.stringify(data), type: 'POST', contentType: 'application/json', dataType: 'json' }); 

Controlador c #:

 [HttpPost] public ActionResult MyAction(string dictionary) { var s = new System.Web.Script.Serialization.JavaScriptSerializer(); Dictionary d = s.Deserialize>(dictionary); return View(); } 

Usando ASP.NET 5 y MVC 6 directamente de la caja, estoy haciendo esto:

jSON:

 { "Name": "somename", "D": { "a": "b", "b": "c", "c": "d" } } 

Controlador:

 [HttpPost] public void Post([FromBody]Dictionary dictionary) { } 

Esto es lo que aparece cuando se produce (Nombre y D son las claves):

enter image description here

Para cualquier persona que se encuentre con este problema recientemente aún, siempre y cuando no necesite que su controlador acepte específicamente un diccionario, puede hacer lo siguiente:

 HttpResponseMessage SomeMethod([FromBody] IEnumerable> values) { Dictionary dictionary = values.ToDictionary(x => x.Key, x = x.Value); } 

Aunque es un poco hacky.