Programing

ASP.NET MVC에 JSON 데이터 게시

lottogame 2020. 12. 26. 09:24
반응형

ASP.NET MVC에 JSON 데이터 게시


JSON을 사용하여 웹 페이지에 광고 항목 목록을 가져 오려고합니다. 그런 다음 도착한 동일한 JSON 구조를 사용하여 ajax 요청에 의해 조작되고 서버로 다시 전송됩니다 (필드 값이 변경된 경우 제외).

서버에서 데이터를 수신하는 것은 쉽고 조작이 훨씬 쉽습니다! 하지만 자살 시간을 절약하기 위해 JSON 데이터를 서버로 다시 보냅니다! 누군가 도울 수 있습니다!

자바 스크립트

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. 데이터로 채우지 않습니다.

해결책

주로 djch다른 스택 오버플로 게시물과 그 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.
    }

고객 입장에서

이 의존성 https://github.com/douglascrockford/JSON-js/blob/master/json2.js 에서 사용할 수있는 json.org의 stringify 메서드를 사용합니다 (최소화시 2.5kb).

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

JSON 데이터를 바인딩 하는 모델 에 대한 Phil Haack의 게시물을 살펴보십시오 . 문제는 기본 모델 바인더가 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 }
});

이 작업을 수행하는 가장 간단한 방법

문제를 직접 해결하는 이 블로그 게시물 을 읽어 보시기 바랍니다 .

맞춤 모델 바인더를 사용하는 것은 Phil Haack이 지적한 것처럼 실제로 현명하지 않습니다 (그의 블로그 게시물은 상단 블로그 게시물에도 링크되어 있습니다).

기본적으로 세 가지 옵션이 있습니다.

  1. JSON과 직접 통신하는 JsonValueProviderFactory것과 같은 클라이언트 측 라이브러리를 작성 하고 사용하십시오 json2.js.

  2. 또는 JQueryValueProviderFactory에서 발생하는 jQuery JSON 객체 변환을 이해하는 작성$.ajax

  3. 블로그 게시물에 설명 된 매우 간단하고 빠른 jQuery 플러그인을 사용 하여 Asp.net MVC 기본값에서 이해할 수 있는 JSON 개체 ( 바인딩 될 배열IList<T>서버 측에서 인스턴스 로 올바르게 구문 분석되는 날짜 포함DateTime )를 준비합니다. 모델 바인더.

세 가지 중 마지막 것이 가장 간단하고 Asp.net MVC 내부 작동을 방해하지 않으므로 가능한 버그 표면을 낮 춥니 다. 블로그 게시물에 설명 된이 기술을 사용하면 강력한 유형 작업 매개 변수를 올바르게 데이터 바인딩하고 유효성도 검사 할 수 있습니다. 따라서 기본적으로 윈윈 상황입니다.


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. 작업에서 문자열을 역 직렬화 한 다음 데이터를 사전으로 사용합니다.

아래 자바 스크립트 샘플 (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});

    }

문자열로 들어오는 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"
    }
}
}

BeRecursive's answer is the one I used, so that we could standardize on Json.Net (we have MVC5 and WebApi 5 -- WebApi 5 already uses Json.Net), but I found an issue. When you have parameters in your route to which you're POSTing, MVC tries to call the model binder for the URI values, and this code will attempt to bind the posted JSON to those values.

Example:

[HttpPost]
[Route("Customer/{customerId:int}/Vehicle/{vehicleId:int}/Policy/Create"]
public async Task<JsonNetResult> Create(int customerId, int vehicleId, PolicyRequest policyRequest)

The BindModel function gets called three times, bombing on the first, as it tries to bind the JSON to customerId with the error: Error reading integer. Unexpected token: StartObject. Path '', line 1, position 1.

I added this block of code to the top of BindModel:

if (bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null) {
    return base.BindModel(controllerContext, bindingContext);
}

The ValueProvider, fortunately, has route values figured out by the time it gets to this method.


I solved using a "manual" deserialization. I'll explain in code

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
}

I see everyone here "took the long route!". As long as you are using MVC, I strongly recommend you to use the easiest method over all which is Newtonsoft.JSON... Also If you dont wanna use libraries check the answer links below. I took a good research time for this to solve the issue for my self and these are the solutions I found;

First implement the Newtonsoft.Json:

using Newtonsoft.Json;

Prepare your Ajax request:

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

Then go for your result class:

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

See the trick up there? Instead of using JsonResult I used regular ActionResult with a string which includes json string. Then deserialized into my Model so I can use this in any action method I have.

Plus sides of this method is :

  1. Easier to pass between actions,
  2. Lesser and more clear usage of code,
  3. No need to change your model,
  4. Simple implementation with JSON.stringify(Model)
  5. Passing only string

Down sides of this method is :

  1. Passing only string
  2. Deserialization process

Also check these questions and answers which are really helpfull:

https://stackoverflow.com/a/45682516/861019

another method :

https://stackoverflow.com/a/31656160/861019

and another method :

https://stackoverflow.com/a/50787450/861019

ReferenceURL : https://stackoverflow.com/questions/4164114/posting-json-data-to-asp-net-mvc

반응형