イントロ:
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は「リクエストが大きすぎます」をスローします
Dictionary<String,Object>
に逆シリアル化できましたが、それは私が望むものではありません-それを私の素敵なPOCOオブジェクトに逆シリアル化し、アクションの入力パラメーターとして使用したいです。だから、質問:
誰でも良い解決策を提案できますか?
組み込みの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());
しかし、maxRequestLengthでは問題が解決されないことがわかりました。以下の設定で問題を解決しました。カスタムValueProviderFactoryを実装するよりもクリーンです
<appSettings>
<add key="aspnet:MaxJsonDeserializerMembers" value="150000" />
</appSettings>
クレジットは、次の質問に進みます。
JsonValueProviderFactoryは「リクエストが大きすぎます」をスローします
「JSONリクエストが大きすぎてデシリアライズできませんでした」
この設定は明らかに、実際のサイズではなく、非常に複雑なJSONモデルに関連しています。
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;
}