JSONを使用して広告申込情報のリストをWebページに取得しようとしています。JSONは、到着した同じJSON構造を使用してajax要求によって操作され、サーバーに返送されます(フィールド値が変更された場合を除く)。
サーバーからのデータの受信は簡単で、操作はさらに簡単です!しかし、JSONデータをサーバーに送り返して、自殺時間を節約します!誰か助けてください!
Javascript
var lineitems;
// get data from server
$.ajax({
url: '/Controller/GetData/',
success: function(data){
lineitems = data;
}
});
// post data to server
$.ajax({
url: '/Controller/SaveData/',
data: { incoming: lineitems }
});
C#-オブジェクト
public class LineItem{
public string reference;
public int quantity;
public decimal amount;
}
C#-コントローラー
public JsonResult GetData()
{
IEnumerable<LineItem> lineItems = ... ; // a whole bunch of line items
return Json(lineItems);
}
public JsonResult SaveData(IEnumerable<LineItem> incoming){
foreach(LineItem item in incoming){
// save some stuff
}
return Json(new { success = true, message = "Some message" });
}
データは、シリアル化された投稿データとしてサーバーに到着します。自動化されたモデルバインダーはIEnumerable<LineItem> incoming
をバインドしようとし、驚くべきことに、結果のIEnumerable
が正しい数のLineItems
を取得します-データを入力しません。
[〜#〜] solution [〜#〜]
主に別のstackoverflow投稿のdjch
と以下のBeRecursive
の多くのソースからの回答を使用して、2つの主な方法を使用して問題を解決しました。
サーバー側
以下のデシリアライザーでは、System.Runtime.Serialization
およびusing System.Runtime.Serialization.Json
への参照が必要です
private T Deserialise<T>(string json)
{
using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
var serialiser = new DataContractJsonSerializer(typeof(T));
return (T)serialiser.ReadObject(ms);
}
}
public void Action(int id, string items){
IEnumerable<LineItem> lineitems = Deserialise<IEnumerable<LineItem>>(items);
// do whatever needs to be done - create, update, delete etc.
}
クライアント側
この依存関係で利用可能なjson.orgのstringifyメソッドを使用します https://github.com/douglascrockford/JSON-js/blob/master/json2.js (縮小すると2.5kbになります)
$.ajax({
type: 'POST',
url: '/Controller/Action',
data: { 'items': JSON.stringify(lineItems), 'id': documentId }
});
モデルバインディングJSONデータ に関するPhil Haackの投稿をご覧ください。問題は、デフォルトのモデルバインダーがJSONを適切にシリアル化しないことです。何らかのValueProvider ORが必要です。カスタムモデルバインダーを作成できます。
using System.IO;
using System.Web.Script.Serialization;
public class JsonModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
if(!IsJSONRequest(controllerContext)) {
return base.BindModel(controllerContext, bindingContext);
}
// Get the JSON data that's been posted
var request = controllerContext.HttpContext.Request;
//in some setups there is something that already reads the input stream if content type = 'application/json', so seek to the begining
request.InputStream.Seek(0, SeekOrigin.Begin);
var jsonStringData = new StreamReader(request.InputStream).ReadToEnd();
// Use the built-in serializer to do the work for us
return new JavaScriptSerializer()
.Deserialize(jsonStringData, bindingContext.ModelMetadata.ModelType);
// -- REQUIRES .NET4
// If you want to use the .NET4 version of this, change the target framework and uncomment the line below
// and comment out the above return statement
//return new JavaScriptSerializer().Deserialize(jsonStringData, bindingContext.ModelMetadata.ModelType);
}
private static bool IsJSONRequest(ControllerContext controllerContext) {
var contentType = controllerContext.HttpContext.Request.ContentType;
return contentType.Contains("application/json");
}
}
public static class JavaScriptSerializerExt {
public static object Deserialize(this JavaScriptSerializer serializer, string input, Type objType) {
var deserializerMethod = serializer.GetType().GetMethod("Deserialize", BindingFlags.NonPublic | BindingFlags.Static);
// internal static method to do the work for us
//Deserialize(this, input, null, this.RecursionLimit);
return deserializerMethod.Invoke(serializer,
new object[] { serializer, input, objType, serializer.RecursionLimit });
}
}
Global.asaxファイルで使用するようMVCに指示します。
ModelBinders.Binders.DefaultBinder = new JsonModelBinder();
また、このコードはコンテンツタイプ= 'application/json'を使用するため、jqueryで次のように設定してください。
$.ajax({
dataType: "json",
contentType: "application/json",
type: 'POST',
url: '/Controller/Action',
data: { 'items': JSON.stringify(lineItems), 'id': documentId }
});
このブログ記事 問題を直接解決することをお勧めします。
Phil Haackが指摘したように、カスタムモデルバインダーを使用するのは賢明ではありません(彼のブログ投稿は上部のブログ投稿にもリンクされています)。
基本的に、threeオプションがあります:
JsonValueProviderFactory
を記述し、json2.js
などのクライアント側ライブラリを使用して、JSONと直接通信します。
$.ajax
で発生するjQuery JSONオブジェクト変換を理解するJQueryValueProviderFactory
または
ブログ投稿で概説されている非常にシンプルで素早いjQueryプラグインを使用して、IList<T>
にバインドされるJSONオブジェクト(arraysでも準備します)およびdatesは、サーバー側でDateTime
インスタンスとして正しく解析されます)これは、Asp.net MVCのデフォルトモデルバインダーによって理解されます。
3つのうち、最後のものが最も単純で、Asp.net MVCの内部動作に干渉しないため、バグの可能性が低くなります。ブログの投稿で概説されているこの手法を使用すると、ストロングタイプアクションパラメーターが正しくデータバインドされ、それらも検証されます。したがって、基本的にはwin winの状況です。
MVC3ではこれを追加しました。
しかし、さらに素晴らしいのは、MVCソースコードが開いているため、ValueProviderを取得して、自分で自分のコードで使用できることです(MVC3をまだ使用していない場合)。
あなたはこのようなものになるでしょう
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory())
私はここでvestigalのヒントに従ってこの問題を解決しました:
web.configのmaxJsonLengthに無制限の長さを設定できますか?
大きなJSONをコントローラーのアクションにポストする必要がある場合、有名な「JSON JavaScriptSerializerを使用した逆シリアル化中のエラー。文字列の長さがmaxJsonLengthプロパティで設定された値を超えています。\ r\nパラメーター名:入力値プロバイダー」。
新しいValueProviderFactory、LargeJsonValueProviderFactoryを作成し、GetDeserializedObjectメソッドでMaxJsonLength = Int32.MaxValueを設定しました
public sealed class LargeJsonValueProviderFactory : ValueProviderFactory
{
private static void AddToBackingStore(LargeJsonValueProviderFactory.EntryLimitedDictionary backingStore, string prefix, object value)
{
IDictionary<string, object> dictionary = value as IDictionary<string, object>;
if (dictionary != null)
{
foreach (KeyValuePair<string, object> keyValuePair in (IEnumerable<KeyValuePair<string, object>>) dictionary)
LargeJsonValueProviderFactory.AddToBackingStore(backingStore, LargeJsonValueProviderFactory.MakePropertyKey(prefix, keyValuePair.Key), keyValuePair.Value);
}
else
{
IList list = value as IList;
if (list != null)
{
for (int index = 0; index < list.Count; ++index)
LargeJsonValueProviderFactory.AddToBackingStore(backingStore, LargeJsonValueProviderFactory.MakeArrayKey(prefix, index), list[index]);
}
else
backingStore.Add(prefix, value);
}
}
private static object GetDeserializedObject(ControllerContext controllerContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return (object) null;
string end = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();
if (string.IsNullOrEmpty(end))
return (object) null;
var serializer = new JavaScriptSerializer {MaxJsonLength = Int32.MaxValue};
return serializer.DeserializeObject(end);
}
/// <summary>Returns a JSON value-provider object for the specified controller context.</summary>
/// <returns>A JSON value-provider object for the specified controller context.</returns>
/// <param name="controllerContext">The controller context.</param>
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
object deserializedObject = LargeJsonValueProviderFactory.GetDeserializedObject(controllerContext);
if (deserializedObject == null)
return (IValueProvider) null;
Dictionary<string, object> dictionary = new Dictionary<string, object>((IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
LargeJsonValueProviderFactory.AddToBackingStore(new LargeJsonValueProviderFactory.EntryLimitedDictionary((IDictionary<string, object>) dictionary), string.Empty, deserializedObject);
return (IValueProvider) new DictionaryValueProvider<object>((IDictionary<string, object>) dictionary, CultureInfo.CurrentCulture);
}
private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString((IFormatProvider) CultureInfo.InvariantCulture) + "]";
}
private static string MakePropertyKey(string prefix, string propertyName)
{
if (!string.IsNullOrEmpty(prefix))
return prefix + "." + propertyName;
return propertyName;
}
private class EntryLimitedDictionary
{
private static int _maximumDepth = LargeJsonValueProviderFactory.EntryLimitedDictionary.GetMaximumDepth();
private readonly IDictionary<string, object> _innerDictionary;
private int _itemCount;
public EntryLimitedDictionary(IDictionary<string, object> innerDictionary)
{
this._innerDictionary = innerDictionary;
}
public void Add(string key, object value)
{
if (++this._itemCount > LargeJsonValueProviderFactory.EntryLimitedDictionary._maximumDepth)
throw new InvalidOperationException("JsonValueProviderFactory_RequestTooLarge");
this._innerDictionary.Add(key, value);
}
private static int GetMaximumDepth()
{
NameValueCollection appSettings = ConfigurationManager.AppSettings;
if (appSettings != null)
{
string[] values = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers");
int result;
if (values != null && values.Length > 0 && int.TryParse(values[0], out result))
return result;
}
return 1000;
}
}
}
次に、Global.asax.csのApplication_Startメソッドで、ValueProviderFactoryを新しいものに置き換えます。
protected void Application_Start()
{
...
//Add LargeJsonValueProviderFactory
ValueProviderFactory jsonFactory = null;
foreach (var factory in ValueProviderFactories.Factories)
{
if (factory.GetType().FullName == "System.Web.Mvc.JsonValueProviderFactory")
{
jsonFactory = factory;
break;
}
}
if (jsonFactory != null)
{
ValueProviderFactories.Factories.Remove(jsonFactory);
}
var largeJsonValueProviderFactory = new LargeJsonValueProviderFactory();
ValueProviderFactories.Factories.Add(largeJsonValueProviderFactory);
}
これらを試すことができます。 1. ajaxを介してサーバーアクションを呼び出す前にJSONオブジェクトを文字列化します。2.アクション内の文字列を逆シリアル化し、データを辞書として使用します。
以下のJavascriptサンプル(JSONオブジェクトの送信
$.ajax(
{
type: 'POST',
url: 'TheAction',
data: { 'data': JSON.stringify(theJSONObject)
}
})
以下のアクション(C#)サンプル
[HttpPost]
public JsonResult TheAction(string data) {
string _jsonObject = data.Replace(@"\", string.Empty);
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
Dictionary<string, string> jsonObject = serializer.Deserialize<Dictionary<string, string>>(_jsonObject);
return Json(new object{status = true});
}
BeRecursiveの答え は私が使用したものなので、Json.Netで標準化できます(MVC5とWebApi 5があります-WebApi 5はすでにJson.Netを使用しています)が、問題が見つかりました。 POSTを実行するルートにパラメーターがある場合、MVCはURI値のモデルバインダーの呼び出しを試み、このコードはポストされたJSONをそれらの値にバインドしようとします。
例:
[HttpPost]
[Route("Customer/{customerId:int}/Vehicle/{vehicleId:int}/Policy/Create"]
public async Task<JsonNetResult> Create(int customerId, int vehicleId, PolicyRequest policyRequest)
BindModel
関数は、JSONをcustomerId
にバインドしようとするため、3回呼び出され、最初に爆撃されます:Error reading integer. Unexpected token: StartObject. Path '', line 1, position 1.
このコードブロックをBindModel
の先頭に追加しました。
if (bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null) {
return base.BindModel(controllerContext, bindingContext);
}
幸いなことに、ValueProviderには、このメソッドに到達するまでに計算されたルート値があります。
文字列として入力されるJSONデータがある場合(例: '[{"id":1、 "name": "Charles"}、{"id":8、 "name": "John"}、{ "id":13、 "name": "Sally"}] ')
次に、 JSON.net を使用し、Linq to JSONを使用して値を取得します...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request["items"] != null)
{
var items = Request["items"].ToString(); // Get the JSON string
JArray o = JArray.Parse(items); // It is an array so parse into a JArray
var a = o.SelectToken("[0].name").ToString(); // Get the name value of the 1st object in the array
// a == "Charles"
}
}
}
「手動」のシリアル化解除を使用して解決しました。コードで説明します
public ActionResult MyMethod([System.Web.Http.FromBody] MyModel model)
{
if (module.Fields == null && !string.IsNullOrEmpty(Request.Form["fields"]))
{
model.Fields = JsonConvert.DeserializeObject<MyFieldModel[]>(Request.Form["fields"]);
}
//... more code
}