185

I am trying to post multiple parameters on a WebAPI controller. One param is from the URL, and the other from the body. Here is the url: /offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

Here is my controller code:

public HttpResponseMessage Put(Guid offerId, OfferPriceParameters offerPriceParameters)
{
    //What!?
    var ser = new DataContractJsonSerializer(typeof(OfferPriceParameters));
    HttpContext.Current.Request.InputStream.Position = 0;
    var what = ser.ReadObject(HttpContext.Current.Request.InputStream);

    return new HttpResponseMessage(HttpStatusCode.Created);
}

The content of the body is in JSON:

{
    "Associations":
    {
        "list": [
        {
            "FromEntityId":"276774bb-9bd9-4bbd-a7e7-6ed3d69f196f",
            "ToEntityId":"ed0d2616-f707-446b-9e40-b77b94fb7d2b",
            "Types":
            {
                "list":[
                {
                    "BillingCommitment":5,
                    "BillingCycle":5,
                    "Prices":
                    {
                        "list":[
                        {
                            "CurrencyId":"274d24c9-7d0b-40ea-a936-e800d74ead53",
                            "RecurringFee":4,
                            "SetupFee":5
                        }]
                    }
                }]
            }
        }]
    }
}

Any idea why the default binding is not able to bind to the offerPriceParameters argument of my controller? It is always set to null. But I am able to recover the data from the body using the DataContractJsonSerializer.

I also try to use the FromBody attribute of the argument but it does not work either.

CodingGorilla
  • 19,612
  • 4
  • 45
  • 65
Normand Bedard
  • 2,625
  • 2
  • 19
  • 22

13 Answers13

108
[HttpPost]
public string MyMethod([FromBody]JObject data)
{
    Customer customer = data["customerData"].ToObject<Customer>();
    Product product = data["productData"].ToObject<Product>();
    Employee employee = data["employeeData"].ToObject<Employee>();
    //... other class....
}

using referance

using Newtonsoft.Json.Linq;

Use Request for JQuery Ajax

var customer = {
    "Name": "jhon",
    "Id": 1,
};
var product = {
    "Name": "table",
    "CategoryId": 5,
    "Count": 100
};
var employee = {
    "Name": "Fatih",
    "Id": 4,
};

var myData = {};
myData.customerData = customer;
myData.productData = product;
myData.employeeData = employee;

$.ajax({
    type: 'POST',
    async: true,
    dataType: "json",
    url: "Your Url",
    data: myData,
    success: function (data) {
        console.log("Response Data ↓");
        console.log(data);
    },
    error: function (err) {
        console.log(err);
    }
});
Fatih GÜRDAL
  • 1,489
  • 1
  • 17
  • 19
  • 5
    Great solution. If it's not already clear to others, you can also use .ToObject(), .ToObject(), .ToString(), etc if you are passing in simple, multiple parameters from your ajax call. – secretwep Dec 30 '16 at 18:28
  • Thank you, I have tried your solution by creating my own API and testing it through Postman and it is working fine;But I have added a fourth parameter like var test= {"Name" : "test" } and added it to myData object and it was sent successfully; is there anyway to avoid this and to restrict only original objects? – Mlle 116 Nov 15 '17 at 08:32
  • @H.Al No, Newtonsoft.Json can have any kind of json data that the library knows about translation. You can not prevent sending data. It depends on you to use the incoming data – Fatih GÜRDAL Nov 16 '17 at 07:07
  • This gives a nasty error if you do not properly adapt ConfigureServices(); https://stackoverflow.com/questions/60831768/the-json-value-could-not-be-converted-to-newtonsoft-json-linq-jtoken-path It is also an old solution by now, there are better choices, see below. – Alvaro Rodriguez Scelza Apr 19 '21 at 21:20
71

Natively WebAPI doesn't support binding of multiple POST parameters. As Colin points out there are a number of limitations that are outlined in my blog post he references.

There's a workaround by creating a custom parameter binder. The code to do this is ugly and convoluted, but I've posted code along with a detailed explanation on my blog, ready to be plugged into a project here:

Passing multiple simple POST Values to ASP.NET Web API

