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.

22
demandé sur Community 2014-06-02 17:02:28

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.

25
répondu Zoltán Tamási 2014-06-03 09:50:00

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!

12
répondu Jason Butera 2015-12-01 21:26:57

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();
0
répondu Dejan 2015-11-02 12:51:42