programing

여러 개의 복잡한 개체를 포스트/풋 웹 API 메서드로 전달

javajsp 2023. 9. 26. 22:00

여러 개의 복잡한 개체를 포스트/풋 웹 API 메서드로 전달

아래와 같이 C# 콘솔 앱에서 웹 API 컨트롤러로 여러 개체를 전달하는 방법을 알려주실 수 있나요?

using (var httpClient = new System.Net.Http.HttpClient())
{
    httpClient.BaseAddress = new Uri(ConfigurationManager.AppSettings["Url"]);
    httpClient.DefaultRequestHeaders.Accept.Clear();
    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));   

    var response = httpClient.PutAsync("api/process/StartProcessiong", objectA, objectB);
}

내 웹 API 메서드는 다음과 같습니다.

public void StartProcessiong([FromBody]Content content, [FromBody]Config config)
{

}

현재 버전의 Web API에서는 여러 개의 복잡한 객체(예:Content그리고.ConfigWeb API 메서드 서명 내에서 complex objects)를 사용할 수 없습니다.나는 돈을 많이 걸 것입니다.config(두 번째 파라미터)는 항상 NULL로 돌아옵니다.하나의 요청에 대해 본문에서 하나의 복잡한 개체만 구문 분석할 수 있기 때문입니다.성능상의 이유로 Web API 요청 본문은 한 만 액세스 및 구문 분석이 가능합니다.따라서 "content" 파라미터에 대한 요청 바디의 스캔 및 파싱이 발생한 후 모든 후속 바디 파싱은 "NULL"로 종료됩니다.그래서 기본적으로:

  • 에서는 의 항목만 할 수 .[FromBody].
  • 를 하면 항목 할 수 .[FromUri].

아래는 Mike Stall의 우수한 블로그 기사에서 유용한 발췌문입니다(오래되긴 했지만 금빛!).4번 항목에 주의를 기울여야 합니다.

다음은 매개 변수가 모델 바인딩으로 읽히는지 또는 포맷터로 읽히는지를 결정하는 기본 규칙입니다.

  1. 매개 변수에 속성이 없는 경우에는 순전히 매개 변수의 속성에 따라 결정됩니다.NET 타입."Simple type"은 모델 바인딩을 사용합니다.복잡한 형식은 형식을 사용합니다."단순 유형"에는 다음이 포함됩니다.TimeSpan,DateTime,Guid,Decimal,String, 아니면 뭔가를 가지고 있는.TypeConverter현에서 변환되는 것입니다.
  2. 당신은 a를 사용할 수 있습니다.[FromBody]: e:합니다.
  3. 당신은 a를 사용할 수 있습니다.[ModelBinder]매개 변수가 모형 바인딩되도록 지정하는 매개 변수 또는 매개 변수 유형에 대한 속성입니다.이 특성을 사용하면 모델 바인더를 구성할 수도 있습니다.[FromUri]입니다의 입니다.[ModelBinder]URI에서만 볼 수 있도록 모델 바인더를 구체적으로 구성합니다.
  4. 본문은 한 번만 읽을 수 있습니다. 그 중 하나는면 2에, 있어야 합니다.[ModelBinder]그것에 책임을 돌리다.

이러한 규칙이 정적이고 예측 가능하도록 설계하는 것이 핵심 목표였습니다.

MVC와 웹 API의 주요 차이점은 MVC가 콘텐츠(예: 요청 본문)를 버퍼링한다는 것입니다.이는 MVC의 파라미터 바인딩이 반복적으로 본문을 검색하여 파라미터 조각을 찾을 수 있음을 의미합니다. 웹 에서는 요청 웹 관(an.HttpContent)는 읽기 전용, 무한, buff되지 않은, 되감기 불가능한 스트림일 수 있습니다.

여러분은 이 엄청나게 유용한 기사의 나머지 부분을 스스로 읽을 수 있으므로, 간단히 말하면, 여러분이 하려는 일은 현재 그러한 방식으로 가능하지 않습니다(즉, 창의력을 발휘해야 합니다).다음에 나오는 것은 해결책이 아니라 해결책이며 한 가지 가능성뿐입니다. 다른 방법도 있습니다.

솔루션/해결 방법

(거부자:제가 직접 사용해 본 것은 아니고, 이론을 알고 있을 뿐입니다!)

한 가지 가능한 "해결책"은 개체를 사용하는 것입니다.이 객체는 JSON과 함께 작업하기 위해 특별히 설계된 구체적인 유형을 제공합니다.

