C# Web API POST parameter FromBody is always null - c#

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

Related

ASP.NET Core: JSON object header parameter

My API sends requests to another system to get data. I am trying to give more control to the consumer of my API over the requests sent to the uderlying system, in the sense of filtering and specifying characteristics of the request.
I am hoping to achieve this by allowing the user to supply a specified JSON object in a header parameter.
Not all actions require this however, so I am using an attribute and an operationfilter to add the parameter where necessary.
The attribute is defined as below:
[AttributeUsage(AttributeTargets.Method)]
public class DmsFilter : Attribute
{
// Nothing here
}
The subsequent OperationFilter applied is as below:
public class DmsFilterOperationFilter : IOperationFilter
{
/// <inheritdoc />
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if(context.MethodInfo.GetCustomAttribute(typeof(DmsFilter), true) is null) return;
operation.Parameters ??= new List<OpenApiParameter>();
OpenApiSchema dmsFilterScheme =
context.SchemaGenerator.GenerateSchema(typeof(DmsRequestModel), context.SchemaRepository);
operation.Parameters.Add(new OpenApiParameter
{
Name = "dmsFilter",
Description = "Configure requests to Indicium here.",
In = ParameterLocation.Header,
Required = false,
Schema = dmsFilterScheme
});
}
private OpenApiSchema CreateDmsFilterScheme()
{
PropertyInfo[] propertyList = typeof(DmsRequestModel).GetProperties();
OpenApiSchema schema = new()
{
Type = nameof(DmsRequestModel),
Default = new OpenApiObject()
};
foreach (PropertyInfo propertyInfo in propertyList)
{
bool isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
if (schema.Properties.ContainsKey(propertyInfo.Name)) continue;
schema.Properties.Add(propertyInfo.Name, new OpenApiSchema
{
Type = propertyInfo.PropertyType.Name,
Default = isNullable ? null : new OpenApiString(string.Empty)
});
if (!isNullable)
schema.Required.Add(propertyInfo.Name);
}
return schema;
}
}
The object which I want to pass as JSON in a header, DmsRequestModel, is as below:
public class DmsRequestModel
{
public string? Filter { get; set; }
public string? Select { get; set; }
public string? Prefilter { get; set; }
public string? OrderBy { get; set; }
public bool? IncludeFiles { get; set; }
public bool? IncludeHashCodes { get; set; }
}
I have tried using both fthe built-in SchemaGenerator to generate the object, as well as the self-defined CreateDmsFilterScheme method in the OperationFilter. The resulting parameter from the SchemeGenerator appears to come closest to my needs so, to avoid confusion, the remainder of this question will be about this method.
In SwaggerUI, the resulting parameter looks as such:
On the surface, this appears to fit my needs exactly, as it looks like a valid JSON object.
However, when making an actual request, in the cURL output the entire object is seemingly supplied as a comma-separated string:
curl -X GET "http://localhost:5001/modules/dms/WorkOrders/all?api-version=1.0" -H "accept: text/plain" -H "basicAuthHeader: Basic <basic auth token>" -H "dmsFilter: prefilter,authorized,my_workshop,work_order_status,hide_work_order_status_cancelled,orderBy,planned_starting_date_time asc,includeFiles,false,includeHashCodes,true" -H "Authorization: Bearer <bearer token>"
I am not sure why this is happening, but I cannot seem to get the object into the header as a JSON string. I am not very experienced or familiar with HTTP technicalities, however I imagine supplying a JSON string in a header would not or should not be impossible. I have built APIs before but never with this level of complexity.
Am I doing this wrong, or is there some extra step somewhere which I am missing? I am not sure if this is the right way to go about this, but it felt like the most straight-forward and obvious way. Any suggestions or possible alternative ways of achieving this are welcome!
Please feel free to comment should anything be unclear.
Thank you in advance!
I don't think your current approach is the right one. As far as I'm aware, the header is not meant for changing what you return, but rather providing context to the request and the form it is returned in. E.g. JSON/XML and the way caching behaves. But no matter what you provide in the headers, the data itself should remain the same (that's the way I use headers, but if this is wrong please correct me).
The comma seperation is also quite logical: headers aren't exactly meant to have complex objects in them such as json.
A more logical approach would be to use the query string if you can. From a usage point this is also more logical as querystrings are generally used to change what a specific request returns. It also makes working with your API much easier. If you're having this much difficulty making what should be a simple get request, someone else likely will as well.
Alternatively, if the objects are really complex I would use a POST request instead, as this does allow you to send a much more expansive body.
On a sidenote: it is possible to send a body with a GET request, but especially in your usecase this would not be recommended as it could severely impact caching among other things. (referencee)

How to pass two filled class models via an Angular http.post 'body' to the controller?

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

Using Required and JsonRequired in ASP.NET Core Model Binding with JSON body

I'm using ASP.NET Core 2.0, and I have a request object annotated like this:
public class MyRequest
{
[Required]
public Guid Id { get; set; }
[Required]
public DateTime EndDateTimeUtc { get; set; }
[Required]
public DateTime StartDateTimeUtc { get; set; }
}
And in my controller:
public async Task<IActionResult> HandleRequest([FromBody] MyRequest request)
{ /* ... */ }
I noticed an issue with model binding: When I send a request containing the header Content-Type set to application/json and a an empty body, as I expect, the request in my controller is null and ModelState.IsValid is false.
But when I have a body like this:
{
"hello": "a-string-value!"
}
my request is NOT null, it has default values for everything, and ModelState.IsValid is true
This is happening of course while I'm missing all the Required properties, and the only existing one's name doesn't match a property there (even the type for this single parameter is string, which doesn't match any type on my model).
So in a way, those Required attributes seem to be working if there's nothing in my request, but they don't do anything if my request is not empty!
As I was preparing this question, I noticed that there's also a JsonRequired attribute, and it seems to take care of the properties being present.
So, what's the difference between Required and JsonRequired?
For correct work of Required attribute, you should make the properties nullable:
public class MyRequest
{
[Required]
public Guid? Id { get; set; }
[Required]
public DateTime? EndDateTimeUtc { get; set; }
[Required]
public DateTime? StartDateTimeUtc { get; set; }
}
Now if you send request with missing Id, EndDateTimeUtc or StartDateTimeUtc, corresponding field will be set to null, ModelState.IsValid will be set to false and ModelState will contain error(s) description, e.g. The EndDateTimeUtc field is required.
JsonRequired attribute is specific to JSON.Net. It plays during deserialization, while Required attribute (as other attributes from System.ComponentModel.DataAnnotations namespace) plays after model is deserialized, during model validation. If JsonRequired attribute is violated, the model will not be deserialized at all and corresponding action parameter will be set to null.
The main reason why you should prefer Required attribute over JsonRequired is that JsonRequired will not work for other content types (like XML). Required in its turn is universal since it's applied after the model is deserialized.
When you use [FromBody] as binding source, the Model properties will get default values and [BindRequired] will be ignored.
There is a related issue on "Problems with Parameter Validation".
In this case is better to use [JsonRequired] instead of [BindRequired] to enforce binding properties.
Note that [JsonRequired] affects serialization and deserialization both.
Yes, the difficulty is that if you choose Required, the semantics for the client of the web request change incorrectly - i.e. you are saying you can pass in a null, when you really need a proper value.
Using JsonRequired sorts this, but this is provided by NewtonSoft, so stops working when you upgrade to .Net Core 3. This is because .Net Core 3 uses its own Json parser instead of NewtonSoft.

