I'm experiencing a problem doing post requests via ASP.NET Core Web API. I'm a bit confused of the articles I read/found while Googling.
So here's my scenario:
I'm trying to do a HTTP Post request to an api endpoint with a complex parameter
Example:
[HttpPost]
[Route("add")]
public async Task
{
if (model != null)
{
var isStockAdded = await _inventorySvc.AddStockAsync(model);
if (isStockAdded)
return Ok();
else
return BadRequest();
}
return BadRequest();
}
}
My problem is that every time I make a post request to this endpoint the model value(s) are always null even if there's the [FromBody] parameter attribute or not.
Is this a bug in ASP.NET Core 2?
What does the Stock object look like? Also what are you sending to the endpoint?
Thanks for responding @jkotalik
```C#
Here's the structure of Stock class
public class Stock
{
[Key]
public Guid StockId { get; set; }
[Required]
[DataType(DataType.Text)]
public string StockName { get; set; }
[Required]
[DataType(DataType.Text)]
public Guid SKU { get; set; }
public string Description { get; set; }
[Required]
public int Qty { get; set; }
[Required]
public DateTime DateAdded { get; set; }
[Required]
public decimal UnitPrice { get; set; }
[Required]
public decimal MarkupPrice { get; set; }
[Required]
public decimal SellingPrice { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser User { get; set; }
[Required]
public Guid UserId { get; set; }
}
This is the data I'm sending to the endpoint using Postman:
{
"StockId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"StockName":"test item",
"SKU":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"Description":"test item",
"Qty":10,
"DateAdded":"XXXXXXXXXXX",
"UnitPrice":00,
"MarkupPrice":00,
"SellingPrice":00,
"UserId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
}
```
Most likely one of these properties failed to bind. @rynowak I don't know who is in charge of model binding, but you have any ideas?
@lorenz31 I have tried like this and it does work.
@lorenz31 Stock like this:
{
"stockId": "8fc759e0-0da1-478d-96b7-d939e4498d48",
"stockName": "string",
"sku": "8fc759e0-0da1-478d-96b7-d939e4498d48",
"description": "string",
"qty": 0,
"dateAdded": "2017-09-19T02:37:28.937Z",
"unitPrice": 0,
"markupPrice": 0,
"sellingPrice": 0,
"userId": "8fc759e0-0da1-478d-96b7-d939e4498d48"
}
Thanks for checking that @zzyykk123456.
@zzyykk123456 But how come the prices doesn't have the actual values being passed upon request?
@lorenz31 It works too.
@zzyykk123456 I'm using Postman to test it but it doesn't pass the values even with or without the [FromBody] parameter attribute.
@lorenz31 With or without [FromBody] is ok.The postman setup is here.
@zzyykk123456 Hmm in VS 2017 I really can't get the values being passed to the endpoint upon the request. But as what you showed in your samples, this makes me confused.
@zzyykk123456 I created the asp.net core web api project via VS 2017 Community edition version 15.3.4.
@zzyykk123456 This is just a class derived from DbContext.
@zzyykk123456 Sorry, I was mistaken. Its just a class with 3 properties
public class ApplicationUser
{
[Key]
public Guid Id { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
public virtual ICollection<Stock> Stocks { get; set; }
}
NOTE: I'm not using Asp.Net Identity here.
@zzyykk123456 Hmm so what do you think is the problem? How come that it can't receive the values being passed from postman to the api controller when making the POST request?
@zzyykk123456 I have the same setup as what you have.
@zzyykk123456 Here's my setup
On Postman
On Visual Studio 2017 Community Edition
@lorenz31 could you try camelCase for your property names in postman
@kirst It still doesn't work.
@lorenz31 Your StockId and SKU guid are invalid.
You can get new guid in https://www.guidgenerator.com/online-guid-generator.aspx. Try it.
@zzyykk123456 That was just a sample. I'm actually generating new stockid and sku via the _inventorySvc.AddStockAsync() method. I just put it there to fill the field.
Can you tell us what ModelState.IsValid returns? If it returns false I would evaluate ModelState to see why.
I also have a feeling that the signature with string sellerid has something to do with your issue. Where is that being populated from? If I have a signature like that I typically pass that via query string param.
You can change your method to look something like this:
```
[HttpPost]
[Route("add/{sellerId}")]
public async Task PostStock([FromBody]Stock model, string sellerId)
{
if (model != null)
{
var isStockAdded = await _inventorySvc.AddStockAsync(model);
if (isStockAdded)
return Ok();
else
return BadRequest();
}
return BadRequest();
}
}
Then post to: http://localhost:49828/api/TokenAuth/add?sellerId=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
@jayslife The prefix route is "api/Inventory/{sellerid}" and the route for PostStock() is "add" so there will be "api/Inventory/{sellerid}/add".
@lorenz31 Did you solve your issue? I seem to be experiencing the same thing.
@duanemck Fortunately yes, I already solved my issue :). It turns out that I shouldn't be passing the StockId and SKU fields along with the request because those fields are filled in my service class (generating new Guid). That's my case, it works now.
Could you post a code snippet of your web api and sample request? Hopefully I might help you.
Luckily I just found my issue too. I had some custom middleware for request logging that was reading the stream and not resetting it, so the modelbinder had nothing to bind to. Spent all day chasing such a silly thing. Glad yours was sorted too, thanks for the offer to help.
Not much guesswork in these issues really, look at the ModelState object and see what binding errors occurred. That usually tells you 99% of the time.
Not much guesswork in these issues really, look at the ModelState object and see what binding errors occurred. That usually tells you 99% of the time.
@akakira Can't believe I didn't think to check ModelState
. I wasted hours trying to figure out a similar issue, and ModelState
helped point it out. Thanks for the tip!
No problem, cheers to you mate.
Hello @lorenz31 I am facing similar problem . can u help me with this?
Here is my api for uploading an image:
public async Task
Upload(int vId,IFormFile file)
{
var vehicle = await this.repository.GetVehicle(vId, hasAdditional: false);
if (vehicle == null)
return NotFound();
var uploadsFolderPath = Path.Combine(host.WebRootPath, "uploads");
if (!Directory.Exists(uploadsFolderPath))
Directory.CreateDirectory(uploadsFolderPath);
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName);
var filePath = Path.Combine(uploadsFolderPath, fileName);
error shows on this line:
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName);
that indicates its not getting the file while I am sending a image file with the same key "file".
Everything else works fine.
@trktuhin I think you shouldn't use Path.GetExtension() since that returns only the file extension not the full name of the file with its extension.
Hey,
I have the same issue but I figure out that my model contains a DateTime type value and it is not getting set.Can anybody tell me how to solve this issue ?
I have a int field being completely ignored by modelbinder. It just don't show up on ModelState at all, and it is in the request i can even access it with Request.Form["field"] it's there, modelbinder just chose to ignore it. When i try to bind it manually i get something weird like "The runtime refused to evaluate the expression at this time." but if i set the value itself, it works, only not if i am setting it from the Form.
It has a weird Microsoft.Extensions.Primitives.StringValues type on it, i did a GetType and it gave me this dude. But it's just a simple NUMBER. It should be int. :-(
Ok so i figured out my issue.
For some reason the ModelBinder don't like to have an Object property in the model.
public object Id { get; set; }
This gets absolutely and utterly ignored by model binder :-( weird AF.
I also figured out what my issue was. I tried all the solutions above and realised after a lot of testing what the issue was.
As silly as it sounds, in one of my string fields in the model I was passing in a local file path:
{
"path": "c:\"
}
The '\' was escaping the second quotation mark causing invalid json to be passed through.
You can include '\' in your fields by using a double '\\'
{
"path": "c:\\"
}
I'm getting the same thing. My controller doesn't bind the model unless it has [FromBody]
infront of it. In the variant that doesn't work I just get nulls and default values.
Doesn't work:
[HttpPost]
public IActionResult Post(Product product)
}
// ... code ...
}
Works:
[HttpPost]
public IActionResult Post([FromBody]Product product)
}
// ... code ...
}
Edit: this article seems to suggest that [FromBody]
is required whenever we are posting a JSON model to our methods?
Can't seem to fix the same issue on my end. My model only receives null / default values whenever I do HttpPost. Both in postman and Angular7 (typescript). Tried adding [FromBody]
and Content-Type: application/json
when doing requests, still no luck.
Can't seem to fix the same issue on my end. My model only receives null / default values whenever I do HttpPost. Both in postman and Angular7 (typescript). Tried adding
[FromBody]
andContent-Type: application/json
when doing requests, still no luck.
Resolved mine. Removing [JsonArray]
was my solution. But still with [FromBody]
and Content-Type: application/json
as header.
I'm getting the same thing. My controller doesn't bind the model unless it has
[FromBody]
infront of it. In the variant that doesn't work I just get nulls and default values.Doesn't work:
[HttpPost] public IActionResult Post(Product product) } // ... code ... }
Works:
[HttpPost] public IActionResult Post([FromBody]Product product) } // ... code ... }
Edit: this article seems to suggest that
[FromBody]
is required whenever we are posting a JSON model to our methods?
Thanks, Its Work!
When sending data to an endpoint, there is a difference between setting the Id to null and not setting an Id attribut at all.
In my case, when i send data like this, an Image object will be created with those data, and will be saved in the database correctly with a new Id:
{
"Name": "00001.jpg",
"Title": "Image 1 title",
"Description": "Image 1 description",
"Created": "03/04/2019",
"Modified": "03/04/2019"
}
but when i send data like this:
{
"Id": null
"Name": "00001.jpg",
"Title": "Image 1 title",
"Description": "Image 1 description",
"Created": "03/04/2019",
"Modified": "03/04/2019"
}
This will instantiate a new object and save it in database but without the data i sended to the api.
Hello, I used to have the same problem in my code, however I realized that the problem was very simple to solve, yes after I almost drive myself crazy!
It is necessary to make sure that the controller is decorated with [ApiController], other wise, the object you send from postman over http, will not arrive to the method.
```C#
[Route("api/[controller]")]
[ApiController] //<- Make sure it is there
public class ApplicantDocumentController : Controller
{
.
.
.
[HttpPost]
public async Task<ActionResult<ApplicantDocument>> post(ApplicantDocument AppDoc)
{
.
.
.
}
}
```
This is a request without the decorator
This is a request with the decorator
@ancor1369 Possibly due to the ApiController
applying binding source parameter inference....
The same bug that @ancor1369 mentioned.
Once I decorated with the attribute "api/[controller]" it performs.
I almost get crazy!
For those who ran into what @duanemck pointed out, where some middleware is reading the body and then later on in the pipeline your model binder gets null
, this middleware at the top of my pipeline resolved the issue for me:
public class EnableRewindableBodyStartup {
private readonly RequestDelegate _next;
public EnableRewindableBodyStartup(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext httpContext) => await RewindBodyStreamAndNext(httpContext, _next);
private async Task RewindBodyStreamAndNext(HttpContext context, RequestDelegate next) {
context.Request.EnableRewind(); // extension method in `Microsoft.AspNetCore.Http.Internal`
await next(context);
}
}
in startup call it like:
```
app.UseMiddleware
...add other middleware (audit log, global error, static files, mvc, etc)
````
services
.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver
= new CamelCasePropertyNamesContractResolver();
});
This resolve for me
Most helpful comment
Not much guesswork in these issues really, look at the ModelState object and see what binding errors occurred. That usually tells you 99% of the time.