Rick Strahl
  • 17,302
  • 14
  • 89
  • 134
  • 1
    All the credit goes to you :) I just happened to be reading your series on WebAPI while starting my own implementation when this question popped up. – Colin Young Jan 19 '13 at 21:24
  • 2
    As of 2019 it does now. – Max Feb 09 '19 at 05:53
  • @John - is there a base version from which this functionality is supported? Not having any success today. – Neil Moss Aug 20 '19 at 15:46
  • @Max do you have a source for that? I'm running into the same problem when passing more than one param. – Lovethenakedgun Dec 21 '22 at 17:00
  • 1
    @Lovethenakedgun sorry for late reply - didn't see until now. The trick is you cannot pass object parameters and expect them to be sent as JSON and deserialized server side to their C# equivalent. Instead, 1) on the client-side pass all your parameters as string (use json.stringify() for complex objects), and 2) post your data with Content-Type set to multipart/form-data (basic POST); and 3) on the server side, your controller method should have a variable for all the parameters sent and 4) deserialize those that are json strings, and that's it. Lots of answers here are too complicated. – Max Jan 17 '23 at 05:36
34

If attribute routing is being used, you can use the [FromUri] and [FromBody] attributes.

Example:

[HttpPost()]
[Route("api/products/{id:int}")]
public HttpResponseMessage AddProduct([FromUri()] int id,  [FromBody()] Product product)
{
  // Add product
}
Bryan Rayner
  • 4,172
  • 4
  • 26
  • 38
  • 1
    I have used exactly the same method. I need to pass two Models to the action. I have passed one with the less properties via query string and other from body. Also you don't need to explicitly specify the [FromBody] attribyte – SerjG Sep 09 '15 at 20:45
  • 1
    I can't make this work, do you have a more complete example? – The One Apr 06 '16 at 20:46
  • I dont think this is the right way to send data via POST method but I dont see another solution if you have to send 2 models via post. – Alexandr Apr 06 '17 at 09:48
  • This answer is the Jam! – Leonardo Wildt May 22 '18 at 15:44
  • 6
    I am using aspnetcore and you have to use `[FromRoute]` instead of `[FromUri]` – DanielV Nov 27 '19 at 10:56
  • I got [FromUri] to work, but only without the [Route] attribute.The key (for me) was to add "/?id=1" when calling to api: "api/products/?id=1" Found the solution in this example: https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api#using-fromuri – jornhd Dec 04 '20 at 15:27
26

We passed Json object by HttpPost method, and parse it in dynamic object. it works fine. this is sample code:

webapi:

[HttpPost]
public string DoJson2(dynamic data)
{
   //whole:
   var c = JsonConvert.DeserializeObject<YourObjectTypeHere>(data.ToString()); 

   //or 
   var c1 = JsonConvert.DeserializeObject< ComplexObject1 >(data.c1.ToString());

   var c2 = JsonConvert.DeserializeObject< ComplexObject2 >(data.c2.ToString());

   string appName = data.AppName;
   int appInstanceID = data.AppInstanceID;
   string processGUID = data.ProcessGUID;
   int userID = data.UserID;
   string userName = data.UserName;
   var performer = JsonConvert.DeserializeObject< NextActivityPerformers >(data.NextActivityPerformers.ToString());

   ...
}

The complex object type could be object, array and dictionary.

ajaxPost:
...
Content-Type: application/json,
data: {"AppName":"SamplePrice",
       "AppInstanceID":"100",
       "ProcessGUID":"072af8c3-482a-4b1c‌​-890b-685ce2fcc75d",
       "UserID":"20",
       "UserName":"Jack",
       "NextActivityPerformers":{
           "39‌​c71004-d822-4c15-9ff2-94ca1068d745":[{
                 "UserID":10,
                 "UserName":"Smith"
           }]
       }}
