Проводка данных 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 }
});
С# - Объекты
public class LineItem{
public string reference;
public int quantity;
public decimal amount;
}
С# - контроллер
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
- оно просто не заполняет их данными.
Решение
Используя ответы от нескольких источников, в первую очередь djch
на другой записи stackoverflow и BeRecursive
ниже, я решил свою проблему, используя два основных метода.
Сторона сервера
Для десериализатора ниже требуется ссылка на 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.
}
Клиентская сторона
Он использует метод jonn.org stringify, доступный в этом 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 }
});
Ответы
Ответ 1
Взгляните на сообщение Фила Хаака на привязанные к модели данные 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 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 }
});
Ответ 2
Самый простой способ сделать это
Я настоятельно рекомендую вам прочитать это сообщение в блоге, которое напрямую решает вашу проблему.
Использование пользовательских привязок моделей на самом деле не так мудро, как отметил Фил Хаак (его сообщение в блоге также связано в верхнем блоге).
В основном у вас есть три варианта:
-
Напишите JsonValueProviderFactory
и используйте библиотеку на стороне клиента, например json2.js
, чтобы напрямую общаться с JSON.
-
Напишите JQueryValueProviderFactory
, который понимает преобразование объекта JQuery JSON, которое происходит в $.ajax
или
-
Используйте очень простой и быстрый плагин jQuery, описанный в сообщении в блоге, который подготавливает любой объект JSON (даже массивы, который будет привязан к IList<T>
и датам, который будет правильно анализировать на стороне сервера как DateTime
экземпляры), которые будут поняты с помощью связующего по умолчанию модели Asp.net MVC.
Из всех трех последних самое простое и не мешает внутренним работам Asp.net MVC, таким образом снижая возможную поверхность ошибки. Используя этот метод, описанный в сообщении в блоге, данные будут привязаны к вашим сильным параметрам действия и будут также проверять их. Таким образом, это в основном ситуация с выигрышной победой .
Ответ 3
В MVC3 они добавили это.
Но что еще более приятно, так как исходный код MVC открыт, вы можете захватить ValueProvider и использовать его самостоятельно в своем собственном коде (если вы еще не на MVC3).
В итоге вы получите что-то вроде этого
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory())
Ответ 4
Я решил эту проблему после следующих советов:
Могу ли я установить неограниченную длину для maxJsonLength в web.config?
Когда мне нужно было отправить большой json в действие в контроллере, я получил бы знаменитую "Ошибка при десериализации с помощью JSON JavaScriptSerializer. Длина строки превышает значение, установленное в свойстве maxJsonLength.\r\nПараметр name: поставщик входных значений".
То, что я сделал, это создать новый ValueProviderFactory, LargeJsonValueProviderFactory и установить MaxJsonLength = Int32.MaxValue в методе GetDeserializedObject
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;
}
}
}
Затем в методе 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);
}
Ответ 5
Вы можете попробовать их.
1. стройте свой объект JSON перед вызовом действия сервера через ajax
2. десериализуем строку в действии, затем используйте данные в качестве словаря.
Пример Javascript ниже (отправка объекта JSON
$.ajax(
{
type: 'POST',
url: 'TheAction',
data: { 'data': JSON.stringify(theJSONObject)
}
})
Пример действия (С#) ниже
[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});
}
Ответ 6
Если у вас есть данные 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"
}
}
}
Ответ 7
Ответ 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
с ошибкой: 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, к счастью, есть значения маршрута, которые были выяснены к тому времени, когда он попадает к этому методу.
Ответ 8
Я решил использовать "ручную" десерилизацию. Я объясню код
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
}
Ответ 9
Я вижу, что все здесь "взяли длинный путь!". Пока вы используете MVC
, я настоятельно рекомендую вам использовать самый простой метод из всех Newtonsoft.JSON
... Кроме того, если вы не хотите использовать библиотеки, проверьте ссылки на ответы ниже. Я потратил достаточно времени на исследования, чтобы решить эту проблему для себя, и вот решения, которые я нашел;
Сначала реализуйте Newtonsoft.Json:
using Newtonsoft.Json;
Подготовьте запрос Ajax:
$.ajax({
dataType: "json",
contentType: "application/json",
type: 'POST',
url: '/Controller/Action',
data: { 'items': JSON.stringify(lineItems), 'id': documentId }
});
Затем перейдите к классу результатов:
public ActionResult SaveData(string incoming, int documentId){
// DeSerialize into your Model as your Model Array
LineItem[] jsr = JsonConvert.DeserializeObject<LineItem[]>(Temp);
foreach(LineItem item in jsr){
// save some stuff
}
return Json(new { success = true, message = "Some message" });
}
Видишь хитрость там? Вместо использования JsonResult
я использовал обычный ActionResult
со строкой, содержащей строку json. Затем десериализовался в мой Model
, чтобы я мог использовать это в любом методе действия, который у меня есть.
Плюсами этого метода являются:
- Проще переходить между действиями,
- Меньшее и более четкое использование кода,
- Не нужно менять свою модель,
- Простая реализация с
JSON.stringify(Model)
- Передача только строки
Недостатки этого метода:
- Передача только строки
- Процесс десериализации
Также проверьте эти вопросы и ответы, которые действительно полезны:
fooobar.com/info/14194095/...
другой метод:
fooobar.com/info/74561/...
и другой метод:
fooobar.com/info/74561/...