59 votes

Poster des données JSON dans ASP.NET MVC

J'essaie d'obtenir une liste d'articles de ligne sur une page web en utilisant JSON, qui sera ensuite manipulée et renvoyée au serveur par une requête ajax en utilisant la même structure JSON qu'à l'arrivée (sauf que la valeur d'un champ a été modifiée).

Recevoir des données du serveur est facile, la manipulation encore plus facile ! mais renvoyer ces données JSON au serveur pour les sauvegarder... temps de suicide ! S'il vous plaît, que quelqu'un m'aide !

Javascript

var lineitems;

// get data from server
$.ajax({
    url: '/Controller/GetData/',
    success: function(data){
        lineitems = data;
    }
});

// post data to server
$.ajax({
    url: '/Controller/SaveData/',
    data: { incoming: lineitems }
});

C# - Objets

public class LineItem{
    public string reference;
    public int quantity;
    public decimal amount;
}

C# - Contrôleur

public JsonResult GetData()
{
    IEnumerable<LineItem> lineItems = ... ; // a whole bunch of line items
    return Json(lineItems);
}

public JsonResult SaveData(IEnumerable<LineItem> incoming){
    foreach(LineItem item in incoming){
        // save some stuff
    }
    return Json(new { success = true, message = "Some message" });
}

Les données arrivent au serveur sous forme de données post sérialisées. Le liant de modèle automatisé essaie de lier IEnumerable<LineItem> incoming et obtient étonnamment le résultat suivant IEnumerable a le nombre correct de LineItems - il ne les remplit pas avec des données.

SOLUTION

En utilisant les réponses d'un certain nombre de sources, principalement djch sur un autre post de stackoverflow et BeRecursive ci-dessous, j'ai résolu mon problème en utilisant deux méthodes principales.

Côté serveur

Le désérialiseur ci-dessous nécessite une référence à System.Runtime.Serialization y using System.Runtime.Serialization.Json

private T Deserialise<T>(string json)
{
    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
    {
        var serialiser = new DataContractJsonSerializer(typeof(T));
        return (T)serialiser.ReadObject(ms);
    }
}

public void Action(int id, string items){
    IEnumerable<LineItem> lineitems = Deserialise<IEnumerable<LineItem>>(items);
    // do whatever needs to be done - create, update, delete etc.
}

Côté client

Il utilise la méthode stringify de json.org, disponible dans cette dépendance. https://github.com/douglascrockford/JSON-js/blob/master/json2.js (qui fait 2.5kb une fois minifié)

$.ajax({
    type: 'POST',
    url: '/Controller/Action',
    data: { 'items': JSON.stringify(lineItems), 'id': documentId }
});

0 votes

Essayez d'envelopper les données dans une chaîne, par exemple : data : "{contenu :" + contentString + "}"

32voto

BeRecursive Points 4334

Jetez un coup d'œil au billet de Phil Haack sur modèle liant les données JSON . Le problème est que le classeur de modèles par défaut ne sérialise pas JSON correctement. Vous avez besoin d'une sorte de ValueProvider OU vous pouvez écrire un model binder personnalisé :

using System.IO;
using System.Web.Script.Serialization;

public class JsonModelBinder : DefaultModelBinder {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
            if(!IsJSONRequest(controllerContext)) {
                return base.BindModel(controllerContext, bindingContext);
            }

            // Get the JSON data that's been posted
            var request = controllerContext.HttpContext.Request;
            //in some setups there is something that already reads the input stream if content type = 'application/json', so seek to the begining
            request.InputStream.Seek(0, SeekOrigin.Begin);
            var jsonStringData = new StreamReader(request.InputStream).ReadToEnd();

            // Use the built-in serializer to do the work for us
            return new JavaScriptSerializer()
                .Deserialize(jsonStringData, bindingContext.ModelMetadata.ModelType);

            // -- REQUIRES .NET4
            // If you want to use the .NET4 version of this, change the target framework and uncomment the line below
            // and comment out the above return statement
            //return new JavaScriptSerializer().Deserialize(jsonStringData, bindingContext.ModelMetadata.ModelType);
        }

        private static bool IsJSONRequest(ControllerContext controllerContext) {
            var contentType = controllerContext.HttpContext.Request.ContentType;
            return contentType.Contains("application/json");
        }
    }

public static class JavaScriptSerializerExt {
        public static object Deserialize(this JavaScriptSerializer serializer, string input, Type objType) {
            var deserializerMethod = serializer.GetType().GetMethod("Deserialize", BindingFlags.NonPublic | BindingFlags.Static);

            // internal static method to do the work for us
            //Deserialize(this, input, null, this.RecursionLimit);

            return deserializerMethod.Invoke(serializer,
                new object[] { serializer, input, objType, serializer.RecursionLimit });
        }
    }

Et dites à MVC de l'utiliser dans votre fichier Global.asax :

ModelBinders.Binders.DefaultBinder = new JsonModelBinder();

De plus, ce code utilise le type de contenu = 'application/json'. Assurez-vous donc de le définir dans jquery comme suit :

$.ajax({
    dataType: "json",
    contentType: "application/json",            
    type: 'POST',
    url: '/Controller/Action',
    data: { 'items': JSON.stringify(lineItems), 'id': documentId }
});

0 votes

