Reputation: 2685
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.
Upvotes: 187
Views: 493211
Reputation: 129
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.
Upvotes: 0
Reputation: 4164
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();
}
}
Upvotes: 6
Reputation: 1797
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":{
"39c71004-d822-4c15-9ff2-94ca1068d745":[{
"UserID":10,
"UserName":"Smith"
}]
}}
...
Upvotes: 27
Reputation: 781
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
Upvotes: 0
Reputation: 574
Request parameters like
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();
}
Upvotes: 1
Reputation: 3141
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>)
Upvotes: 4
Reputation: 554
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
Upvotes: 11
Reputation: 1519
[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);
}
});
Upvotes: 110
Reputation: 21224
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.
Upvotes: 8
Reputation: 37875
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();
}
Upvotes: 19
Reputation: 4330
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
}
Upvotes: 34
Reputation: 4063
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
Upvotes: 2
Reputation: 17651
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
Upvotes: 71