당신은 단지 서명을 조정해서 신체로부터 단지 하나의 복잡한 물체를 받아들이면 됩니다.JObject, 이쯤 해 두자stuff. 그런 다음 JSON 객체의 속성을 수동으로 파싱하고 제네릭을 사용하여 콘크리트 유형에 수분을 공급해야 합니다.

예를 들어, 아래는 여러분에게 아이디어를 제공할 수 있는 간단한 '더러운' 예입니다.

public void StartProcessiong([FromBody]JObject stuff)
{
  // Extract your concrete objects from the json object.
  var content = stuff["content"].ToObject<Content>();
  var config = stuff["config"].ToObject<Config>();

  . . . // Now do your thing!
}

다른 방법도 있다고 말씀드렸습니다. 예를 들어, 두 개체를 자신이 만든 초개체로 간단히 포장하고 그것을 자신의 행동 방법에 전달할 수 있습니다.또는 URI에서 두 개의 복잡한 파라미터 중 하나를 제공함으로써 요청 기관에 필요한 두 개의 파라미터를 간단히 제거할 수도 있습니다. 아니면... 요점을 이해할 수 있습니다.

이론적으로는 모든 것이 효과가 있겠지만, 제가 직접 시도해 본 적이 없다는 것을 다시 한번 말씀드리겠습니다.

@djikay가 언급했듯이, 당신은 여러개를 통과할 수 없습니다.FromBody매개 변수들.

제가 해결할 수 있는 한 가지 방법은CompositeObject,

public class CompositeObject
{
    public Content Content { get; set; }
    public Config Config { get; set; }
}

웹 API를 통해 이것을 가져가도록 합니다.CompositeObject대신 매개 변수로 사용합니다.

public void StartProcessiong([FromBody] CompositeObject composite)
{ ... }

다음과 같이 클라이언트에서 여러 부분의 컨텐츠를 게시해 볼 수 있습니다.

 using (var httpClient = new HttpClient())
{
    var uri = new Uri("http://example.com/api/controller"));

    using (var formData = new MultipartFormDataContent())
    {
        //add content to form data
        formData.Add(new StringContent(JsonConvert.SerializeObject(content)), "Content");

        //add config to form data
        formData.Add(new StringContent(JsonConvert.SerializeObject(config)), "Config");

        var response = httpClient.PostAsync(uri, formData);
        response.Wait();

        if (!response.Result.IsSuccessStatusCode)
        {
            //error handling code goes here
        }
    }
}

서버 측에서는 다음과 같은 내용을 읽을 수 있습니다.

public async Task<HttpResponseMessage> Post()
{
    //make sure the post we have contains multi-part data
    if (!Request.Content.IsMimeMultipartContent())
    {
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    }

    //read data
    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);

    //declare backup file summary and file data vars
    var content = new Content();
    var config = new Config();

    //iterate over contents to get Content and Config
    foreach (var requestContents in provider.Contents)
    {
        if (requestContents.Headers.ContentDisposition.Name == "Content")
        {
            content = JsonConvert.DeserializeObject<Content>(requestContents.ReadAsStringAsync().Result);
        }
        else if (requestContents.Headers.ContentDisposition.Name == "Config")
        {
            config = JsonConvert.DeserializeObject<Config>(requestContents.ReadAsStringAsync().Result);
        }
    }

    //do something here with the content and config and set success flag
    var success = true;

    //indicate to caller if this was successful
    HttpResponseMessage result = Request.CreateResponse(success ? HttpStatusCode.OK : HttpStatusCode.InternalServerError, success);
    return result;

}

}

이것이 오래된 질문이라는 것을 알고 있지만, 저는 같은 문제가 있었고 여기 제가 생각해낸 것이 있고 누군가에게 유용하기를 바랍니다.그러면 요청 URL(GET)에서 JSON 형식의 매개 변수를 개별적으로 전달하거나, ?(GET) 이후에 하나의 단일 JSON 개체로 전달하거나, 단일 JSON 본문 개체(POST) 내에서 전달할 수 있습니다.제 목표는 RPC 스타일의 기능이었습니다.

HttpParameterBinding에서 상속하여 사용자 지정 특성 및 매개 변수 바인딩을 만들었습니다.

public class JSONParamBindingAttribute : Attribute
{

}

public class JSONParamBinding : HttpParameterBinding
{

    private static JsonSerializer _serializer = JsonSerializer.Create(new JsonSerializerSettings()
    {
        DateTimeZoneHandling = DateTimeZoneHandling.Utc
    });