C'est génial, merci. Un problème cependant - dès que vous dites à votre requête $.ajax() que son contentType = 'application/json' d'une manière ou d'une autre, les données qu'il envoie au serveur sont complètement foutues :(

0 votes

J'ai ce code qui fonctionne moi-même, donc je ne suis pas sûr de ce que vous voulez dire.

2 votes

@Jimbo J'ai eu le même problème avec la demande. Ce n'était pas la faute de jquery, quelque chose d'autre dans votre application (et la mienne) lisait le flux d'entrée s'il était de type 'application/json'. C'est pourquoi le flux d'entrée semblait vide. J'ai modifié le code pour appeler seek sur le flux d'entrée et tout va bien maintenant.

13voto

Robert Koritnik Points 45499

La manière la plus simple de procéder est la suivante

Je vous invite à lire cet article de blog qui répond directement à votre problème.

L'utilisation de classeurs de modèles personnalisés n'est pas vraiment judicieuse, comme l'a souligné Phil Haack (son article de blog est également lié à l'article de blog supérieur).

En gros, vous avez trois options :

  1. Écrire un JsonValueProviderFactory et utiliser une bibliothèque côté client comme json2.js pour communiquer directement avec JSON.

  2. Écrire un JQueryValueProviderFactory qui comprend la transformation d'objet JSON de jQuery qui se produit dans $.ajax ou

  3. Utilisez le plugin jQuery très simple et rapide décrit dans l'article de blog, qui prépare n'importe quel objet JSON (même tableaux qui sera lié à IList<T> y dates qui sera correctement analysé du côté serveur en tant que DateTime ) qui seront comprises par le liant de modèle par défaut d'Asp.net MVC.

De ces trois techniques, la dernière est la plus simple et n'interfère pas avec le fonctionnement interne d'Asp.net MVC, ce qui réduit la surface de bogue possible. L'utilisation de cette technique décrite dans l'article de blog permettra de lier correctement les données de vos paramètres d'action de type fort et de les valider également. Il s'agit donc essentiellement d'un Une situation gagnant-gagnant.

2 votes

Fantastique, ce plugin était exactement ce que je cherchais et il a fonctionné sans problème ! Merci !

0 votes

C'est bien mais j'ai besoin de quelque chose que je puisse utiliser avec Angular 2. Pas jQuery.

0 votes

$serjsagan quel genre de problèmes rencontrez-vous dans Angular, car je n'ai rien eu à faire lors de l'envoi de données depuis Angular vers l'API Web.

9voto

Kenny Eliasson Points 1166

Dans MVC3, ils ont ajouté ceci.

Mais ce qui est encore plus agréable, c'est que le code source de MVC étant ouvert, vous pouvez récupérer le ValueProvider et l'utiliser vous-même dans votre propre code (si vous n'êtes pas encore sur MVC3).

Vous obtiendrez quelque chose comme ceci

ValueProviderFactories.Factories.Add(new JsonValueProviderFactory())

0 votes

C'est vrai, mais il n'a pas spécifié MVC3 et il est évident qu'il ne l'utilise pas puisque ça ne fonctionne pas :)

2 votes

Oui, c'est ce que je voulais dire en mentionnant que puisque MVC3 (et toutes les versions de MVC) est open-source, vous pouvez récupérer la source et copier-coller sur la JsonValueProviderFactory.

0 votes

Je pense que cela ne fonctionne pas simplement parce que le contentType doit être changé en application/json, par défaut il est réglé sur form encoded.

0voto

djch Points 940

Si vos données JSON arrivent sous forme de chaîne (par exemple, '[{"id":1, "name" : "Charles"},{"id":8, "name" : "John"},{"id":13, "name" : "Sally"}]]')

Alors j'utiliserais JSON.net et utiliser Linq to JSON pour récupérer les valeurs...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

    if (Request["items"] != null)
    {
        var items = Request["items"].ToString(); // Get the JSON string
        JArray o = JArray.Parse(items); // It is an array so parse into a JArray
        var a = o.SelectToken("[0].name").ToString(); // Get the name value of the 1st object in the array
        // a == "Charles"
    }
}
}

0 votes

Merci pour cet article - j'espérais éviter deux choses découlant de ce qui précède : 1. L'analyse manuelle de l'objet de destination (même si c'est à partir d'un tableau, ce qui serait plus facile) et 2. L'utilisation d'une autre dépendance (Newtonsoft).

0voto

YakkoWarner Points 41

La réponse de BeRecursive est celui que j'ai utilisé, afin que nous puissions standardiser sur Json.Net (nous avons MVC5 et WebApi 5 -- WebApi 5 utilise déjà Json.Net), mais j'ai trouvé un problème. Lorsque vous avez des paramètres dans votre route à laquelle vous POSTing, MVC essaie d'appeler le modèle binder pour les valeurs URI, et ce code va essayer de lier le JSON affiché à ces valeurs.

Exemple :

[HttpPost]
[Route("Customer/{customerId:int}/Vehicle/{vehicleId:int}/Policy/Create"]
public async Task<JsonNetResult> Create(int customerId, int vehicleId, PolicyRequest policyRequest)

El BindModel est appelée trois fois, en bombardant la première, alors qu'elle tente de lier le JSON à la fonction customerId avec l'erreur : Error reading integer. Unexpected token: StartObject. Path '', line 1, position 1.

J'ai ajouté ce bloc de code en haut de la page d'accueil du site. BindModel :

if (bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null) {
    return base.BindModel(controllerContext, bindingContext);
}

Heureusement, le ValueProvider connaît déjà les valeurs des itinéraires avant d'arriver à cette méthode.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X