Подтвердить что ты не робот

Исключение JsonMaxLength при десериализации больших объектов json

Введение:

Веб-приложение ASP.NET MVC 3, действие контроллера, которое принимает экземпляр класса модели POCO с (потенциально) большим полем.

Модельный класс:

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

Действие контроллера:

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

Проблема:

Действие должно иметь возможность принимать большие объекты JSON (по крайней мере, до ста мегабайт в одном запросе и без шуток). По умолчанию я встречался с несколькими ограничениями, такими как httpRuntime maxRequestLength и т.д. - все решалось кроме MaxJsonLengh - это означает, что значение ValueProviderFactory по умолчанию для JSON не способно обрабатывать такие объекты.

Пробовал:

Настройка

  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="2147483647"/>
      </webServices>
    </scripting>
  </system.web.extensions>
  • не помогает.

Создание собственного пользовательского ValueProviderFactory, как описано в @Darin, здесь:

JsonValueProviderFactory выдает запрос слишком большой "

  • также не удалось, потому что у меня нет возможности использовать JSON.Net(по нетехническим причинам). Я попытался реализовать правильную десериализацию здесь сам, но, по-видимому, это немного выше моих знаний (пока). Мне удалось десериализовать мою строку JSON в Dictionary<String,Object> здесь, но это не то, что я хочу - я хочу десериализовать ее на мои прекрасные объекты POCO и использовать их в качестве входных параметров для действий.

Итак, вопросы:

  • Кто-нибудь знает лучший способ преодолеть проблему без реализации универсального настраиваемого ValueProviderFactory?
  • Есть ли возможность указать, для какого конкретного контроллера и действия я хочу использовать свой собственный ValueProviderFactory? Если я знаю действие заранее, я смогу десериализовать JSON в POCO без большой кодировки в ValueProviderFactory...
  • Я также думаю о реализации пользовательского ActionFilter для этой конкретной проблемы, но я думаю, что это немного уродливо.

Кто-нибудь может предложить хорошее решение?

4b9b3361

Ответ 1

Встроенный JsonValueProviderFactory игнорирует параметр <jsonSerialization maxJsonLength="50000000"/>. Таким образом, вы можете написать пользовательский factory с помощью встроенной реализации:

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

Единственная модификация, которую я сделал по сравнению со значением по умолчанию factory, заключается в добавлении следующей строки:

serializer.MaxJsonLength = 2147483647;

К сожалению, этот factory не является расширяемым, закрытым, поэтому мне пришлось его воссоздать.

и в вашем Application_Start:

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

Ответ 2

Я обнаружил, что maxRequestLength не решила проблему. Я решил проблему с настройками ниже. Это более чистое, чем выполнение пользовательского ValueProviderFactory

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

Кредит относится к следующим вопросам:

JsonValueProviderFactory выдает запрос слишком большой "

Получение "Запрос JSON был слишком большим для десериализации"

Этот параметр, очевидно, относится к очень сложной модели json, а не к фактическому размеру.

Ответ 3

Решение Дарина Димитрова работает для меня, но мне нужно reset положение потока запроса перед его чтением, добавив эту строку:

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

Итак, теперь метод 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;
    }