    public JSONParamBinding(HttpParameterDescriptor descriptor)
        : base(descriptor)
    {
    }

    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
                                                HttpActionContext actionContext,
                                                CancellationToken cancellationToken)
    {
        JObject jobj = GetJSONParameters(actionContext.Request);

        object value = null;

        JToken jTokenVal = null;
        if (!jobj.TryGetValue(Descriptor.ParameterName, out jTokenVal))
        {
            if (Descriptor.IsOptional)
                value = Descriptor.DefaultValue;
            else
                throw new MissingFieldException("Missing parameter : " + Descriptor.ParameterName);
        }
        else
        {
            try
            {
                value = jTokenVal.ToObject(Descriptor.ParameterType, _serializer);
            }
            catch (Newtonsoft.Json.JsonException e)
            {
                throw new HttpParseException(String.Join("", "Unable to parse parameter: ", Descriptor.ParameterName, ". Type: ", Descriptor.ParameterType.ToString()));
            }
        }

        // Set the binding result here
        SetValue(actionContext, value);

        // now, we can return a completed task with no result
        TaskCompletionSource<AsyncVoid> tcs = new TaskCompletionSource<AsyncVoid>();
        tcs.SetResult(default(AsyncVoid));
        return tcs.Task;
    }

    public static HttpParameterBinding HookupParameterBinding(HttpParameterDescriptor descriptor)
    {
        if (descriptor.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<JSONParamBindingAttribute>().Count == 0 
            && descriptor.ActionDescriptor.GetCustomAttributes<JSONParamBindingAttribute>().Count == 0)
            return null;

        var supportedMethods = descriptor.ActionDescriptor.SupportedHttpMethods;

        if (supportedMethods.Contains(HttpMethod.Post) || supportedMethods.Contains(HttpMethod.Get))
        {
            return new JSONParamBinding(descriptor);
        }

        return null;
    }

    private JObject GetJSONParameters(HttpRequestMessage request)
    {
        JObject jobj = null;
        object result = null;
        if (!request.Properties.TryGetValue("ParamsJSObject", out result))
        {
            if (request.Method == HttpMethod.Post)
            {
                jobj = JObject.Parse(request.Content.ReadAsStringAsync().Result);
            }
            else if (request.RequestUri.Query.StartsWith("?%7B"))
            {
                jobj = JObject.Parse(HttpUtility.UrlDecode(request.RequestUri.Query).TrimStart('?'));
            }
            else
            {
                jobj = new JObject();
                foreach (var kvp in request.GetQueryNameValuePairs())
                {
                    jobj.Add(kvp.Key, JToken.Parse(kvp.Value));
                }
            }
            request.Properties.Add("ParamsJSObject", jobj);
        }
        else
        {
            jobj = (JObject)result;
        }

        return jobj;
    }



    private struct AsyncVoid
    {
    }
}

WebApiConfig.cs 의 Register 메서드 안에 바인딩 규칙 삽입:

        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.ParameterBindingRules.Insert(0, JSONParamBinding.HookupParameterBinding);

            config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
        }

이를 통해 다음과 같은 기본 매개 변수 값과 혼합 복잡성을 가진 컨트롤러 작업을 수행할 수 있습니다.

[JSONParamBinding]
    [HttpPost, HttpGet]
    public Widget DoWidgetStuff(Widget widget, int stockCount, string comment="no comment")
    {
        ... do stuff, return Widget object
    }

예제 포스트 본문:

{ 
    "widget": { 
        "a": 1, 
        "b": "string", 
        "c": { "other": "things" } 
    }, 
    "stockCount": 42, 
    "comment": "sample code"
} 

또는 GET 단일 매개 변수(URL 인코딩 필요)

controllerPath/DoWidgetStuff?{"widget":{..},"comment":"test","stockCount":42}

또는 GET multiple param(URL 인코딩 필요)

controllerPath/DoWidgetStuff?widget={..}&comment="test"&stockCount=42

다른 사람들이 언급한 것처럼 컨텐츠와 구성을 결합하기 위해 하나의 복잡한 객체를 만들고 동적을 사용한 후 그냥 a를 수행합니다.대상 개체(); 다음과 같습니다.

[HttpPost]
public void StartProcessiong([FromBody] dynamic obj)
{
   var complexObj= obj.ToObject<ComplexObj>();
   var content = complexObj.Content;
   var config = complexObj.Config;
}

