Comment utiliser Json.NET pour JSON modelbinding dans un projet MVC5?
j'ai cherché sur internet une réponse ou un exemple, mais je n'en ai pas encore trouvé. Je voudrais simplement changer le serializer par défaut de JSON qui est utilisé pour desérialiser JSON tout en modelbinding à JSON.NET bibliothèque.
j'ai trouvé ce donc post, mais ne peut pas l'implémenter jusqu'à présent, je ne peux même pas voir l'espace de nom System.Net.Http.Formatters
, ni ne peux voir GlobalConfiguration
.
Qu'est-ce que je rate?
Mise à jour
j'ai un ASP.NET projet MVC, il s'agissait essentiellement d'un projet MVC3. Actuellement, je cible .NET 4.5 et en utilisant le ASP.NET MVC 5 et les paquets NuGet associés.
Je ne vois pas le système.Web.Http assemblée, ni de toute autre espace de noms. Dans ce contexte, je voudrais JSON.NET à utiliser comme liant de modèle par défaut pour les requêtes de type JSON.
3 réponses
j'ai enfin trouvé une réponse. En gros, je n'ai pas besoin du truc MediaTypeFormatter
, qui n'est pas conçu pour être utilisé dans l'environnement MVC, mais dans ASP.NET API Web, c'est pourquoi je ne vois pas ces références et namespaces (soit dit en passant, ceux-ci sont inclus dans le paquet NuGet Microsoft.AspNet.WeApi
).
la solution est d'utiliser une usine de fournisseur de valeur personnalisée. Voici le code nécessaire.
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;
}
}
Et vous pouvez l'utiliser comme ceci dans votre Application_Start
méthode:
// remove default implementation
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
// add our custom one
ValueProviderFactories.Factories.Add(new JsonNetValueProviderFactory());
Ici est le post qui m'a indiqué la bonne direction, et également a donné une bonne explication sur la valeur des fournisseurs et modelbinders.
j'ai eu un problème avec ce bien. Je postais JSON à une action, mais mes noms de JsonProperty ont été ignorés. Ainsi, mes propriétés de modèle étaient toujours vides.
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; }
}
je poste à une action en utilisant cette fonction jQuery personnalisée:
(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));
et je l'appelle comme ceci:
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
});
malheureusement, seul foo était jamais lié (étrange, puisque le cas n'est pas le même, mais je suppose que le défaut modelbinder n'est pas sensible à la casse)
[HttpPost]
public JsonNetResult MyAction(MyModel model)
{
...
}
la solution a fini par être assez simple
je viens d'implémenter une version générique du modèle binder de Dejan qui fonctionne très bien pour moi. Il pourrait probablement utiliser quelques chèques fictifs (comme s'assurer que la requête est bien application/json), mais il fait l'affaire en ce moment.
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);
}
}
quand je veux l'utiliser sur une action spécifique, je dites-lui que je veux utiliser mon personnalisée Json.Net modèle de classeur:
[HttpPost]
public JsonNetResult MyAction([ModelBinder(typeof(JsonNetModelBinder))] MyModel model)
{
...
}
maintenant mes attributs [Jsonproperty (PropertyName ="")] ne sont plus ignorés sur MyModel et tout est lié correctement!
dans mon cas, je devais désérialiser les objets complexes, y compris les interfaces et les types chargés dynamiquement, etc. ainsi, fournir un fournisseur de valeur personnalisé ne fonctionne pas car MVC doit encore essayer de comprendre comment instancier les interfaces et échoue ensuite.
comme mes objets étaient déjà correctement annotés pour travailler avec Json.NET, j'ai pris une voie différente: j'ai mis en œuvre un modèle personnalisé de classeur et utilisé Json.NET pour désérialiser explicitement les données du corps de la requête comme ceci:
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://stackoverflow.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, ...);
}
}
le classeur de modèle personnalisé est enregistré dans Global.asax.cs
:
ModelBinders.Binders.Add(typeof(MyClass), new CustomModelBinder();