Envoyer des données JSON à ASP.NET MVC

im essayant d'obtenir une liste d'éléments de ligne à une page Web en utilisant JSON, qui sera ensuite manipulée et renvoyée au serveur par la requête ajax en utilisant la même structure JSON qui est arrivée (sauf avoir eu un champ valeurs changées).

recevoir des données du serveur est facile, manipulation encore plus facile! mais renvoyer les données JSON au serveur pour les sauvegarder... le suicide de temps! SVP quelqu'un peut vous aider!

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 en tant que données de post sérialisées. Le binder de modèle automatisé essaie de lier IEnumerable<LineItem> incoming et obtient étonnamment le résultat IEnumerable a le nombre correct de LineItems - il ne suffit pas de les peupler avec des données.

SOLUTION

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

Côté Serveur

le deserialiseur ci-dessous doit faire référence à System.Runtime.Serialization et 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 json.org 's stringify méthode, disponible dans cette dépendance https://github.com/douglascrockford/JSON-js/blob/master/json2.js (qui est de 2,5 kb lorsqu'elle est réduite)

        $.ajax({
            type: 'POST',
            url: '/Controller/Action',
            data: { 'items': JSON.stringify(lineItems), 'id': documentId }
        });
56
demandé sur Jimbo 2010-11-12 14:25:57

8 réponses

regardez L'article de Phil Haack sur model binding JSON data . Le problème est que le classeur de modèle par défaut ne sérialise pas JSON correctement. Vous avez besoin d'une sorte de ValueProvider ou vous pouvez écrire un modèle personnalisé classeur:

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 Global.fichier asax:

ModelBinders.Binders.DefaultBinder = new JsonModelBinder();

aussi, ce code utilise le type de contenu = 'application / json' donc assurez-vous que vous définissez que dans jquery like so:

$.ajax({
    dataType: "json",
    contentType: "application/json",            
    type: 'POST',
    url: '/Controller/Action',
    data: { 'items': JSON.stringify(lineItems), 'id': documentId }
});
27
répondu BeRecursive 2013-09-13 19:35:33

la façon La plus simple de le faire

je vous invite à lire ce billet de blog qui traite directement votre problème.

utiliser des classeurs de modèles personnalisés n'est pas vraiment sage comme Phil Haack l'a souligné (son billet de blog est également lié dans le billet de blog supérieur).

fondamentalement, vous avez trois options:

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

  2. écrivez 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 le blog post, qui prépare tout objet JSON (même tableaux qui sera lié à IList<T> et dates qui seront correctement parse sur le côté du serveur comme DateTime instances) qui seront comprises par Asp.net MVC default model binder.

de tous les trois, le dernier est le plus simple et n'interfère pas avec Asp.net MVC travaux intérieurs réduisant ainsi la surface de bug possible. En utilisant cette technique décrite dans le blog post sera correctement données lient vos paramètres d'action de type fort et de valider ainsi. Il s'agit donc essentiellement d'une situation gagnant-gagnant .

12
répondu Robert Koritnik 2011-08-11 20:50:22

dans MVC3 ils ont ajouté ceci.

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

vous finirez avec quelque chose comme ça

ValueProviderFactories.Factories.Add(new JsonValueProviderFactory())
8
répondu Kenny Eliasson 2010-11-12 12:23:43

j'ai résolu ce problème en suivant les conseils de vestigal ici:

puis-je définir une longueur illimitée pour maxJsonLength dans web.config?

quand j'avais besoin de poster un gros json à une action dans un contrôleur, j'obtenais la fameuse "erreur lors de la desérialisation en utilisant le JavaScriptSerializer JSON. La longueur de la chaîne dépasse la valeur définie sur la propriété maxJsonLength.\r\nParameter nom: saisissez la valeur de fournisseur".

ce que j'ai fait c'est créer un nouveau ValueProviderFactory, Largejsonvaluproviderfactory, et mettre le MaxJsonLength = Int32.MaxValue dans la méthode GetDeserializedObject

public sealed class LargeJsonValueProviderFactory : ValueProviderFactory
{
    private static void AddToBackingStore(LargeJsonValueProviderFactory.EntryLimitedDictionary backingStore, string prefix, object value)
    {
        IDictionary<string, object> dictionary = value as IDictionary<string, object>;
        if (dictionary != null)
        {
            foreach (KeyValuePair<string, object> keyValuePair in (IEnumerable<KeyValuePair<string, object>>) dictionary)
                LargeJsonValueProviderFactory.AddToBackingStore(backingStore, LargeJsonValueProviderFactory.MakePropertyKey(prefix, keyValuePair.Key), keyValuePair.Value);
        }
        else
        {
            IList list = value as IList;
            if (list != null)
            {
                for (int index = 0; index < list.Count; ++index)
                    LargeJsonValueProviderFactory.AddToBackingStore(backingStore, LargeJsonValueProviderFactory.MakeArrayKey(prefix, index), list[index]);
            }
            else
                backingStore.Add(prefix, value);
        }
    }

    private static object GetDeserializedObject(ControllerContext controllerContext)
    {
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
            return (object) null;
        string end = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();
        if (string.IsNullOrEmpty(end))
            return (object) null;

        var serializer = new JavaScriptSerializer {MaxJsonLength = Int32.MaxValue};

        return serializer.DeserializeObject(end);
    }