webapi 서비스에 여러 개의 복잡한 개체를 전달하는 가장 좋은 방법은 dynamic, json string, custom class 이외의 튜플을 사용하는 것입니다.

HttpClient.PostAsJsonAsync("http://Server/WebService/Controller/ServiceMethod?number=" + number + "&name" + name, Tuple.Create(args1, args2, args3, args4));

[HttpPost]
[Route("ServiceMethod")]
[ResponseType(typeof(void))]
public IHttpActionResult ServiceMethod(int number, string name, Tuple<Class1, Class2, Class3, Class4> args)
{
    Class1 c1 = (Class1)args.Item1;
    Class2 c2 = (Class2)args.Item2;
    Class3 c3 = (Class3)args.Item3;
    Class4 c4 = (Class4)args.Item4;
    /* do your actions */
    return Ok();
}

튜플을 사용하는 동안 지나가는 개체를 직렬화 및 역직렬화할 필요가 없습니다.7개 이상의 복잡한 개체를 보내려면 마지막 튜플 인수에 대한 내부 튜플 개체를 만듭니다.

여기서 저는 Jobject를 사용하여 jquery에서 WEB API로 여러 개의 일반 객체(json으로)를 전달한 다음 api 컨트롤러에서 필요한 특정 객체 유형으로 다시 캐스트하는 해결 방법을 찾았습니다.이 객체는 JSON과 함께 작업하기 위해 특별히 설계된 구체적인 유형을 제공합니다.

var combinedObj = {}; 
combinedObj["obj1"] = [your json object 1]; 
combinedObj["obj2"] = [your json object 2];

$http({
       method: 'POST',
       url: 'api/PostGenericObjects/',
       data: JSON.stringify(combinedObj)
    }).then(function successCallback(response) {
         // this callback will be called asynchronously
         // when the response is available
         alert("Saved Successfully !!!");
    }, function errorCallback(response) {
         // called asynchronously if an error occurs
         // or server returns response with an error status.
         alert("Error : " + response.data.ExceptionMessage);
});

그러면 이 물체를 당신의 컨트롤러에 넣을 수 있습니다.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public [OBJECT] PostGenericObjects(object obj)
    {
        string[] str = GeneralMethods.UnWrapObjects(obj);
        var item1 = JsonConvert.DeserializeObject<ObjectType1>(str[0]);
        var item2 = JsonConvert.DeserializeObject<ObjectType2>(str[1]);

        return *something*;
    } 

저는 복잡한 객체의 포장을 푸는 일반 기능을 만들어서, 보내고 푸는 동안 객체의 개수에 제한이 없습니다.우리는 심지어 2개 이상의 물건을 보낼 수 있습니다.

public class GeneralMethods
{
    public static string[] UnWrapObjects(object obj)
    {
        JObject o = JObject.Parse(obj.ToString());

        string[] str = new string[o.Count];

        for (int i = 0; i < o.Count; i++)
        {
            string var = "obj" + (i + 1).ToString();
            str[i] = o[var].ToString(); 
        }

        return str;
    }

}

저는 쉽게 통합할 수 있도록 간단한 코드와 함께 조금 더 자세한 설명으로 해결책을 블로그에 올렸습니다.

여러 개의 복잡한 개체를 Web API로 전달

누군가에게 도움이 되었으면 좋겠습니다.이 방법론을 사용하는 것에 대한 장단점에 대해 여기 전문가들의 의견을 듣고 싶습니다.

여기에 당신에게 유용할 수 있는 또 다른 패턴이 있습니다.이것은 Get을 위한 것이지만 Post/Put에는 동일한 원리와 코드가 적용되지만 반대입니다.기본적으로 개체를 이 ObjectWrapper 클래스로 변환하여 유형 이름을 다른 쪽으로 유지하는 원리로 작동합니다.

