2012-03-30 17 views
24

Introduzione:eccezione JsonMaxLength sulla deserializzazione grande json oggetti

applicazione Web, ASP.NET MVC 3, un'azione di controllo che accetta un'istanza della classe POCO modello con (potenzialmente) campo grande.

classe

Modello:

public class View 
{ 
    [Required] 
    [RegularExpression(...)] 
    public object name { get; set; } 
    public object details { get; set; } 
    public object content { get; set; } // the problem field 
} 

Action Controller:

[ActionName(...)] 
[Authorize(...)] 
[HttpPost] 
public ActionResult CreateView(View view) 
{ 
    if (!ModelState.IsValid) { return /*some ActionResult here*/;} 
    ... //do other stuff, create object in db etc. return valid result 
} 

Problema:

Un'azione dovrebbe essere in grado di accettare grandi oggetti JSON (almeno fino a centinaia di megabyte in una singola richiesta e non è uno scherzo). Per impostazione predefinita, ho incontrato diverse restrizioni come httpRuntime maxRequestLength ecc., Tutte risolte tranne MaxJsonLengh, il che significa che il valore predefinito di ValueProviderFactory per JSON non è in grado di gestire tali oggetti.

provato:

Impostazione

<system.web.extensions> 
    <scripting> 
     <webServices> 
     <jsonSerialization maxJsonLength="2147483647"/> 
     </webServices> 
    </scripting> 
    </system.web.extensions> 
  • non aiuta.

creare il mio personalizzato ValueProviderFactory come descritto nel @ risposta di Darin qui:

JsonValueProviderFactory throws "request too large"

  • anche fallito perché non ho possibilità di utilizzare JSON.Net (per ragioni non tecniche) . Ho provato a implementare la corretta deserializzazione qui, ma a quanto pare è un po 'al di sopra della mia conoscenza (ancora). Sono stato in grado di deserializzare la mia stringa JSON su Dictionary<String,Object> qui, ma non è quello che voglio: voglio deserializzare i miei deliziosi oggetti POCO e usarli come parametri di input per le azioni.

Quindi, le domande:

  1. qualcuno conosce il modo migliore per superare il problema senza implementare universale personalizzato ValueProviderFactory?
  2. Esiste la possibilità di specificare per quale specifico controller e azione voglio utilizzare il mio valore personalizzatoProviderFactory? Se conosco l'azione in anticipo di quanto sarò in grado di deserializzare JSON in POCO senza molta codifica in ValueProviderFactory ...
  3. Sto anche pensando di implementare un ActionFilter personalizzato per quel problema specifico, ma penso che sia un po 'brutto.

Chiunque può suggerire una buona soluzione?

risposta

58

Il JsonValueProviderFactory integrato ignora l'impostazione <jsonSerialization maxJsonLength="50000000"/>.Quindi, si potrebbe scrivere una fabbrica personalizzato utilizzando l'implementazione built-in:

public sealed class MyJsonValueProviderFactory : ValueProviderFactory 
{ 
    private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value) 
    { 
     IDictionary<string, object> d = value as IDictionary<string, object>; 
     if (d != null) 
     { 
      foreach (KeyValuePair<string, object> entry in d) 
      { 
       AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value); 
      } 
      return; 
     } 

     IList l = value as IList; 
     if (l != null) 
     { 
      for (int i = 0; i < l.Count; i++) 
      { 
       AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]); 
      } 
      return; 
     } 

     // primitive 
     backingStore[prefix] = value; 
    } 

    private static object GetDeserializedObject(ControllerContext controllerContext) 
    { 
     if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) 
     { 
      // not JSON request 
      return null; 
     } 

     StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); 
     string bodyText = reader.ReadToEnd(); 
     if (String.IsNullOrEmpty(bodyText)) 
     { 
      // no JSON data 
      return null; 
     } 

     JavaScriptSerializer serializer = new JavaScriptSerializer(); 
     serializer.MaxJsonLength = 2147483647; 
     object jsonData = serializer.DeserializeObject(bodyText); 
     return jsonData; 
    } 

    public override IValueProvider GetValueProvider(ControllerContext controllerContext) 
    { 
     if (controllerContext == null) 
     { 
      throw new ArgumentNullException("controllerContext"); 
     } 

     object jsonData = GetDeserializedObject(controllerContext); 
     if (jsonData == null) 
     { 
      return null; 
     } 

     Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); 
     AddToBackingStore(backingStore, String.Empty, jsonData); 
     return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture); 
    } 

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

