对大型json对象反序列化的JsonMaxLength异常

时间:2022-06-04 04:04:23

Intro:

人物介绍:

Web application, ASP.NET MVC 3, a controller action that accepts an instance of POCO model class with (potentially) large field.

Web应用程序,ASP。NET MVC 3是一个控制器动作,它接受一个带有(可能)大字段的POCO模型类实例。

Model class:

模型类:

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

Controller action:

控制器动作:

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

问题:

An action should be able to accept large JSON objects (at least up to hundred megabytes in a single request and that's no joke). By default I met with several restrictions like httpRuntime maxRequestLength etc. - all solved except MaxJsonLengh - meaning that default ValueProviderFactory for JSON is not capable of handling such objects.

一个操作应该能够接受大的JSON对象(至少在一个请求中可以接受百兆字节,这不是开玩笑)。默认情况下,我遇到了一些限制,如httpRuntime maxRequestLength等。所有解决的都是MaxJsonLengh,这意味着JSON的默认ValueProviderFactory不能处理这些对象。

Tried:

尝试:

Setting

设置

  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="2147483647"/>
      </webServices>
    </scripting>
  </system.web.extensions>
  • does not help.
  • 没有帮助。

Creating my own custom ValueProviderFactory as described in @Darin's answer here:

创建我自己的自定义ValueProviderFactory,如@Darin在这里的回答所述:

JsonValueProviderFactory throws "request too large"

JsonValueProviderFactory抛出“请求太大”

  • also failed because I have no possibility to use JSON.Net (due to non-technical reasons). I tried to implement correct deserialization here myself but apparently it's a bit above my knowledge (yet). I was able to deserialize my JSON string to Dictionary<String,Object> here, but that's not what I want - I want to deserialize it to my lovely POCO objects and use them as input parameters for actions.
  • 也失败了,因为我不可能使用JSON。Net(由于非技术原因)。我试图在这里实现正确的反序列化,但显然这超出了我的知识。我可以将JSON字符串反序列化为Dictionary< string,对象>,但这不是我想要的——我想将它反序列化为可爱的POCO对象,并将它们用作动作的输入参数。

So, the questions:

所以,问题:

  1. Anyone knows better way to overcome the problem without implementing universal custom ValueProviderFactory?
  2. 有谁知道在不实现通用自定义值提供程序的情况下如何更好地解决这个问题?
  3. Is there a possibility to specify for what specific controller and action I want to use my custom ValueProviderFactory? If I know the action beforehand than I will be able to deserialize JSON to POCO without much coding in ValueProviderFactory...
  4. 是否有可能指定要使用自定义ValueProviderFactory的特定控制器和操作?如果我事先知道了这个操作,那么我就可以在没有ValueProviderFactory代码的情况下将JSON反序列化为POCO。
  5. I'm also thinking about implementing a custom ActionFilter for that specific problem, but I think it's a bit ugly.
  6. 我也在考虑为这个特定的问题实现一个定制的ActionFilter,但是我认为它有点难看。

Anyone can suggest a good solution?

谁能提出一个好的解决方案?

3 个解决方案

#1


61  

The built-in JsonValueProviderFactory ignores the <jsonSerialization maxJsonLength="50000000"/> setting. So you could write a custom factory by using the built-in implementation:

内置的JsonValueProviderFactory忽略 设置。因此,您可以使用内置实现编写自定义工厂:

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

The only modification I did compared to the default factory is adding the following line:

与默认工厂相比,我所做的唯一修改是添加以下一行:

serializer.MaxJsonLength = 2147483647;

Unfortunately this factory is not extensible at all, sealed stuff so I had to recreate it.

不幸的是,这个工厂是不可扩展的,密封的东西,所以我不得不重新创建它。

and in your Application_Start:

和你的Application_Start:

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

#2


16  

I found that the maxRequestLength did not solve the problem however. I resolved my issue with the below setting. It is cleaner than having to implement a custom ValueProviderFactory

我发现maxRequestLength并没有解决问题。我用下面的设置解决了我的问题。它比必须实现自定义的ValueProviderFactory更干净。

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

Credit goes to the following questions:

信贷的问题如下:

JsonValueProviderFactory throws "request too large"

JsonValueProviderFactory抛出“请求太大”

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

获取“JSON请求太大,无法反序列化”

This setting obviously relates to a highly complex json model and not the actual size.

这个设置显然与高度复杂的json模型有关,而与实际大小无关。

#3


4  

The solution of Darin Dimitrov works for me but i need reset the position of the stream of the request before read it, adding this line:

Darin Dimitrov的解决方案为我工作,但我需要在读取之前重置请求流的位置,并添加这一行:

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

controllerContext.HttpContext.Request.InputStream。位置= 0;

So now, the method GetDeserializedObject looks like this:

现在,GetDeserializedObject方法看起来是这样的:

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

#1


61  

The built-in JsonValueProviderFactory ignores the <jsonSerialization maxJsonLength="50000000"/> setting. So you could write a custom factory by using the built-in implementation:

内置的JsonValueProviderFactory忽略 设置。因此,您可以使用内置实现编写自定义工厂:

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

The only modification I did compared to the default factory is adding the following line:

与默认工厂相比,我所做的唯一修改是添加以下一行:

serializer.MaxJsonLength = 2147483647;

Unfortunately this factory is not extensible at all, sealed stuff so I had to recreate it.

不幸的是,这个工厂是不可扩展的,密封的东西,所以我不得不重新创建它。

and in your Application_Start:

和你的Application_Start:

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

#2


16  

I found that the maxRequestLength did not solve the problem however. I resolved my issue with the below setting. It is cleaner than having to implement a custom ValueProviderFactory

我发现maxRequestLength并没有解决问题。我用下面的设置解决了我的问题。它比必须实现自定义的ValueProviderFactory更干净。

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

Credit goes to the following questions:

信贷的问题如下:

JsonValueProviderFactory throws "request too large"

JsonValueProviderFactory抛出“请求太大”

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

获取“JSON请求太大,无法反序列化”

This setting obviously relates to a highly complex json model and not the actual size.

这个设置显然与高度复杂的json模型有关,而与实际大小无关。

#3


4  

The solution of Darin Dimitrov works for me but i need reset the position of the stream of the request before read it, adding this line:

Darin Dimitrov的解决方案为我工作,但我需要在读取之前重置请求流的位置,并添加这一行:

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

controllerContext.HttpContext.Request.InputStream。位置= 0;

So now, the method GetDeserializedObject looks like this:

现在,GetDeserializedObject方法看起来是这样的:

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