Подтвердить что ты не робот

Проводка данных 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 }
        });
4b9b3361

Ответ 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, чтобы я мог использовать это в любом методе действия, который у меня есть.

Плюсами этого метода являются:

  1. Проще переходить между действиями,
  2. Меньшее и более четкое использование кода,
  3. Не нужно менять свою модель,
  4. Простая реализация с JSON.stringify(Model)
  5. Передача только строки

Недостатки этого метода:

  1. Передача только строки
  2. Процесс десериализации

Также проверьте эти вопросы и ответы, которые действительно полезны:

fooobar.com/info/14194095/...

другой метод:

fooobar.com/info/74561/...

и другой метод:

fooobar.com/info/74561/...