Intro:
Application Web, ASP.NET MVC 3, une action de contrôleur qui accepte une instance de classe de modèle POCO avec un champ (potentiellement) grand.
Classe de modèle:
public class View
{
[Required]
[RegularExpression(...)]
public object name { get; set; }
public object details { get; set; }
public object content { get; set; } // the problem field
}
Action du contrôleur:
[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
}
Problème:
Une action doit pouvoir accepter des objets JSON volumineux (au moins jusqu'à cent mégaoctets en une seule demande et ce n'est pas une blague). Par défaut, j'ai rencontré plusieurs restrictions comme httpRuntime maxRequestLength
etc. - tous résolus sauf MaxJsonLengh - ce qui signifie que ValueProviderFactory par défaut pour JSON n'est pas capable de gérer de tels objets.
Testé:
Réglage
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="2147483647"/>
</webServices>
</scripting>
</system.web.extensions>
Créer mon propre ValueProviderFactory personnalisé comme décrit dans la réponse de @ Darin ici:
JsonValueProviderFactory lève "demande trop grande"
Dictionary<String,Object>
ici, mais ce n'est pas ce que je veux - je veux le désérialiser à mes beaux objets POCO et les utiliser comme paramètres d'entrée pour les actions.Donc, les questions:
N'importe qui peut suggérer une bonne solution?
Le JsonValueProviderFactory intégré ignore le <jsonSerialization maxJsonLength="50000000"/>
réglage. Vous pouvez donc écrire une fabrique personnalisée en utilisant l'implémentation intégrée:
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;
}
}
La seule modification que j'ai faite par rapport à l'usine par défaut est l'ajout de la ligne suivante:
serializer.MaxJsonLength = 2147483647;
Malheureusement cette usine n'est pas du tout extensible, des trucs scellés donc j'ai dû la recréer.
et dans votre Application_Start
:
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<System.Web.Mvc.JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory());
J'ai trouvé que le maxRequestLength n'a pas résolu le problème cependant. J'ai résolu mon problème avec le paramètre ci-dessous. C'est plus propre que d'avoir à implémenter une valeur ValueProviderFactory
<appSettings>
<add key="aspnet:MaxJsonDeserializerMembers" value="150000" />
</appSettings>
Le mérite revient aux questions suivantes:
JsonValueProviderFactory lève "demande trop grande"
Obtenir "La requête JSON était trop volumineuse pour être désérialisée"
Ce paramètre concerne évidemment un modèle json très complexe et non la taille réelle.
La solution de Darin Dimitrov fonctionne pour moi mais j'ai besoin de réinitialiser la position du flux de la demande avant de la lire, en ajoutant cette ligne:
controllerContext.HttpContext.Request.InputStream.Position = 0;
Alors maintenant, la méthode GetDeserializedObject ressemble à ceci:
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;
}