Comment utiliser la liaison de modèle Json.NET for JSON dans un projet MVC5?

J’ai cherché une réponse ou un exemple sur Internet, mais je n’ai pas encore trouvé de réponse. Je voudrais simplement changer le sérialiseur JSON par défaut qui est utilisé pour désérialiser JSON lors de la liaison de modèle à la bibliothèque JSON.NET.

J’ai trouvé cette publication SO, mais je ne peux pas la mettre en œuvre jusqu’à présent. Je ne peux même pas voir l’ System.Net.Http.Formatters noms System.Net.Http.Formatters , pas plus que GlobalConfiguration .

Qu’est-ce que je rate?

METTRE À JOUR

J’ai un projet ASP.NET MVC, il s’agissait essentiellement d’un projet MVC3. Actuellement, je cible .NET 4.5 et j’utilise ASP.NET MVC 5 et les packages NuGet associés.

Je ne vois pas l’assembly System.Web.Http, ni aucun espace de noms similaire. Dans ce contexte, je voudrais injecter JSON.NET à utiliser comme classeur de modèle par défaut pour les demandes de type JSON.

J’ai enfin trouvé une réponse. Fondamentalement, je n’ai pas besoin du matériel MediaTypeFormatter , qui n’est pas conçu pour être utilisé dans l’environnement MVC, mais dans les API Web ASP.NET, c’est pourquoi je ne vois pas ces références et ces espaces de noms (d’ailleurs, ceux-ci sont inclus dans Microsoft.AspNet.WeApi NuGet).

La solution consiste à utiliser une fabrique de fournisseur de valeur personnalisée. Voici le code requirejs.

  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", SsortingngComparison.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>(JSONReader); else JSONObject = JSONSerializer.Deserialize(JSONReader); // create a backing store to hold all properties for this deserialization var backingStore = new Dictionary(SsortingngComparer.OrdinalIgnoreCase); // add all properties to this backing store AddToBackingStore(backingStore, Ssortingng.Empty, JSONObject); // return the object in a dictionary value provider so the MVC understands it return new DictionaryValueProvider(backingStore, CultureInfo.CurrentCulture); } private static void AddToBackingStore(Dictionary backingStore, ssortingng prefix, object value) { var d = value as IDictionary; 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 méthode Application_Start :

 // remove default implementation ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType().FirstOrDefault()); // add our custom one ValueProviderFactories.Factories.Add(new JsonNetValueProviderFactory()); 

Voici le message qui m'a orienté dans la bonne direction, et celui-ci a également fourni une bonne explication sur les fournisseurs de valeur et les modelbinders.

J’ai eu un tel problème avec cela aussi. Je publiais JSON pour une action, mais mes noms 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 sur 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.ssortingngify(data); if (dataType !== undefined) o.dataType = dataType; return $.ajax(o); }; }(jQuery)); 

Et j’appelle ça comme ça:

 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 a déjà été lié (impair, car le cas n’est pas le même, mais je suppose que le classeur par défaut 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 classeur de modèle de Dejan qui fonctionne très bien pour moi. Il pourrait probablement utiliser des vérifications fictives (comme s’assurer que la requête est bien application / json), mais c’est le cas actuellement.

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

Lorsque je veux l’utiliser pour une action spécifique, je lui dis simplement que je veux utiliser mon classeur de modèles Json.Net personnalisé à la place:

 [HttpPost] public JsonNetResult MyAction([ModelBinder(typeof(JsonNetModelBinder))] MyModel model) { ... } 

Maintenant, mes atsortingbuts [JsonProperty (PropertyName = “”)] ne sont plus ignorés sur MyModel et tout est lié correctement!

Dans mon cas, je devais désérialiser des objects complexes, notamment des interfaces, des types chargés dynamicment, etc., de sorte que la fourniture d’un fournisseur de valeur personnalisé ne fonctionne pas, car MVC doit encore essayer de savoir comment instancier des interfaces, puis échouer.

Comme mes objects étaient déjà correctement annotés pour fonctionner avec Json.NET, j’ai emprunté un chemin différent: j’ai implémenté un classeur de modèle personnalisé et utilisé Json.NET pour désérialiser explicitement les données du corps de la requête, comme suit:

 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); ssortingng json = readStream.ReadToEnd(); return JsonConvert.DeserializeObject(json, ...); } } 

Le classeur de modèle personnalisé est enregistré dans Global.asax.cs :

  ModelBinders.Binders.Add(typeof(MyClass), new CustomModelBinder();