using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace WebAPI
{
    public class ObjectWrapper
    {
        #region Public Properties
        public string RecordJson { get; set; }
        public string TypeFullName { get; set; }
        #endregion

        #region Constructors

        public ObjectWrapper() : this(null, null)
        {
        }

        public ObjectWrapper(object objectForWrapping) : this(objectForWrapping, null)
        {
        }

        public ObjectWrapper(object objectForWrapping, string typeFullName)
        {
            if (typeFullName == null && objectForWrapping != null)
            {
                TypeFullName = objectForWrapping.GetType().FullName;
            }
            else
            {
                TypeFullName = typeFullName;
            }

            RecordJson = JsonConvert.SerializeObject(objectForWrapping);
        }
        #endregion

        #region Public Methods
        public object ToObject()
        {
            var type = Type.GetType(TypeFullName);
            return JsonConvert.DeserializeObject(RecordJson, type);
        }
        #endregion

        #region Public Static Methods
        public static List<ObjectWrapper> WrapObjects(List<object> records)
        {
            var retVal = new List<ObjectWrapper>();
            records.ForEach
            (item =>
            {
                retVal.Add
                (
                    new ObjectWrapper(item)
                );
            }
            );

            return retVal;
        }

        public static List<object> UnwrapObjects(IEnumerable<ObjectWrapper> objectWrappers)
        {
            var retVal = new List<object>();

            foreach(var item in objectWrappers)
            {
                retVal.Add
                (
                    item.ToObject()
                );
            }

            return retVal;
        }
        #endregion
    }
}

REST 코드에서:

[HttpGet]
public IEnumerable<ObjectWrapper> Get()
{
    var records = new List<object>();
    records.Add(new TestRecord1());
    records.Add(new TestRecord2());
    var wrappedObjects = ObjectWrapper.WrapObjects(records);
    return wrappedObjects;
}

REST 클라이언트 라이브러리를 사용하는 클라이언트 측(UWP) 코드입니다.클라이언트 라이브러리는 Newtonsoft Json 직렬화 라이브러리를 사용할 뿐이며 화려한 것은 아닙니다.

private static async Task<List<object>> Getobjects()
{
    var result = await REST.Get<List<ObjectWrapper>>("http://localhost:50623/api/values");
    var wrappedObjects = (IEnumerable<ObjectWrapper>) result.Data;
    var unwrappedObjects =  ObjectWrapper.UnwrapObjects(wrappedObjects);
    return unwrappedObjects;
}

기본적으로 별도의 화려한 일을 하지 않고도 복잡한 물건을 보낼 수 있습니다.또는 Web-Api를 변경하지 않을 수도 있습니다.제 말은 웹-Api라는 코드에 결함이 있는데 왜 웹-Api를 변경해야 하느냐는 것입니다.

뉴턴소프트의 Json 라이브러리를 사용하면 다음과 같습니다.

string jsonObjectA = JsonConvert.SerializeObject(objectA);
string jsonObjectB = JsonConvert.SerializeObject(objectB);
string jSoNToPost = string.Format("\"content\": {0},\"config\":\"{1}\"",jsonObjectA , jsonObjectB );
//wrap it around in object container notation
jSoNToPost = string.Concat("{", jSoNToPost , "}"); 
//convert it to JSON acceptible content
HttpContent content = new StringContent(jSoNToPost , Encoding.UTF8, "application/json"); 

var response = httpClient.PutAsync("api/process/StartProcessiong", content);

답변이 늦었지만 개체가 공통 속성 이름을 공유하지 않는 한 하나의 JSON 문자열에서 여러 개체를 병렬화할 수 있다는 점을 이용할 수 있습니다.

    public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
    {
        var jsonString = await request.Content.ReadAsStringAsync();
        var content  = JsonConvert.DeserializeObject<Content >(jsonString);
        var config  = JsonConvert.DeserializeObject<Config>(jsonString);
    }

합성물 객체 만들기

 public class CollectiveObject<X, Y>
{
    public X FirstObj;

    public Y SecondObj;
}

보내려는 두 개체로 복합 개체를 초기화합니다.

 CollectiveObject<myobject1, myobject2> collectiveobj =
                   new CollectiveObject<myobject1, myobject2>();
            collectiveobj.FirstObj = myobj1;
            collectiveobj.SecondObj = myobj2;

직렬화하기

   var req = JSONHelper.JsonSerializer`<CollectiveObject<myobject1, `myobject2>>(collectiveobj);`

`

당신의 API는 다음과 같습니다.

    [Route("Add")]


public List<APIAvailibilityDetails> Add([FromBody]CollectiveObject<myobject1, myobject2> collectiveobj)
    { //to do}

개체로 Tuple을 만들 수 있습니다.

public IActionResult Index(int id,int dateId,CultureType cultureType) 
{
var tourbyid = _tourService.GetById(id, cultureType);
var tourbydate = _tourService.GetByIdDate(dateId, cultureType);

            
            return Ok(Tuple.Create(tourbydate , tourbyid));
}

언급URL : https://stackoverflow.com/questions/24874490/pass-multiple-complex-objects-to-a-post-put-web-api-method