c# - recorrer - newtonsoft.json example
¿Cómo usar Json.NET para el enlace de modelos JSON en un proyecto MVC5? (3)
He estado buscando una respuesta o un ejemplo en Internet, pero aún no he podido encontrar una. Simplemente me gustaría cambiar el serializador JSON predeterminado que se usa para deserializar JSON mientras se enlaza a la biblioteca JSON.NET
He encontrado this publicación SO, pero no puedo implementarla hasta el momento, ni siquiera puedo ver el espacio de nombres de System.Net.Http.Formatters
, ni puedo ver la GlobalConfiguration
.
¿Qué me estoy perdiendo?
ACTUALIZAR
Tengo un proyecto MVC de ASP.NET, era básicamente un proyecto MVC3. Actualmente estoy apuntando a .NET 4.5 y uso el ASP.NET MVC 5 y los paquetes relacionados de NuGet.
No veo el ensamblado System.Web.Http, ni ningún espacio de nombres similar. En este contexto, me gustaría inyectar JSON.NET para usarlo como el modelo de carpeta predeterminado para el tipo de solicitudes JSON.
En mi caso, tuve que deserializar objetos complejos, incluidas las interfaces y los tipos cargados dinámicamente, etc. para que el proveedor de valor personalizado no funcione, ya que MVC aún necesita intentar descubrir cómo crear una instancia de las interfaces y luego fallar.
Como mis objetos ya estaban correctamente anotados para trabajar con Json.NET, tomé una ruta diferente: implementé una carpeta de modelos personalizada y utilicé Json.NET para deserializar explícitamente los datos del cuerpo de la solicitud, como este:
internal class CustomModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// use Json.NET to deserialize the incoming Position
controllerContext.HttpContext.Request.InputStream.Position = 0; // see: http://.com/a/3468653/331281
Stream stream = controllerContext.RequestContext.HttpContext.Request.InputStream;
var readStream = new StreamReader(stream, Encoding.UTF8);
string json = readStream.ReadToEnd();
return JsonConvert.DeserializeObject<MyClass>(json, ...);
}
}
El archivador de modelo personalizado está registrado en Global.asax.cs
:
ModelBinders.Binders.Add(typeof(MyClass), new CustomModelBinder();
Por fin he encontrado una respuesta. Básicamente, no necesito el material MediaTypeFormatter
, que no está diseñado para ser utilizado en un entorno MVC, pero en las API web de ASP.NET, es por eso que no veo esas referencias y espacios de nombres (por cierto, están incluidos en Microsoft.AspNet.WeApi
NuGet paquete).
La solución es utilizar una fábrica de proveedores de valor personalizado. Aquí está el código requerido.
public class JsonNetValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
// first make sure we have a valid context
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
// now make sure we are dealing with a json request
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;
// get a generic stream reader (get reader for the http stream)
var streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
// convert stream reader to a JSON Text Reader
var JSONReader = new JsonTextReader(streamReader);
// tell JSON to read
if (!JSONReader.Read())
return null;
// make a new Json serializer
var JSONSerializer = new JsonSerializer();
// add the dyamic object converter to our serializer
JSONSerializer.Converters.Add(new ExpandoObjectConverter());
// use JSON.NET to deserialize object to a dynamic (expando) object
Object JSONObject;
// if we start with a "[", treat this as an array
if (JSONReader.TokenType == JsonToken.StartArray)
JSONObject = JSONSerializer.Deserialize<List<ExpandoObject>>(JSONReader);
else
JSONObject = JSONSerializer.Deserialize<ExpandoObject>(JSONReader);
// create a backing store to hold all properties for this deserialization
var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
// add all properties to this backing store
AddToBackingStore(backingStore, String.Empty, JSONObject);
// return the object in a dictionary value provider so the MVC understands it
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}
private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
{
var d = value as IDictionary<string, object>;
if (d != null)
{
foreach (var entry in d)
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
}
return;
}
var l = value as IList;
if (l != null)
{
for (var i = 0; i < l.Count; i++)
{
AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
}
return;
}
// primitive
backingStore[prefix] = value;
}
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;
}
}
Y puedes usarlo así en tu método Application_Start
:
// remove default implementation
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
// add our custom one
ValueProviderFactories.Factories.Add(new JsonNetValueProviderFactory());
Here está el post que me indicó la dirección correcta, y también esta dio una buena explicación sobre proveedores de valor y encuadernadores de modelos.
Yo también tuve un problema con esto. Estaba publicando JSON en una acción, pero mis nombres de JsonProperty fueron ignorados. Por lo tanto, mis propiedades modelo estaban siempre vacías.
public class MyModel
{
[JsonProperty(PropertyName = "prop1")]
public int Property1 { get; set; }
[JsonProperty(PropertyName = "prop2")]
public int Property2 { get; set; }
[JsonProperty(PropertyName = "prop3")]
public int Property3 { get; set; }
public int Foo { get; set; }
}
Estoy publicando una acción usando esta función jquery personalizada:
(function ($) {
$.postJSON = function (url, data, dataType) {
var o = {
url: url,
type: ''POST'',
contentType: ''application/json; charset=utf-8''
};
if (data !== undefined)
o.data = JSON.stringify(data);
if (dataType !== undefined)
o.dataType = dataType;
return $.ajax(o);
};
}(jQuery));
Y lo llamo así:
data = {
prop1: 1,
prop2: 2,
prop3: 3,
foo: 3,
};
$.postJSON(''/Controller/MyAction'', data, ''json'')
.success(function (response) {
...do whatever with the JSON I got back
});
Desafortunadamente, solo foo se estaba uniendo (extraño, ya que el caso no es el mismo, pero supongo que el modelador por defecto no distingue entre mayúsculas y minúsculas)
[HttpPost]
public JsonNetResult MyAction(MyModel model)
{
...
}
La solución terminó siendo bastante simple.
Acabo de implementar una versión genérica de la carpeta de modelos de Dejan que funciona muy bien para mí. Probablemente podría usar algunos controles falsos (como asegurarse de que la solicitud sea en realidad application / json), pero está haciendo el truco ahora mismo.
internal class JsonNetModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.HttpContext.Request.InputStream.Position = 0;
var stream = controllerContext.RequestContext.HttpContext.Request.InputStream;
var readStream = new StreamReader(stream, Encoding.UTF8);
var json = readStream.ReadToEnd();
return JsonConvert.DeserializeObject(json, bindingContext.ModelType);
}
}
Cuando quiero usarlo en una acción específica, simplemente le digo que quiero usar mi carpeta de modelos personalizada Json.Net en su lugar:
[HttpPost]
public JsonNetResult MyAction([ModelBinder(typeof(JsonNetModelBinder))] MyModel model)
{
...
}
¡Ahora mis atributos [JsonProperty (PropertyName = "")] ya no se ignoran en MyModel y todo está enlazado correctamente!