Pass multiple complex objects to a post/put Web API method

后端 未结 11 1418
南旧
南旧 2020-11-29 21:51

Can some please help me to know how to pass multiple objects from a C# console app to Web API controller as shown below?

using (var httpClient = new System.N         


        
相关标签:
11条回答
  • 2020-11-29 21:58

    You could try posting multipart content from the client like this:

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

    On the server side you could read the the content like this:

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

    }

    0 讨论(0)
  • 2020-11-29 21:59

    Best way to pass multiple complex object to webapi services is by using tuple other than 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();
    }
    

    No need to serialize and deserialize passing object while using tuple. If you want to send more than seven complex object create internal tuple object for last tuple argument.

    0 讨论(0)
  • 2020-11-29 22:02

    As @djikay mentioned, you cannot pass multiple FromBody parameters.

    One workaround I have is to define a CompositeObject,

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

    and have your WebAPI takes this CompositeObject as the parameter instead.

    public void StartProcessiong([FromBody] CompositeObject composite)
    { ... }
    
    0 讨论(0)
  • 2020-11-29 22:05

    Here I found a workaround to pass multiple generic objects (as json) from jquery to a WEB API using JObject, and then cast back to your required specific object type in api controller. This objects provides a concrete type specifically designed for working with 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);
    });
    

    and then you can get this object in your controller

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

    I have made a generic function to unwrap the complex object, so there is no limitation of number of objects while sending and unwrapping. We can even send more than two objects

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

    I have posted the solution to my blog with a little more description with simpler code to integrate easily.

    Pass multiple complex objects to Web API

    I hope it would help someone. I would be interested to hear from the experts here regarding the pros and cons of using this methodology.

    0 讨论(0)
  • 2020-11-29 22:06

    I know this is an old question, but I had the same issue and here is what I came up with and hopefully will be useful to someone. This will allow passing JSON formatted parameters individually in request URL (GET), as one single JSON object after ? (GET) or within single JSON body object (POST). My goal was RPC-style functionality.

    Created a custom attribute and parameter binding, inheriting from 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
        {
        }
    }
    

    Inject binding rule inside WebApiConfig.cs's Register method:

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

    This allows for controller actions with default parameter values and mixed complexity, as such:

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

    example post body:

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

    or GET single param (needs URL encoding)

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

    or GET multiple param (needs URL encoding)

    controllerPath/DoWidgetStuff?widget={..}&comment="test"&stockCount=42
    
    0 讨论(0)
  • 2020-11-29 22:17

    Late answer, but you can take advantage of the fact that you can deserialize multiple objects from one JSON string, as long as the objects don't share any common property names,

        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);
        }
    
    0 讨论(0)
提交回复
热议问题