pass array of an object to webapi

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&param2=bling&param3=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.

Problem getting the MVC3 model binder to bind to a decimal instead of an int

I have a AJAX call from my webpage sending the following data to my MVC3 action method.
{
"name":"Test",
"newitems":[
{"id":15,"amount":100,"unit":"gram"},
{"id":1,"amount":75,"unit":"gram"},
{"id":46,"amount":25,"unit":"gram"}
]
}
In my controller I have the following classes:
public class NewDataItem
{
public string name { get; set; }
public List<NewDataItemDetails> newitems { get; set; }
}
public class NewDataItemDetails
{
public int id { get; set; }
public int amount { get; set; }
public string unit { get; set; }
}
And the Action method that received the request have a NewDataItem as a parameter. This works perfect, however the amount property of NewDataItemDetails might not always contain an int. It might for example be 50.45. So because of that I changed the line public int amount { get; set; } to public decimal amount { get; set; }.
After this change amount is always shown as 0, and not the proper value that it did when it was an int.
Why does MVC fail to bind the value to the property when it is a decimal, when it worked just fine as an int?
See the answer here Default ASP.NET MVC 3 model binder doesn't bind decimal properties from ryudice about creating a DecimalModelBinder. It works and it also keeps all the model validation working, (some other solutions use deserialization which can stop validation from working.
I found the problem.
If I change the line "amount":100, to "amount":"100", it works fine. It seems that the MVC ModelBinder can manage the string to decimal conversion, but not the int to decimal.
Your type (class) NewDataItemDetails property amount is of type int.
You are making things difficult to manage. If you are expecting to have amount as a decimal eventually, it simplifies to actually use amount's type as decimal.
This will avoid having you to extend the default model binder and cater for your behaviour.
I think, it is wiser and it simplies to send the amount as decimal in the ajax call. it provides consistency.
I know I am a bit late with this but there is another solution which works without changing anything until MVC fixes it (I believe it is at their end).
In my javascript I add 0.00001 to my value if it is supposed to be a decimal but is a round number. For those I know should be currency values I know this has no effect and will round down. For those that I do not know their value and this could affect it I remove the 0.00001 from the value after binding in mvc. The binding works correctly as this is no longer an int in the eyes of mvc
If you don't want to change model binder then you can create json object and convert amount to string.It will work fine for me.
var test={"amount":""};
var test1={"amount":100}
test.amount=test1.amount.toString();

Categories