...
Bes Ley
  • 1,685
  • 1
  • 20
  • 39
  • 1
    We can put multiple parameters formatted as one json object to post, and we will parse it to multiple objects later in server side. This could be another way to think. – Bes Ley Jul 19 '13 at 03:36
  • @EkoosticMartin, It works fine, you need to parse the dynamic type by using: **JsonConvert.DeserializeObject(data.ToString());** A complex data content sample is here, it includes array and dictionary object. {"AppName":"SamplePrice","AppInstanceID":"100","ProcessGUID":"072af8c3-482a-4b1c-890b-685ce2fcc75d","UserID":"20","UserName":"Jack","NextActivityPerformers":{"39c71004-d822-4c15-9ff2-94ca1068d745":[{"UserID":10,"UserName":"Smith"}]}} – Bes Ley Jul 20 '13 at 05:08
  • 1
    Okay sure, then just use a single string param, there is no diff. – EkoostikMartin Jul 20 '13 at 13:31
  • Single doesnt means simple, json string could be combined with many different type of objects. This is the key point, and is another way to solve questions. – Bes Ley Jul 22 '13 at 01:03
  • Works like a charm. Default deserializer fails miserably; `Newtonsoft`... bim, bam, DONE! Works perfectly. – eidylon Mar 24 '22 at 18:25
18

A simple parameter class can be used to pass multiple parameters in a post:

public class AddCustomerArgs
{
    public string First { get; set; }
    public string Last { get; set; }
}

[HttpPost]
public IHttpActionResult AddCustomer(AddCustomerArgs args)
{
    //use args...
    return Ok();
}
Greg Gum
  • 33,478
  • 39
  • 162
  • 233
  • 1
    Any chance you know how the sample POST request should look? – Nadia Solovyeva Jul 23 '20 at 15:16
  • @NadiaSolovyeva, It's more than a query string, because the POSTED information is in the body, not the query string. I like to use PostMan to make test queries, and then you can see exactly what it looks like. – Greg Gum Jul 23 '20 at 20:24
  • Never mind, I have already found how to do it. POST header: Content-Type: application/json; POST body: { "First":"1", "Last":"1000" } – Nadia Solovyeva Jul 24 '20 at 04:00
11

Nice question and comments - learnt much from the replies here :)

As an additional example, note that you can also mix body and routes e.g.

[RoutePrefix("api/test")]
public class MyProtectedController 
{
    [Authorize]
    [Route("id/{id}")]
    public IEnumerable<object> Post(String id, [FromBody] JObject data)
    {
        /*
          id                                      = "123"
          data.GetValue("username").ToString()    = "user1"
          data.GetValue("password").ToString()    = "pass1"
         */
    }
}

Calling like this:

POST /api/test/id/123 HTTP/1.1
Host: localhost
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer x.y.z
Cache-Control: no-cache

username=user1&password=pass1


enter code here
Anthony De Souza
  • 544
  • 5
  • 10
  • 1
    I would like to send 2 complex type parameters. Like as[HttpPost] public string UploadFile(UploadMediaFile mediaFile, byte[] datas) how to do it. – Başar Kaya Aug 07 '18 at 09:02
8

You can allow multiple POST parameters by using the MultiPostParameterBinding class from https://github.com/keith5000/MultiPostParameterBinding

To use it:

1) Download the code in the Source folder and add it to your Web API project or any other project in the solution.

2) Use attribute [MultiPostParameters] on the action methods that need to support multiple POST parameters.

[MultiPostParameters]
public string DoSomething(CustomType param1, CustomType param2, string param3) { ... }

3) Add this line in Global.asax.cs to the Application_Start method anywhere before the call to GlobalConfiguration.Configure(WebApiConfig.Register):

GlobalConfiguration.Configuration.ParameterBindingRules.Insert(0, MultiPostParameterBinding.CreateBindingForMarkedParameters);

4) Have your clients pass the parameters as properties of an object. An example JSON object for the DoSomething(param1, param2, param3) method is:

{ param1:{ Text:"" }, param2:{ Text:"" }, param3:"" }

Example JQuery:

$.ajax({
    data: JSON.stringify({ param1:{ Text:"" }, param2:{ Text:"" }, param3:"" }),
    url: '/MyService/DoSomething',
    contentType: "application/json", method: "POST", processData: false
})
.success(function (result) { ... });

Visit the link for more details.

Disclaimer: I am directly associated with the linked resource.

bPratik
  • 6,894
  • 4
  • 36
  • 67
Keith
  • 20,636
  • 11
  • 84
  • 125
5

2021 and there are new solutions. Pradip Rupareliya suggested a good one, that I'll complement using only Dict, instead of a helper data structure as He did:

