2012-03-30 15 views
24

Intro:JsonMaxLength wyjątek na deserializacji dużą json obiektów

aplikacji sieciowej ASP.NET MVC 3, działanie sterownika, który przyjmuje wystąpienie POCO klasy modelu z (potencjalnie) dużym obszarze.

klasa

Model:

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

Controller działanie:

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

Problem:

Skarga powinna być w stanie przyjąć duże obiekty JSON (co najmniej do setki megabajtów jedna prośba i to nie żart). Domyślnie spotkałem się z kilkoma ograniczeniami, takimi jak httpRuntime maxRequestLength itd. - wszystkie rozwiązane z wyjątkiem MaxJsonLengh - co oznacza, że ​​domyślny ValueProviderFactory dla JSON nie jest w stanie obsługiwać takich obiektów.

Tried:

Ustawianie

<system.web.extensions> 
    <scripting> 
     <webServices> 
     <jsonSerialization maxJsonLength="2147483647"/> 
     </webServices> 
    </scripting> 
    </system.web.extensions> 
  • nie pomaga.

Tworzenie własną niestandardową ValueProviderFactory jak opisano w @ odpowiedź Darin tu:

JsonValueProviderFactory throws "request too large"

  • również nie, bo nie mam możliwość korzystania Json.NET (z przyczyn nietechnicznych) . Próbowałem implementować poprawną deserializację tutaj, ale najwyraźniej jest to nieco ponad moją wiedzą (jeszcze). Byłem w stanie deserializować mój ciąg JSON do Dictionary<String,Object> tutaj, ale to nie jest to, czego chcę - chcę deserializować go do moich pięknych obiektów POCO i używać ich jako parametrów wejściowych dla akcji.

więc pytania:

  1. Każdy zna lepszy sposób rozwiązania problemu bez wdrożenia uniwersalny niestandardową ValueProviderFactory?
  2. Czy istnieje możliwość określenia, dla jakiego konkretnego kontrolera i działania chcę używać mojego niestandardowego ValueProviderFactory? Jeśli znam działania wcześniej niż będę w stanie deserializować JSON do POCO bez dużego kodowania w ValueProviderFactory ...
  3. Myślę także o implementacji niestandardowego ActionFiltra dla tego konkretnego problemu, ale myślę, że jest trochę brzydki.

Ktoś może zaproponować dobre rozwiązanie?

Odpowiedz

58

Wbudowany JsonValueProviderFactory ignoruje ustawienie <jsonSerialization maxJsonLength="50000000"/>.Więc można napisać niestandardowy fabrykę za pomocą wbudowanego w realizacji:

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

Jedyna modyfikacja zrobiłem porównaniu z fabrycznym jest dodanie następującej linii:

serializer.MaxJsonLength = 2147483647; 

Niestety ta fabryka nie jest w ogóle rozciągliwy, zapieczętowany materiał, więc musiałem go odtworzyć.

iw swoim Application_Start:

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

Jest wiele osób, które zajmują się tym tematem i jest to jedyne rozwiązanie, jakie mogłem znaleźć w mojej aplikacji MVC4. Dziękuję Ci! – stitz

+2

Super! Działa świetnie do wiązania dużego obiektu Json. W przypadku żądań GET z dużym obiektem Json używam tutaj klasy: http://brianreiter.org/2011/01/03/custom-jsonresult-class-for-asp-net-mvc-to-avoid-maxjsonlength-exceeded -exception/ –

+2

Jeśli masz problemy z publikowaniem dużych struktur Json nad postem ajaxowym do kontrolera MVC4, spróbuj tego przed innymi rzeczami. Próbowałem wielu innych aproaches bez powodzenia i tylko to uratowało mój tydzień. Wielkie dzięki @DarinDimitrov! –

16

okazało się, że maxRequestLength nie rozwiązuje problemu jednak. Rozwiązałem problem z ustawieniem poniżej. To jest czystsze niż konieczności wdrożenia niestandardowego ValueProviderFactory

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

zasługa następujących pytań:

JsonValueProviderFactory throws "request too large"

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

Ustawienie to oczywiście dotyczy wysoce złożonym modelu json a nie rzeczywisty rozmiar.

+2

Może to być przydatne dla kogoś, chociaż ustawienie to nie miało wpływu na mój pierwotny problem. Miałeś bardzo złożony dokument JSON z wieloma elementami - dzięki temu ustawienie pomogło ci - i miałem dość prosty dokument z dużą zakodowaną zawartością dla niektórych wartości. –

+0

Masz 100% racji. Poprawiłem moją odpowiedź jako taką. – Oliver

+0

Oliver - Udało Ci się trafić w maks. Liczbę elementów w słowniku json, a nie w długość i złożoność treści. Domyślnie w obiekcie JavaScriptSerializer jest ograniczenie do 1000 elementów. Twoja odpowiedź jest poprawna w tym scenariuszu, ale tutaj jest link do tematu https://msdn.microsoft.com/en-us/library/hh975440.aspx –

3

Rozwiązanie Darin Dimitrov pracuje dla mnie, ale muszę przedtem zresetować pozycję strumienia wniosek czytać, dodając wiersz:

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

Więc teraz, metoda GetDeserializedObject wygląda następująco :

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; 
    } 
Powiązane problemy