Проводка данных JSON в ASP.NET MVC

Я пытаюсь получить список позиций на веб-странице с использованием JSON, который затем будет обрабатываться и отправляться обратно на сервер по запросу ajax с использованием той же структуры JSON, которая была получена (за исключением того, что были изменены значения полей).

Получить данные с сервера легко, манипулировать еще проще! но отправляя данные 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 # – controller

 public JsonResult GetData() { IEnumerable lineItems = ... ; // a whole bunch of line items return Json(lineItems); } public JsonResult SaveData(IEnumerable incoming){ foreach(LineItem item in incoming){ // save some stuff } return Json(new { success = true, message = "Some message" }); } 

Данные поступают на сервер в виде последовательных данных. Автоматическое связующее устройство пытается связать IEnumerable incoming и неожиданно получает в результате, что в результате IEnumerable имеет правильное количество LineItems – он просто не заполняет их данными.

РЕШЕНИЕ

Используя ответы из нескольких источников, в первую очередь djch на другую запись stackoverflow и BeRecursive ниже, я решил свою проблему, используя два основных метода.

Серверная сторона

Для десериализатора ниже требуется ссылка на System.Runtime.Serialization и using System.Runtime.Serialization.Json

  private T Deserialise(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 lineitems = Deserialise<IEnumerable>(items); // do whatever needs to be done - create, update, delete etc. } 

Сторона клиента

Он использует метод stringify json.org, ansible в этом dependecy https://github.com/douglascrockford/JSON-js/blob/master/json2.js (который составляет 2,5 килобайта при минимализации)

  $.ajax({ type: 'POST', url: '/Controller/Action', data: { 'items': JSON.stringify(lineItems), 'id': documentId } }); 

Взгляните на сообщение Фила Хаака по данным привязки модели JSON . Проблема в том, что связующее устройство по умолчанию не упорядочивает JSON должным образом. Вам нужен какой-то ValueProvider ИЛИ вы могли бы написать настраиваемое связующее устройство:

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

И скажите MVC использовать его в файле Global.asax:

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

Самый простой способ сделать это

Я настоятельно рекомендую вам прочитать это сообщение в блоге, которое напрямую решает вашу проблему.

Использование пользовательских привязок к модели не очень мудро, как отметил Фил Хаак (его сообщение в блоге также связано в верхнем блоге).

В принципе у вас есть три варианта:

  1. Напишите JsonValueProviderFactory и используйте библиотеку на стороне клиента, такую ​​как json2.js чтобы напрямую общаться с JSON.

  2. Напишите JQueryValueProviderFactory который понимает преобразование объекта JQuery JSON, которое происходит в $.ajax или

  3. Используйте очень простой и быстрый плагин jQuery, описанный в сообщении в блоге, который подготавливает любой объект JSON (даже массивы, которые будут привязаны к IList и датам, которые будут правильно анализировать на стороне сервера в качестве экземпляров DateTime ), что будет понято Asp.net MVC по умолчанию.

Из всех трех, последний из них является самым простым и не мешает внутренней работе Asp.net MVC, тем самым снижая возможную поверхность ошибки. Используя этот метод, описанный в сообщении в блоге, данные будут привязаны к вашим сильным параметрам действия и будут также проверять их. Так что это в основном ситуация с выигрышной победой.

В MVC3 они добавили это.

Но что еще более приятно, так как исходный код MVC открыт, вы можете захватить ValueProvider и использовать его самостоятельно в своем собственном коде (если вы еще не на MVC3).

Вы получите что-то вроде этого

 ValueProviderFactories.Factories.Add(new JsonValueProviderFactory()) 

Я решил эту проблему, следуя советам здесь:

Могу ли я установить неограниченную длину для maxJsonLength в web.config?

Когда мне нужно было отправить большой json в действие в controllerе, я получил бы знаменитую «Ошибка при десериализации с помощью JSON JavaScriptSerializer. Длина строки превышает значение, установленное в свойстве maxJsonLength. \ R \ nПараметр: input стоимость поставщика “.

То, что я сделал, это создать новый ValueProviderFactory, LargeJsonValueProviderFactory и установить MaxJsonLength = Int32.MaxValue в методе GetDeserializedObject

 public sealed class LargeJsonValueProviderFactory : ValueProviderFactory { private static void AddToBackingStore(LargeJsonValueProviderFactory.EntryLimitedDictionary backingStore, string prefix, object value) { IDictionary dictionary = value as IDictionary; if (dictionary != null) { foreach (KeyValuePair keyValuePair in (IEnumerable>) 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); } /// Returns a JSON value-provider object for the specified controller context. /// A JSON value-provider object for the specified controller context. /// The controller context. 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 dictionary = new Dictionary((IEqualityComparer) StringComparer.OrdinalIgnoreCase); LargeJsonValueProviderFactory.AddToBackingStore(new LargeJsonValueProviderFactory.EntryLimitedDictionary((IDictionary) dictionary), string.Empty, deserializedObject); return (IValueProvider) new DictionaryValueProvider((IDictionary) 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 _innerDictionary; private int _itemCount; public EntryLimitedDictionary(IDictionary 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; } } } 

Затем в методе Application_Start из Global.asax.cs замените 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. стройте свой объект JSON перед вызовом действия сервера через ajax 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 jsonObject = serializer.Deserialize>(_jsonObject); return Json(new object{status = true}); } 

Если у вас есть данные JSON, входящие в строку (например, «[{« id »: 1,« name »:« Charles »}, {« id »: 8,« name »:« John »}, { “идентификатор”: 13, “название”: “Салли”}] ‘)

Затем я буду использовать JSON.net и использовать Linq для 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" } } } 

Ответ 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 Create(int customerId, int vehicleId, PolicyRequest policyRequest) 

Функция BindModel три раза, бомбардировка первой, поскольку она пытается привязать JSON к customerId с ошибкой: Error reading integer. Unexpected token: StartObject. Path '', line 1, position 1. 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 имеет значения маршрута, которые были определены к тому моменту, когда он дойдет до этого метода.

Я решил использовать «ручную» десерилизацию. Я объясню код

 public ActionResult MyMethod([System.Web.Http.FromBody] MyModel model) { if (module.Fields == null && !string.IsNullOrEmpty(Request.Form["fields"])) { model.Fields = JsonConvert.DeserializeObject(Request.Form["fields"]); } //... more code } 
Interesting Posts
Давайте будем гением компьютера.