web-dev-qa-db-ja.com

大きなJSONオブジェクトのデシリアライズに関するJsonMaxLength例外

イントロ:

Webアプリケーション、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オブジェクト(1回の要求で少なくとも最大100メガバイトであり、それは冗談ではありません)を受け入れられる必要があります。デフォルトでは、httpRuntime maxRequestLengthなどのようないくつかの制限がありました。MaxJsonLenghを除くすべてが解決されました。つまり、JSONのデフォルトのValueProviderFactoryはそのようなオブジェクトを処理できません。

試行:

設定

  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="2147483647"/>
      </webServices>
    </scripting>
  </system.web.extensions>
  • 助けにならない。

ここで@Darinの回答で説明されているように、独自のカスタムValueProviderFactoryを作成します。

JsonValueProviderFactoryは「リクエストが大きすぎます」をスローします

  • また、JSON.Netを使用する可能性がないため(技術的でない理由により)失敗しました。ここで自分で正しい逆シリアル化を実装しようとしましたが、明らかに(まだ)私の知識よりも少し上です。ここでJSON文字列をDictionary<String,Object>に逆シリアル化できましたが、それは私が望むものではありません-それを私の素敵なPOCOオブジェクトに逆シリアル化し、アクションの入力パラメーターとして使用したいです。

だから、質問:

  1. 誰もがユニバーサルカスタムValueProviderFactoryを実装せずに問題を克服するより良い方法を知っていますか?
  2. カスタムValueProviderFactoryを使用する特定のコントローラーとアクションを指定する可能性はありますか? ValueProviderFactoryで多くのコーディングをせずにJSONをPOCOにデシリアライズできるよりも前にアクションを知っていれば...
  3. また、その特定の問題に対してカスタムActionFilterを実装することも考えていますが、少しいと思います。

誰でも良い解決策を提案できますか?

28

組み込みのJsonValueProviderFactoryは<jsonSerialization maxJsonLength="50000000"/>設定を無視します。したがって、組み込みの実装を使用してカスタムファクトリを作成できます。

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

デフォルトのファクトリと比較して行った唯一の変更は、次の行を追加することです。

serializer.MaxJsonLength = 2147483647;

残念ながら、この工場は完全に拡張可能ではなく、密閉されたものなので、再作成する必要がありました。

そして、あなたのApplication_Start

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

しかし、maxRequestLengthでは問題が解決されないことがわかりました。以下の設定で問題を解決しました。カスタムValueProviderFactoryを実装するよりもクリーンです

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

クレジットは、次の質問に進みます。

JsonValueProviderFactoryは「リクエストが大きすぎます」をスローします

「JSONリクエストが大きすぎてデシリアライズできませんでした」

この設定は明らかに、実際のサイズではなく、非常に複雑なJSONモデルに関連しています。

17
Oliver

Darin Dimitrovのソリューションは私には有効ですが、リクエストのストリームの位置をリセットしてから、次の行を追加して読み取る必要があります。

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