I have a .net mvc 4 webapi project that I'm trying to pass an array of an object to a method on my controller.
I've found some examples here on SO that talk about needing to set my object's properties with: param1=whatever¶m2=bling¶m3=blah.
But I don't see how I can pass in a collection using that.
Here is my method signature. Notice I've decorated the argument with the [FromUri] attribute.
public List<PhoneResult> GetPhoneNumbersByNumbers([FromUri] PhoneRequest[] id)
{
List<PhoneResult> prs = new List<PhoneResult>();
foreach (PhoneRequest pr in id)
{
prs.Add(PhoneNumberBL.GetSinglePhoneResult(pr.PhoneNumber, pr.RfiDate, pr.FinDate, pr.State));
}
return prs;
}
here is my simple PhoneRequest object:
public class PhoneRequest
{
public string PhoneNumber { get; set; }
public string RfiDate { get; set; }
public string FinDate { get; set; }
public string State { get; set; }
}
and here's a sample of what I'm using to pass in:
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers/
[{"PhoneNumber":"8016667777","RfiDate":"","FinDate":"2012-02-11","State":"UT"},
{"PhoneNumber":"8018889999","RfiDate":"2012-12-01","FinDate":"","State":"UT"}]
using this comes back with "bad request"
I also tried this
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers?
id=[{"PhoneNumber":"8016667777","RfiDate":"","FinDate":"2012-02-11","State":"UT"},
{"PhoneNumber":"8018889999","RfiDate":"2012-12-01","FinDate":"","State":"UT"}]
which does reach the method, but the array is null.
how can I pass in an array of my PhoneRequest object to my Web API method?
Try passing the PhoneRequest[] from the uri in this format:
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers?
id[0][PhoneNumber]=8016667777&id[0][FinDate]=2012-02-11&id[0][State]=UT&
id[1][PhoneNumber]=8018889999&id[1][RfiDate]=2012-12-01&id[1][State]=UT
I suggest you use POST for this.
As you query string grows, you will run into problems with the maximum length of the URL, which is browser dependent.
If you have a lot of parameters to pass, a POST is perfectly acceptable even if you are really only GETting data. What you will lose, however, is the ability for the user to bookmark a particular page with the query string.
I created a custom model binder, the FieldValueModelBinder class, which can effectively pass any object containing nested array or generic list types of data with query strings having field-name pairs without imbedding any JSON and XML structures. The model binder can resolve all issues discussed above. Since this question was extended by the question ID 19302078, you can see details of my answer in that thread.
Related
I have a web api core project that if I send just the list parameter than the API receives the values, however if I send both parameters that the controller is looking for then both parameters are seen as null
My contoller:
[HttpPost]
[Route("/jobApi/RunBD")]
public int RunBDReport([FromBody]int month, [FromBody] IEnumerable<ClientModel> clients)
{
billingDetailCycle objBillDetail = new billingDetailCycle();
if (ModelState.IsValid)
{
return objBillDetail.Run(clients.ToList(), month);
}
else
{
return 500;
}
}
ClientModel:
public class ClientModel
{
public string BlockOfBus { get; set; }
public string ClientId { get; set; }
public string Location { get; set; }
public string SuppressSsn { get; set; }
}
The request I am sending:
{"month":7,
"ClientModel":[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]}
This causes both parameters to be seen as null by the controller, however if I send my request like this:
[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]
Then the controller is able to see the list object I am sending (however it obviously returns 500 as the model is not valid)
[FromBody] can only be used once since the request body can only be read once.
Don't apply [FromBody] to more than one parameter per action method. Once the request stream is read by an input formatter, it's no longer available to be read again for binding other [FromBody] parameters.
Reference Model Binding in ASP.NET Core
Create a single model that matches the expected data.
public class DbReport {
public int month { get; set; }
public ClientModel[] ClientModel { get; set; }
}
And update the action accordingly
[HttpPost]
[Route("/jobApi/RunBD")]
public int RunBDReport([FromBody]DbReport report) {
billingDetailCycle objBillDetail = new billingDetailCycle();
if (ModelState.IsValid) {
return objBillDetail.Run(report.ClientModel.ToList(), report.month);
} else {
return 500;
}
}
There can be only one parameter modified with [FromBody] attribute. So you need to either modify your method like this :
[Route("/jobApi/RunBD/{month}")]
public int RunBDReport(int month, [FromBody] IEnumerable<ClientModel> clients)
Then make the request like this :
url :/jobApi/RunBD/7
body :
[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]
Or modify both your method and model like this :
public class BdPayload{
public int Month {get; set;}
public IEnumerable<ClientModel> ClientModel {get;set;}
}
[Route("/jobApi/RunBD")]
public int RunBDReport( [FromBody] BdPayload model)
and then you can use the second request's body.
Try:
{"month":7,
"clients":[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]}
It looks like your ClientModel enumerable is mistitled in the payload
Try changing the route to:
[Route("/jobApi/RunBD/{month}")]
public int RunBDReport([FromUri]int month, [FromBody] IEnumerable<ClientModel> clients)
The payload needs to be passed as an array, like in Jonathan's answer.
There are few simple rules that help you get through these kind of issues when trying to pass data to your Web API endpoint. These are the default rules based on which the parameter binding happens. Based on these rules, you need to be applying the attributes like [FromBody] and [FromUri]
GET method call takes both primitive and complex types as a part of the query string
POST method call takes a primitive type parameter by default in the query string and the complex type needs to be passed as a part of the request body.
PUT and PATCH follow similar default rules as that of POST.
DELETE method's default rules are inline with the GET method.
Here by primitive types, I mean types like int and complex types are the classes that we create.
You can tackle the problem that you're dealing with by applying any of the solutions that others have already mentioned -- like moving your complex type into your request body and passing the primitive type through the query string OR wrapping both the primitive and complex types into a single model and deserialize the request body to the model type (which is done as a part of the parameter binding inherently).
I am receiving an error message, "Sequence contains no elements" while trying to update a table in SQL from Angular 7 to an AspNet Core controller by passing two model parameters using an "http.post".
I am passing the data from the form to the class models with no problem because I can see the payload data in the browser console. However, when trying to pass the models as parameters in my api service to the controller, all of the parameters in the model are null. I usually don't have an issue when passing one model parm thru, but passing two of them to get to my controller with a [FromBody] doesn't seem to want to work for me.
I tried to wrap the models in curly brackets to pass them, to no avail:
UpdateService(serviceAddress: ServiceAddressModel, contact: ContactModel) {
let reqHeader = new HttpHeaders();
let body = { svc: serviceAddress, cnt: contact };
reqHeader.append('Content-Type', 'application/json');
return this.http.post(this.baseurl + 'api/customermanagement/update-service-address-info', body, { headers: reqHeader });
When I view the request / response in the browser console, I can see the data within the payload, so I know that the data is ready to pass.
My controller is set up as follows:
[Route("update-service-address-info")]
public bool UpdateServiceAddressAccount([FromBody] ServiceAddressEntity svc_id, [FromBody] ContactEntity cnt_id)
{
return serviceAddressService.UpdateServiceAddressAccount(svc_id, cnt_id);
}
Using breakpoints in this call shows null for all values.
If I can properly pass the parameters to my interface, I should be good-to-go. I am sensing that I am not structuring the parameters properly in the http.post body.
Your request body, { svc: serviceAddress, cnt: contact } is received as a json string, e.g. {"svc":{"serviceAddressProperty1":"value",...},"cnt":{"contactProperty1":"value",...}}. The parameters to your action method are bound via the default model binding mechanism (unless you provide your own custom model binding implementation). The default mechanism attempts to create instances by binding from the top level of the json object received with the request. enter code here
In simpler terms, lets assume you class ServiceAddressModel is defined like this:
public class ServiceAddressModel
{
public string Name { get; set; }
public string Property2 { get; set; }
}
the model binder looks for properties with the names "name" and "property2" at the top level of the json tree. If found, these are bound to the Name and Property2 properties of the created instance.
In your case, wrapping your models in a class that can make svc_id and cnt_id the top level properties would work fine. Like this example:
public class MyRequest
{
public ServiceAddressModel svc_id { get; set; }
public ContactEntity cnt_id { get; set; }
}
Then you can declare your action like
[Route("update-service-address-info")]
public bool UpdateServiceAddressAccount([FromBody] MyRequest request)
{
return serviceAddressService.UpdateServiceAddressAccount(request.svc_id, request.cnt_id);
}
Snake casing, camel casing should be allowed by default (you will have to try it, I havent tested that part). That is, if you declare your properties as SvcId and CntId (if you prefer more natural C# naming conventions) it should be able to bind correctly from JSONs with "svc_id" or "cnt_id".
Another option would be to implement custom model binders, but that might be a longer and more complex route.
Hope this helps.
Just try to pass the value like this and see
let body = { svc_id: serviceAddress, cnt_id: contact };
I've been scouring the web for hours and tried many different solutions also described here on StackOverflow. I know similar questions have been asked before, but none of the answers or comments have worked for me.
The problem: I have a .NET Web API that has a Post-method with some parameters.
One of the parameters is a complex object that is supposed to be read from the body (that is JSON). However, this object is always null.
This is my code:
// POST api/worksheets/post_event/true/false
[Route("post_event/{newWorksheet}/{eindEvent}")]
[HttpPost]
public Event Post(bool newWorksheet, bool eindEvent, [FromBody] Event eventData)
{
return eventData;
}
To be clear: eventData is the object that's always null. The boolean values are read correctly.
The full request body is:
POST http://localhost:5000/api/worksheets/post_event/true/false
Content-Type: application/json
{"Persnr":1011875, "WorksheetId":null, "Projectnr":81445, "Uursoort":8678, "Tijd":{"09-08-2016 9:25"}}
And for reference, this is the Event-class:
public class Event
{
public long Persnr { get; set; }
public int WorksheetId { get; set; }
public int Projectnr { get; set; }
public int Uursoort { get; set; }
public DateTime Tijd { get; set; }
}
Some of the things I've already tried:
Change JSON to different formats (only values, "Event": {} surrounding the actual object, an = in front of the JSON).
Test with just the Event parameter (removing the others as well as in the route)
Add a default ctor to Event.
Remove the [FromBody] tag. If I do this, the Event-object is not null, but all the properties are. Properties can be filled through the URI, but that is not the desired behavior.
According to all solutions and documentation I have read, it should simply work the way I have it displayed above.
What am I missing?
Your json object is invalid. My suggestion is to always run json object written manually through a json parser like this: http://json.parser.online.fr/
"Tijd":{"09-08-2016 9:25"}
should instead be
"Tijd":["09-08-2016 9:25"]
This usually happens when your object can't be deserialized from JSON request.
The best practice would be to make sure that all the request properties can accept null values (make value types properties are nullable). And then you can validate that all needed request properties are provided, or return 400 error if not. This way you at least will be able to understand what request property causes the problem.
The right JSON for this type should be just
{"Persnr":1011875, "WorksheetId":null, "Projectnr":81445, "Uursoort":8678, "Tijd":"09-08-2016 9:25"}
No curly braces for Tijd, because Tijd is plain DateTime property that can be inferred from string, representing DateTime
I am working on a REST API for a project using Visual Studio 2013 with C# and ASP.NET, and I need some guidance.
When the webpage performs a POST, I am passing along a number of fields as a JSON object. By defining a data transfer object in my C# code, I can easily read the values from the JSON, but only if I define all the fields (with the same name).
Here is my current (working) code:
public class AgencyPostDTO
{
public string AgencyName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZIP { get; set; }
}
// POST: api/Agency
public string Post(AgencyPostDTO Agency)
{
int success;
success = SQLUpdateAgency(Agency);
if (success < 1)
{
return "Failed";
}
else
{
return "Success";
}
}
So far no problems. I need to pass the data over to a second function, where I will perform some data processing (including converting the data into XML) and send the data/XML to MS SQL using a stored procedure:
public int SQLUpdateAgency(AgencyPostDTO Agency)
{
string xml = Agency.SerializeObject();
... code to call SQL stored procedure ommitted here
}
Now to my problem. I would prefer if I did not have to define the parameters of the data transfer object AgencyPostDTO in the code, and instead the code would just read the incoming JSON and pass it along to the next function, where I create the XML containing everything passed along.
As it works now, if the JSON contains for example an email address field, it will be dropped unless I define it in AgencyPostDTO.
So why do I want to do this? For future ease of maintenance. The users may come and say they want to add additional fields to the web form. I can then simply have our SQL expert add that column to the table, give me the name of it and I add an input field to the HTML form and make sure it is included in the JSON sent over. That way we never have to touch the already written, tested and working code. The new field is simply passed though the whole process.
Can this be done? If so, any suggestions on how?
If you used JSON.NET to handle the deserialisation of your objects then that has support for dynamic properties. Once you'd read your JSON string, you could convert it to a JArray or JObject and from there by using the .Children() call to get a list of all properties to convert it to any XML object you needed.
Have a look here:
Deserialize json object into dynamic object using Json.net
In ASP.NET MVC 2 (yes, TWO, I'm using MONO for this), I would like to know if it is at all possible to bind multiple Request parameters into an Action method parameter.
Let me give an illustration.
I'm passing 2 parameters (using whatever method I like, GET, POST, etc.):
Name
Guid
Is there a way to bind those parameters to this:
public JsonResult MyMethod(NameClass identifier)
Instead of this:
public JsonResult MyMethod(string name, string guid)
Using this?
public class NameClass
{
public string Guid { get; set; }
public string Name { get; set; }
}
Absolutely. You simply have to name your fields using dot notation as if you were going to access the property from inside the method. This means that the Guid field is named identifier.Guid and the Name field identifier.Name. It is too bad that you can't take advantage of strongly-typed user controls however ;).