J'ai cherché sur Internet une réponse ou un exemple, 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é ceci SO post, mais je ne peux pas l'implémenter jusqu'à présent, je ne peux même pas voir le System.Net.Http.Formatters
espace de noms, je ne vois pas non plus GlobalConfiguration
.
Qu'est-ce que je rate?
MISE À JOUR
J'ai un projet ASP.NET MVC, c'était essentiellement 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 pour être utilisé comme classeur de modèle par défaut pour les requêtes de type JSON.
J'ai enfin trouvé une réponse. Fondamentalement, je n'ai pas besoin des éléments MediaTypeFormatter
, qui ne sont pas conçus pour être utilisés 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, ce sont inclus dans le Microsoft.AspNet.WeApi
Package NuGet).
La solution consiste à utiliser une usine de fournisseur de valeur personnalisée. Voici le code requis.
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 orienté dans la bonne direction, et aussi celui-ci a donné une bonne explication sur les fournisseurs de valeur et les relieurs de modèles.
J'ai aussi eu un tel problème avec ça. Je publiais JSON dans une action, mais mes noms JsonProperty étaient 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 publie une action à l'aide de 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 ç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 était lié (étrange, 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 de mettre en œuvre une version générique du classeur de modèles de Dejan qui fonctionne très bien pour moi. Il pourrait probablement utiliser des vérifications factices (comme s'assurer que la demande est bien application/json), mais il fait l'affaire maintenant.
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 sur une action spécifique, je lui dis simplement que je veux utiliser mon classeur de modèle Json.Net personnalisé à la place:
[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, j'ai dû désérialiser des objets complexes, y compris des interfaces et des types chargés dynamiquement, etc., donc fournir un fournisseur de valeur personnalisé ne fonctionne pas car MVC a encore besoin d'essayer de comprendre comment instancier des interfaces, puis échoue.
Comme mes objets étaient déjà correctement annotés pour fonctionner avec Json.NET, j'ai pris un itinéraire 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 demande 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();