[HttpPost]
public ActionResult MakePurchase([FromBody] Dictionary<string, string> data)
{
    try
    {
        int userId = int.Parse(data["userId"]);
        float boughtAmountInARS = float.Parse(data["boughtAmountInARS"]);
        string currencyName = data["currencyName"];
    }
    catch (KeyNotFoundException)
    {
        return BadRequest();
    }
    catch (FormatException)
    {
        return BadRequest();
    }
}
Alvaro Rodriguez Scelza
  • 3,643
  • 2
  • 32
  • 47
3

If you don't want to go ModelBinding way, you can use DTOs to do this for you. For example, create a POST action in DataLayer which accepts a complex type and send data from the BusinessLayer. You can do it in case of UI->API call.

Here are sample DTO. Assign a Teacher to a Student and Assign multiple papers/subject to the Student.

public class StudentCurriculumDTO
 {
     public StudentTeacherMapping StudentTeacherMapping { get; set; }
     public List<Paper> Paper { get; set; }
 }    
public class StudentTeacherMapping
 {
     public Guid StudentID { get; set; }
     public Guid TeacherId { get; set; }
 }

public class Paper
 {
     public Guid PaperID { get; set; }
     public string Status { get; set; }
 }

Then the action in the DataLayer can be created as:

[HttpPost]
[ActionName("MyActionName")]
public async Task<IHttpActionResult> InternalName(StudentCurriculumDTO studentData)
  {
     //Do whatever.... insert the data if nothing else!
  }

To call it from the BusinessLayer:

using (HttpResponseMessage response = await client.PostAsJsonAsync("myendpoint_MyActionName", dataof_StudentCurriculumDTO)
  {
     //Do whatever.... get response if nothing else!
  }

Now this will still work if I wan to send data of multiple Student at once. Modify the MyAction like below. No need to write [FromBody], WebAPI2 takes the complex type [FromBody] by default.

public async Task<IHttpActionResult> InternalName(List<StudentCurriculumDTO> studentData)

and then while calling it, pass a List<StudentCurriculumDTO> of data.

using (HttpResponseMessage response = await client.PostAsJsonAsync("myendpoint_MyActionName", List<dataof_StudentCurriculumDTO>)
sandiejat
  • 2,552
  • 19
  • 24
2

What does your routeTemplate look like for this case?

You posted this url:

/offers/40D5E19D-0CD5-4FBD-92F8-43FDBB475333/prices/

In order for this to work I would expect a routing like this in your WebApiConfig:

routeTemplate: {controller}/{offerId}/prices/

Other assumptions are: - your controller is called OffersController. - the JSON object you are passing in the request body is of type OfferPriceParameters (not any derived type) - you don't have any other methods on the controller that could interfere with this one (if you do, try commenting them out and see what happens)

And as Filip mentioned it would help your questions if you started accepting some answers as 'accept rate of 0%' might make people think that they are wasting their time

Joanna Derks
  • 4,033
  • 3
  • 26
  • 32
1

Request parameters like

enter image description here

Web api Code be like

public class OrderItemDetailsViewModel
{
    public Order order { get; set; }
    public ItemDetails[] itemDetails { get; set; }
}

public IHttpActionResult Post(OrderItemDetailsViewModel orderInfo)
{
    Order ord = orderInfo.order;
    var ordDetails = orderInfo.itemDetails;
    return Ok();
}
Pradip Rupareliya
  • 545
  • 1
  • 6
  • 18
0

You can get the formdata as string:

    protected NameValueCollection GetFormData()
    {
        string root = HttpContext.Current.Server.MapPath("~/App_Data");
        var provider = new MultipartFormDataStreamProvider(root);

        Request.Content.ReadAsMultipartAsync(provider);

        return provider.FormData;
    }

    [HttpPost]
    public void test() 
    {
        var formData = GetFormData();
        var userId = formData["userId"];

        // todo json stuff
    }

https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/sending-html-form-data-part-2

Martien de Jong
  • 731
  • 1
  • 7
  • 19
0

Actully there is no way to use a multiple parameters in controller. if you "say why I can't?" I have to awnser your question with this:

The reason for this rule is that the request body might be stored in a non-buffered stream that can only be read once. Also you can read this article.

and now we know why we can't use from body in th multiple parameters, so what is solution? The solution should be using the class. you can create the class and use those parameters in that class like a property and then use that class on the input of API! and also this is the best way you can use the multiple parameters in the [FromBody]. But if I have finde the another way I'll say.

Amir Tahan
  • 107
  • 8