    /// <summary>Returns a JSON value-provider object for the specified controller context.</summary>
    /// <returns>A JSON value-provider object for the specified controller context.</returns>
    /// <param name="controllerContext">The controller context.</param>
    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        if (controllerContext == null)
            throw new ArgumentNullException("controllerContext");
        object deserializedObject = LargeJsonValueProviderFactory.GetDeserializedObject(controllerContext);
        if (deserializedObject == null)
            return (IValueProvider) null;
        Dictionary<string, object> dictionary = new Dictionary<string, object>((IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
        LargeJsonValueProviderFactory.AddToBackingStore(new LargeJsonValueProviderFactory.EntryLimitedDictionary((IDictionary<string, object>) dictionary), string.Empty, deserializedObject);
        return (IValueProvider) new DictionaryValueProvider<object>((IDictionary<string, object>) dictionary, CultureInfo.CurrentCulture);
    }

    private static string MakeArrayKey(string prefix, int index)
    {
        return prefix + "[" + index.ToString((IFormatProvider) CultureInfo.InvariantCulture) + "]";
    }

    private static string MakePropertyKey(string prefix, string propertyName)
    {
        if (!string.IsNullOrEmpty(prefix))
            return prefix + "." + propertyName;
        return propertyName;
    }

    private class EntryLimitedDictionary
    {
        private static int _maximumDepth = LargeJsonValueProviderFactory.EntryLimitedDictionary.GetMaximumDepth();
        private readonly IDictionary<string, object> _innerDictionary;
        private int _itemCount;

        public EntryLimitedDictionary(IDictionary<string, object> innerDictionary)
        {
            this._innerDictionary = innerDictionary;
        }

        public void Add(string key, object value)
        {
            if (++this._itemCount > LargeJsonValueProviderFactory.EntryLimitedDictionary._maximumDepth)
                throw new InvalidOperationException("JsonValueProviderFactory_RequestTooLarge");
            this._innerDictionary.Add(key, value);
        }

        private static int GetMaximumDepth()
        {
            NameValueCollection appSettings = ConfigurationManager.AppSettings;
            if (appSettings != null)
            {
                string[] values = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers");
                int result;
                if (values != null && values.Length > 0 && int.TryParse(values[0], out result))
                    return result;
            }
            return 1000;
        }
    }
}

puis, dans la méthode Application_Start de Global.asax.cs, remplacer le ValueProviderFactory par le nouveau:

protected void Application_Start()
    {
        ...

        //Add LargeJsonValueProviderFactory
        ValueProviderFactory jsonFactory = null;
        foreach (var factory in ValueProviderFactories.Factories)
        {
            if (factory.GetType().FullName == "System.Web.Mvc.JsonValueProviderFactory")
            {
                jsonFactory = factory;
                break;
            }
        }

        if (jsonFactory != null)
        {
            ValueProviderFactories.Factories.Remove(jsonFactory);
        }

        var largeJsonValueProviderFactory = new LargeJsonValueProviderFactory();
        ValueProviderFactories.Factories.Add(largeJsonValueProviderFactory);
    }
3
répondu MFA 2017-05-23 10:31:26

vous pouvez essayer ceux-ci. 1. stringifiez votre objet JSON avant d'appeler l'action du serveur via ajax 2. désérialiser la chaîne dans l'action, puis utiliser les données comme un dictionnaire.

exemple Javascript ci-dessous (envoi de L'objet JSON)"

$.ajax(
   {
       type: 'POST',
       url: 'TheAction',
       data: { 'data': JSON.stringify(theJSONObject) 
   }
})

Action (c#) échantillon ci-dessous

[HttpPost]
public JsonResult TheAction(string data) {

       string _jsonObject = data.Replace(@"\", string.Empty);
       var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();           
        Dictionary<string, string> jsonObject = serializer.Deserialize<Dictionary<string, string>>(_jsonObject);


        return Json(new object{status = true});

    }
2
répondu Subathiran Subramaniam 2015-12-15 05:07:02

Si vous avez d'autres données JSON comme une chaîne de caractères (par exemple, '[{"id":1,"name":"Charles"},{"id":8,"nom":"Jean"},{"id":13,"nom":"Sally"}]')

alors j'utiliserais JSON.net et utilisez Linq à JSON pour obtenir 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
répondu David Henderson 2010-11-15 11:28:00

la réponse de BeRecursive est celle que j'ai utilisée, afin que nous puissions standardiser sur Json.Net (nous avons MVC5 et WebApi 5 -- WebApi 5 utilise déjà Json.Net), but I found an issue. Lorsque vous avez des paramètres dans votre route vers laquelle vous postez, MVC essaie d'appeler le binder du model pour les valeurs URI, et ce code tentera de lier le JSON posté à ces valeurs.

exemple:

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

la fonction BindModel se fait appeler trois fois, bombarder sur le premier, comme il tente de lier le JSON à customerId avec l'erreur: Error reading integer. Unexpected token: StartObject. Path '', line 1, position 1.

j'ai ajouté ce bloc de code au début de BindModel :

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

le ValueProvider, heureusement, a des valeurs de route compris par le temps qu'il arrive à cette méthode.

0
répondu YakkoWarner 2017-05-23 12:34:34

j'ai résolu en utilisant une désérialisation "manuelle". Je vais vous expliquer en code

public ActionResult MyMethod([System.Web.Http.FromBody] MyModel model)
{
 if (module.Fields == null && !string.IsNullOrEmpty(Request.Form["fields"]))
 {
   model.Fields = JsonConvert.DeserializeObject<MyFieldModel[]>(Request.Form["fields"]);
 }
 //... more code
}
0
répondu Antonio Santise 2018-01-20 17:47:39