L'unica modifica ho paragonato alla fabbrica di default è l'aggiunta la seguente riga:

serializer.MaxJsonLength = 2147483647; 

Purtroppo questa fabbrica non è estensibile a tutti, materiale sigillato quindi ho dovuto ricrearlo.

e nel tuo Application_Start:

ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<System.Web.Mvc.JsonValueProviderFactory>().FirstOrDefault()); 
ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory()); 
+2

Ci sono molte persone che si occupano di questo argomento e questa è l'unica soluzione che ho trovato che funzionasse nella mia applicazione MVC4. Grazie! – stitz

+2

Super! Funziona benissimo per legare oggetti JSON di grandi dimensioni. Per le richieste GET con un oggetto Json di grandi dimensioni sto utilizzando la classe qui: http://brianreiter.org/2011/01/03/custom-jsonresult-class-for-asp-net-mvc-to-avoid-maxjsonlength-exceeded -exception/ –

+2

Se hai problemi a pubblicare strutture JSON di grandi dimensioni su un post ajax su MVC4 Controller, prova questo prima di qualsiasi altra cosa. Ho provato molti altri rimproveri senza fortuna e questo da solo mi ha salvato la settimana. Grazie mille @DarinDimitrov! –

16

ho trovato che il maxRequestLength non ha risolto il problema però. Ho risolto il problema con l'impostazione di seguito. E 'più pulita di dover implementare una consuetudine ValueProviderFactory

<appSettings> 
    <add key="aspnet:MaxJsonDeserializerMembers" value="150000" /> 
</appSettings> 

credito va alle seguenti domande:

JsonValueProviderFactory throws "request too large"

Getting "The JSON request was too large to be deserialized"

Questa impostazione si riferisce, ovviamente, ad un modello di JSON molto complesso e non le dimensioni reali.

+2

Questo potrebbe essere utile per qualcuno, sebbene il mio problema originale non fosse influenzato da questa impostazione. Hai avuto un documento JSON davvero complesso con molti elementi, quindi l'impostazione ti ha aiutato - e ho avuto un documento piuttosto semplice con contenuti codificati di grandi dimensioni per alcuni valori. –

+0

Hai ragione al 100%. Ho modificato la mia risposta in quanto tale. – Oliver

+0

Oliver - Hai raggiunto il numero massimo di elementi nel dizionario JSON anziché la lunghezza o la complessità del contenuto. C'è una limitazione di 1000 elementi per impostazione predefinita in JavaScriptSerializer. La tua risposta è corretta per questo scenario ma ecco il link sull'argomento https://msdn.microsoft.com/en-us/library/hh975440.aspx –

3

La soluzione di Darin Dimitrov funziona per me, ma ho bisogno di ripristinare la posizione del flusso della richiesta prima di leggerlo, aggiungendo questa linea:

controllerContext.HttpContext.Request.InputStream.Position = 0;

Così ora, il metodo GetDeserializedObject assomiglia a questo :

private static object GetDeserializedObject(ControllerContext controllerContext) 
    { 
     if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) 
     { 
      // not JSON request 
      return null; 
     } 
     controllerContext.HttpContext.Request.InputStream.Position = 0; 
     StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); 
     string bodyText = reader.ReadToEnd(); 
     if (String.IsNullOrEmpty(bodyText)) 
     { 
      // no JSON data 
      return null; 
     } 

     JavaScriptSerializer serializer = new JavaScriptSerializer(); 
     serializer.MaxJsonLength = 2147483647; 
     object jsonData = serializer.DeserializeObject(bodyText); 
     return jsonData; 
    } 
Problemi correlati