Error 415 when trying to send object + file as multipart request - c#

I know that sending multipart requests together with JSON object already had many threads but unfortunately none of them actually resolved the problem for me.
I created controller accepting object containing IFormFile together with some metadata object:
public class DocumentMultipartRequest
{
[FromForm]
public DocumentMetadata Metadata { get; set; }
[FromForm]
public IFormFile File { get; set; }
}
The controller looks like that:
[Consumes("multipart/form-data")]
public async Task<Guid> AddDocument(DocumentMultipartRequest request)
{
}
Apparently Swagger understands my intention correctly because I have string($binary) allowing me to choose a file and object with proper fields inside to fill, unfortunately when I set everything and press "Execute" I will receive "415 Unsupported Media Type" error as result.
How can I fix this problem while maintaining Swagger still understanding my intention, so API is nicely documented?
Setting multiple [Consumes] types didn't help, marking whole DocumentMultipartRequest as [FromForm] will divide DocumentMetadata into different fields f.e. DocumentMetadata.Property1, DocumentMetadata.Property2. The latter will actually work fine and will be properly bound to DocumentMetadata object on backend side, but if possible I would rather accept normal JSON because metadata object may get complicated later on.

Related

Using route and body parameters simultaneously on a post request Asp.Net

Preface
I recently was assigned a project for college, were using ASP.NET Core 6 Web API, and Entity Framework. Due to the subject at hand the objective is to implement an API without taking any consideration around the structure and routing of it.
So this is a warning: I did not choose the routing nor can I change it, even if slightly.
The actual problem
I have 3 model classes, let's call them Application, Module and Data. A single application has various child modules, while each module has a series of data children.
The issue is that when I want to create a Module or a Data model, the teachers decided to give it a twist and we're forced to take data from the body and the URL.
So that everyone is on the same page ill give an example of how to create a Module.
The url goes like this
POST: https://ip:port/api/OurProjectName/{application}/
POSTBODY:
<?xml version="1.0"?>
<Module><!--here on the root node i can be a bit malleable and name it whatever i want so no issues with DTO's-->
<id>99999</id>
<name>some name</name>
<creation_dt>{the current timestamp}</creation_dt>
</Module>
If the implementation was correct what it would do is go to the controller grab the application string and the Module/ModuleDTO, fetch an Application model from the database where the application string is equal to the field name.
Then inside that function due to our choice of using EF, we would just grab the module sent in the body(or create one in case of a DTO) and associate the respective parent sent in the route parameters.
My failed attempts
Although I talked about modules, the behaviour of data is nearly the same as modules except the teachers decided that for some reason with data you can only create and delete, and you can't list, update, etc... When it comes to posting instead of
POST: https://ip:port/api/OurProjectName/{application}/
I have to do something like
POST: https://ip:port/api/OurProjectName/{application}/{module}
while application this time only serves for checking past errors due to the way the database was structured.
I firstly attempted to do something like
[HttpPost("{application}/{module}")]
[Produces("application/xml")]
[Consumes("application/xml")]public IActionResult Post(string application, string module, [FromBody] Data data)
{
var mod = _context.Modules.SingleOrDefault(m => m.name == module);
if (mod == null)
{
return NotFound();
}
if (mod.parent.name != application)
{
//... this is irrelevant for now
}
data.parent = mod;
// TODO: dereference here... fix later
mod.datas.Add(data);
_context.Add(data);
_context.SaveChanges();
return Ok(data);
}
But it returned the usual error of 415, although this try was mostly hoping that [FromBody] wouldn't complain at the routing as I've had good results with put (although in that situation it kinda follows convention instead of completely breaking it) and to top it off since Data has a navigation field, swagger will iterate the objects until no parents are found so the xml object in the body looked... well like a configuration file.
I then tried to use decorations around the two strings [FromRoute], but it still wouldn't budge, and got an "unsupported media format".
Then I tried to do some custom manual routing (as in no decorations)
here's my startup file that I created with only that purpose in mind (since I otherwise would expect the Program.cs file to get cluttered which is a thing I hate)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();
}
var option = new RewriteOptions();
option.AddRedirect("^$", "swagger");
app.UseRewriter(option);
app.UseCors("AllowAll");
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "postDataCustom",
pattern: "api/OurProjectName/{application}/{module}", new {controller = "DataController", action = "Post"});
});
}
I removed the [HttpPost(etc)] and expected to hopefully fix it, but when I checked swagger not only was it missing but the endpoint didn't even exist (I tested with Postman).
I commented it out (the part where I manually set a ControllerRoute) and found some other posts around the subject.
And I saw that I could actually do some kind of dto and put the decorations in each field, this was game breaking as I knew then I would be able to just place a single variable inside the controller function and avoid [FromData] to go insane and parse properly the stuff.
public IActionResult Post(DataDTO data)
{
var mod = _context.Modules.SingleOrDefault(m => m.name == data.module);
if (mod == null)
{
return NotFound();
}
Console.WriteLine(mod.name);
// ...(if it wrote to console I could progress)
return Ok();
}
public class DataDTO
{
[FromRoute]
public string application { get; set; }
[FromRoute]
public string module { get; set; }
[FromBody]
public Data data { get; set; }
public DataDTO(string application, string module, Data data)
{
this.application = application;
this.module = module;
this.data = data;
}
}
It still gave me the error code, this time since I was a bit more skeptical of such a failure, I made a much simpler DTO and routing just to check if I could at least get a 200 code and I did it, the problem is [FromRoute] is pretty much "ignored"
[HttpPost("{module}")]
public IActionResult Post(testeDTO data)
{
Console.WriteLine(data.module);
Console.WriteLine(data.bb);
return Ok();
}
public class testeDTO
{
[FromRoute]
public string? module { get; set; }
[FromBody]
public string bb { get; set; }
public testeDTO(string bb)
{
this.bb = bb;
}
}
Although the route is there and works fine it never assigns the value to module, it seems like whatever is on the body overwrites it (I tried to get around not having duplicate fields by adding the possibility of module being null so I wouldn't be forced to send a module in the body, but no dice, I can't decouple both behaviors).
At this point I'm kinda out of ideas, it's even more frustrating when old documentation gets in the way with search results.
I've also considered using x-www-urlencode, although I'm not sure if it's an option I can take as again the requirements are very restrictive.
TL;DR I can't get a post with parameters in the route to hit successfully the action Post...
Update as of 26/12/2022:
I was playing around the code and weirdly this version "works" i tested it with swagger and it seems to not produce any error, i even checked git to see if added or removed some kind of config or something of the kind but since the last commit(which was uncharacteristic of me as it was a "backup" of sorts) these were the only things that changed
[HttpPost("{module}")]
public IActionResult Post([FromRoute]string module, [FromBody]testeDTO data){
Console.WriteLine(module);
Console.WriteLine(data.bb);
return Ok();
}
...
public class testeDTO{
[FromBody]
public string bb{get;set;}
public testeDTO(string bb){
this.bb = bb;
}
}
I will try to now play around the code to see if i can get to the solution
Step 1)
Go back to your first attempt:
[HttpPost("{application}/{module}")]
[Produces("application/xml")]
[Consumes("application/xml")]
public IActionResult Post(string application, string module, [FromBody] Data data)
{
// Your code
}
Step 2)
Add this line to your Program.cs:
builder.Services.AddControllers().AddXmlSerializerFormatters();
or this line to your ConfigureServices method:
services.AddControllers().AddXmlSerializerFormatters();
Step 3)
Rework your XML to match the parameter name:
<?xml version="1.0"?>
<Data>
<id>99999</id>
<name>some name</name>
<creation_dt>{the current timestamp}</creation_dt>
</Data>
Things should then work.
Step 2) is key, listen to what your being told - 415 is unsupported media type (which you know). So support the media type. I don't know why you think it shouldn't be possible to use a route parameter and a body parameter in the same endpoint but it is.
You may also not want to mix attribute routing and configuration level routing.
I found the answer, apparently the issue revolved around
[FromBody]
//... and
[Consumes("application/xml")]
As the edit on my question suggested i removed the consumes decoration and it started working unfortunately as suggested by the question i cant either change the routing nor can i change the media type it produces and consumes, so my idea was at the time "if i cant use body decorations ill just manually parse the body and keep the routing decorations".
And thats when it hit me XMLserializer requires at least one parameterless constructor to work, and since i had placed parameterless constructors on all other models except on this one, it tricked me into thinking the issue was around the decorators. Ill leave some resources that helped me get into the solution
How to parse xml into objects
Request params documentation

ASP.NET requires media type even if there is no request body

I have the HTTP endpoint with the optional body (OrderRequest):
[HttpPost]
public async Task<ActionResult> Index(OrderRequest? request = null)
{
//...
}
If sending a request in which Content-Type header is not specified, then 415 (Unsupported Media Type) error will appear.
What can I do to allow requests without a body and media type?
Of course, to solve it I could just add the appropriate media type on the client side. But the problem is that the previous version of this endpoint didn't require any request body and therefore media type was not required as well. So in order to ensure backward compatibility, I need to configure this endpoint to be able to accept requests as earlier (without a request body and media type).
By default, the framework doesn't allow POST requests to have an empty body. You can change this behavior in two ways:
Globally. In this scenario, in your Program.cs, change .AddControllers() to .AddControllers(opt => opt.AllowEmptyInputInBodyModelBinding = true).
This endpoint only. In this case, change the method declaration to Index([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] OrderRequest? request = null).
However, this will not fix the issue where the framework simply returns 415 Unsupported Media Type.
I dig a bit into this and apparently one of the ways to change this behavior is by creating a filter, like it is said here.
There is a GitHub issue on the aspnetcore repository where they discussed this situation. There is also a open pull request to support this behavior.
To fix the issue, two action methods should be created. The first one is intended for requests without body, and the second one is the opposite.
After that, add Consumes attribute to the second method. This attribute filters requests for specified media types. As a result, all other requests with different media types or without ones will go to another action method.
[HttpPost]
public async Task<ActionResult> Index() => Index(null);
[HttpPost]
[Consumes("application/json")]
public async Task<ActionResult> Index(OrderRequest? request)
{
//...
}

POST of IFormFile not making it to action

I've got a .NET Core application that has a controller named Documents with a POST signature like this:
[HttpPost]
public async Task<IActionResult> PostAsync(CreateDocumentRequest createDocumentRequest)
The CreateDocumentRequest looks like this:
public class CreateDocumentRequest
{
public string Name { get; set; }
public string Description { get; set; }
public IFormFile File { get; set; }
}
Pretty simple. I then have a POST request configured in Postman like this:
URL: http://localhost:9090/api/documents
Body: configured as form-data and I have Name, Description and File all configured in the key-value pair interface. Furthermore, File is set as a file type so it allowed me to browse for a file.
When executing this POST the DocumentsController executes the constructor and Application Insights indicates that PostAsync was matched:
Activated Event Time Duration Thread
Application Insights: Trace "Route matched with {action = "PostAsync", controller = "Documents"}. Executing action TdlLims.MediaService.Controller.DocumentsController.PostAsync (TdlLims.MediaService)"
However, it never enters the action. My gut tells me that model binding is failing. This is for two reasons. One, all other pieces of the routing work according the Application Insights. Two, if I remove the parameters entirely, it does enter the action. What I've tried:
Added [FromForm] to the createDocumentRequest
Accepted only an IFormFile into the action, dropping the complex object
Split up the CreateDocumentRequest into three different parameters
And some other things along the way with less signifigance
Now, I'm suspect that when we're setting up Mvc, we may be missing something. We are configuring a few things, but I feel like we're missing a formatter for multipart/form-data somehow. I feel that way because we're using AddMvcCore instead of AddMvc:
.AddAuthorization()
.AddJsonFormatters()
.AddApiExplorer()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new OptionConverter());
options.SerializerSettings.Converters.Add(new StringEnumConverter());
});
Finally, I can confirm the controller is working in general, because I have a GET that is accessible and it makes it to the action:
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
In the end, the issue was the size of the file. It would be nice if .NET Core threw an error rather than returning a 200 when something like that happened. I was trying to upload some images, and I'm going to need to figure out the right way to increase the file size, but when I uploaded a small text file the POST worked and the file was deserialized properly into the IFormFile.
I believe the attributes RequestFormLimits and RequestSizeLimit are going to play a role in setting that max file size in the end.

Restful Web Service using C#

I have been assigned to come up with a web service that receives and posts data. However, I am very new to this, and even after looking up multiple examples and trying to follow them, I have a bit of a difficult time understanding.
Referenced examples:
Link 1
Link 2
The code that I have been given as a reference for the model and controller are the following:
Model
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Webservice.Models.ApiModels {
public class SecondlyReading {
[Key]
public int Id { get; set; }
[Required]
public int Name { get; set; }
[Required]
public string TimeStamp { get; set; }
[Required]
public string Date { get; set; }
[Required]
}
}
Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;
using SmartDBWeb.Data;
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SmartDBWeb.Models.ApiModels;
using Microsoft.EntityFrameworkCore;
namespace Webservice.Controllers.Api {
[Route("api/[controller]")]
[Authorize]
public class WebserviceController : Controller {
private ApplicationDbContext _context;
public WebserviceController(ApplicationDbContext context) {
_context = context;
}
// GET: api/Webservice
[HttpGet]
public IEnumerable<Webservice> GetSecondlyReadings() {
return _context.Webservice.ToList();
}
// GET api/Webservice/id
[HttpGet("{id}")]
public async Task<IActionResult> GetWebservice(int id) {
var reading = await _context.Webservice.SingleOrDefaultAsync(c => c.Id == id);
if (reading == null) {
return NotFound();
}
return Ok(reading);
}
[HttpPost]
public IActionResult PostWebservice([FromBody]List<Webservice> Readings) {
if (!ModelState.IsValid) {
return BadRequest();
}
foreach (Webservice reading in Readings) {
_context.Webservice.Add(reading);
}
_context.SaveChanges();
return CreatedAtAction("GetWebservice", new { id = Readings[0].Id }, Readings[0]);
}
}
}
My main question is how the use of the above code works in general. What I have found out(might not be correct), is that the the model is the data itself and the controller links the model and view together.
Model: It is basically a table structure for the database. So, whenever you will create an object and set the values and insert the object in the database.
Controller: It is used to deal with the HTTP calls and to link your business logic with the View.
[HttpGet]
This maps to a GET request to the URL api/Webservice without any query parameter. The actions return type is a List, which means that multiple objects should be returned. In your case, when the client accesses api/Webservice, all objects within your _context.Webservice are returned.
[HttpGet("{id}")]
This maps to a GET request as well, but this time it requires a so called Query Parameter. This is an additional piece of information that your client provides to make its request more specific. E.g., they could be requesting api/Webservice?id=1 which would ask you to return the object with an id of 1.
[HttpPost]
This maps to a POST request and asks you to insert or update an object. [FromBody] tells the request processor to convert the so-called Request Body into an object of a given type. The request body is where your client will put entire objects - converted e.g. in JSON format - they want to submit to the server.
So, for the model
public class SecondlyReading {
[Key]
public int Id { get; set; }
[Required]
public int Name { get; set; }
[Required]
public string TimeStamp { get; set; }
[Required]
public string Date { get; set; }
}
This will create a datatable with Id as the primary key because you are using [Key] attribute.
private ApplicationDbContext _context;
This is used to create a database context. You might have also created the following in ApplicationDbContext class
public DbSet<SecondlyReading> WebService{get; set;}
It will create a DbSet with name WebService.
In WEB APIs, POST is used to insert new data. So, here the API
api/webservice
in POST, will be used to insert the data. You can insert the data using any CLIENT like POSTMAN or ARC. You have to set the data in body of request of the HTTP call.
The response of the API can be JSON or XML depending on your output.
I think it's better to read the basics?So that you will understand how normally Web API works
For self reading
https://www.asp.net/web-api
Start with asking yourself how Web Communication generally works. When accessing any website via your browser by typing in an address, what actually happens behind the scenes?
What I found to be very helpful was to open up a new tab in my browser and use Developer Tools (e.g. by right clicking anywhere and then clicking on "Inspect") to observe traffic by switching to the network tab. Access a website of your choice, say: wikipedia.org.
Now a bunch of stuff is going on, but you are interested in the first new entry to your network communication list that should say "www.wikipedia.org". Click on that.
You should now be looking at the "Headers" tab, specifically the request headers. There are two important fields:
Request URL : This tells the server what you want from it. It is a Resource locator, meaning that you want to access a resource from the server, say, a piece of HTML, an image or raw JSON data that you use in your application.
Request Method : This tells the server what you want to do with the resource you are trying to access. Do you want to GET it? Or do you want to PUT some resource on the server? Maybe you want to DELETE it or POST changes to this resource.
Let's go back to your source code.
What you provided above are a Model class and a Controller class.
Your model is a data structure that represents a resource within your web application. Id, Name, Timestamp, Date are attributes of this resource. Depending on your actual use case, you want to create, use, update or delete objects of this model type and decide on their attribute's values.
To allow your clients to do so, you have a controller class. It is the entry point for all web requests that "map" to :
[Route("api/[controller]")]
Map means, when the request URL of your client (remember our example "www.wikipedia.org") matches to the string you defined in your Route, this controller class is used (notice: [controller] will be replaced with the actual name of your controller class, in this case "Webservice".
Within your controller you define Actions. Depending on the Request URL and the Request Method (see above) of your client's request, your web framework decides, which action is called.
[HttpGet]
This maps to a GET request to the URL api/Webservice. The action's return type is a List, which means that multiple objects should be returned. In your case, when the client accesses api/Webservice, all objects within your _context.Webservice are returned.
[HttpGet("{id}")]
This maps to a GET request as well, but this time it requires a so called Query Parameter. This is an additional piece of information that your client provides to make its request more specific. E.g., they could be requesting api/Webservice?id=1 which would ask you to return the object with an id of 1.
[HttpPost]
This maps to a POST request and asks you to insert or update an object. [FromBody] tells the request processor to convert the so-called Request Body into an object of a given type. The request body is where your client will put entire objects - converted e.g. in JSON format - they want to submit to the server.
Now, I hope this makes your code examples a bit clearer to you. You also mentioned the View, so I will quickly explain what that is: Typically, after a request to your server, you respond with some sort of answer. In the most simple case, it is the Response Status that tells the client if everything went smooth. For a GET request, you typically return an object in the Response Body. What you return is called the view. In your example:
return Ok(reading);
converts the object that was retrieved from the database into a machine-readable format (e.g. JSON) and adds the response status "200 OK" to it. This is sent to your client.
So this should give you a good overview on how web frameworks work. I hope I could help you with this rather long read. Let me know if I can clarify anything.

FromBody string parameter is giving null

This is probably something very basic, but I am having trouble figuring out where I am going wrong.
I am trying to grab a string from the body of a POST, but "jsonString" only shows as null. I also want to avoid using a model, but maybe this isn't possible. The piece of code that I am hitting with PostMan is this chunk:
[Route("Edit/Test")]
[HttpPost]
public void Test(int id, [FromBody] string jsonString)
{
...
}
Maybe it is something I am doing incorrectly with postman, but I have been trying to use "=test" (as seen in other questions asked about this topic) in the value section of the body - x-www-form-urlencoded section with the key as jsonString and nothing. I have also tried using raw - text and raw - text/plain. I get the id so I know the url is correct. Any help with this would be greatly appreciated.
PostMan is set up like this currently:
POST http://localhost:8000/Edit/Test?id=111
key = id value = 111
Body - x-www-form-urlencoded
key = jsonString value = "=test"
By declaring the jsonString parameter with [FromBody] you tell ASP.NET Core to use the input formatter to bind the provided JSON (or XML) to a model. So your test should work, if you provide a simple model class
public class MyModel
{
public string Key {get; set;}
}
[Route("Edit/Test")]
[HttpPost]
public void Test(int id, [FromBody] MyModel model)
{
... model.Key....
}
and a sent JSON like
{
key: "value"
}
Of course you can skip the model binding and retrieve the provided data directly by accessing HttpContext.Request in the controller. The HttpContext.Request.Body property gives you the content stream or you can access the form data via HttpContext.Request.Forms.
I personally prefer the model binding because of the type safety.
Referencing Parameter Binding in ASP.NET Web API
Using [FromBody]
To force Web API to read a simple type from the request body, add the
[FromBody] attribute to the parameter:
[Route("Edit/Test")]
[HttpPost]
public IHttpActionResult Test(int id, [FromBody] string jsonString) { ... }
In this example, Web API will use a media-type formatter to read the
value of jsonString from the request body. Here is an example client
request.
POST http://localhost:8000/Edit/Test?id=111 HTTP/1.1
User-Agent: Fiddler
Host: localhost:8000
Content-Type: application/json
Content-Length: 6
"test"
When a parameter has [FromBody], Web API uses the Content-Type header
to select a formatter. In this example, the content type is
"application/json" and the request body is a raw JSON string (not a
JSON object).
In the above example no model is needed if the data is provided in the correct format in the body.
For URL encoded a request would look like this
POST http://localhost:8000/Edit/Test?id=111 HTTP/1.1
User-Agent: Fiddler
Host: localhost:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
=test
When having [FromBody]attribute, the string sent should not be a raw string, but rather a JSON string as it includes the wrapping quotes:
"test"
Based on https://weblog.west-wind.com/posts/2017/Sep/14/Accepting-Raw-Request-Body-Content-in-ASPNET-Core-API-Controllers
Similar answer string value is Empty when using FromBody in asp.net web api
 
In my case I forgot to use
JSON.stringify(bodyStuff).
I know this answer is kinda old and there are some very good answers who already solve the problem. In order to expand the issue I'd like to mention one more thing that has driven me crazy for the last 4 or 5 hours.
It is VERY VERY VERY important that your properties in your model class have the set attribute enabled.
This WILL NOT work (parameter still null):
/* Action code */
[HttpPost]
public Weird NOURLAuthenticate([FromBody] Weird form) {
return form;
}
/* Model class code */
public class Weird {
public string UserId {get;}
public string UserPwd {get;}
}
This WILL work:
/* Action code */
[HttpPost]
public Weird NOURLAuthenticate([FromBody] Weird form) {
return form;
}
/* Model class code */
public class Weird {
public string UserId {get; set;}
public string UserPwd {get; set;}
}
You are on the right track.
On your header set
Content-Type: application/x-www-form-urlencoded
The body of the POST request should be =test and nothing else. For unknown/variable strings you have to URL encode the value so that way you do not accidentally escape with an input character.
See also POST string to ASP.NET Web Api application - returns null
Post the string with raw JSON, and do not forget the double quotation marks!
Finally got it working after 1 hour struggle.
This will remove null issue, also gets the JSON key1's value of value1, in a generic way (no model binding), .
For a new WebApi 2 application example:
Postman (looks exactly, like below):
POST http://localhost:61402/api/values [Send]
Body
(*) raw JSON (application/json) v
"{ \"key1\": \"value1\" }"
The port 61402 or url /api/values above, may be different for you.
ValuesController.cs
using Newtonsoft.Json;
// ..
// POST api/values
[HttpPost]
public object Post([FromBody]string jsonString)
{
// add reference to Newtonsoft.Json
// using Newtonsoft.Json;
// jsonString to myJsonObj
var myJsonObj = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(jsonString);
// value1 is myJsonObj[key1]
var valueOfkey1 = myJsonObj["key1"];
return myJsonObj;
}
All good for now, not sure if model binding to a class is required if I have sub keys, or, may be DeserializeObject on sub key will work.
If you don't want/need to be tied to a concrete class, you can pass JSON directly to a WebAPI controller. The controller is able to accept the JSON by using the ExpandoObject type. Here is the method example:
public void Post([FromBody]ExpandoObject json)
{
var keyValuePairs = ((System.Collections.Generic.IDictionary<string, object>)json);
}
Set the Content-Type header to application/json and send the JSON as the body. The keyValuePairs object will contain the JSON key/value pairs.
Or you can have the method accept the incoming JSON as a JObject type (from Newtonsoft JSON library), and by setting it to a dynamic type, you can access the properties by dot notation.
public void Post([FromBody]JObject _json)
{
dynamic json = _json;
}
For .net core 3.1 post(url, JSON.stringify(yourVariable)) worked like charm
at the controller MyMethod([FromBody] string yourVariable)
The whole day has gone for me to resolve similar issue.
You must know that built-in serializor and Newtonsoft work differently.
Im my case built-in cannot parse JSON number to System.String.
But I had no obvious exception or details, just data came as null.
I discovered it only when I logged ModelState like that:
logger.LogInformation($"ModelState = {ModelState.IsValid}");
string messages = string.Join("; ", ModelState.Values
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage));
logger.LogInformation($"ModelMessages = {messages}");
And then I saw specific exception in logs:
The JSON value could not be converted to System.String
As a fix I did:
Install Microsoft.AspNetCore.Mvc.NewtonsoftJson which is preview
version.
Change to services.AddControllers().AddNewtonsoftJson();
Solution taken from https://stackoverflow.com/a/57652537/4871693
After a long nightmare of fiddling with Google and trying out the wrong code in Stack Overflow I discovered changing ([FromBody] string model) to ([FromBody] object model) does wonders please not i am using .NET 4.0 yes yes i know it s old but ...
Try the below code:
[Route("/test")]
[HttpPost]
public async Task Test()
{
using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
{
var textFromBody = await reader.ReadToEndAsync();
}
}
I just ran into this and was frustrating. My setup: The header was set to Content-Type: application/JSON and was passing the info from the body with JSON format, and was reading [FromBody] on the controller.
Everything was set up fine and I expect it to work, but the problem was with the JSON sent over. Since it was a complex structure, one of my classes which was defined 'Abstract' was not getting initialized and hence the values weren't assigned to the model properly. I removed the abstract keyword and it just worked..!!!
One tip, the way I could figure this out was to send data in parts to my controller and check when it becomes null... since it was a complex model I was appending one model at a time to my request params. Hope it helps someone who runs into this stupid issue.
Also, if you're using a Postman "environment," make sure the environment is selected before you run the API script that uses it. Otherwise, it will just send the variable strings -- {{varname}} -- instead of their associated values, which the API appropriately rejects.
This might also come in handy.
I needed to pass a JSON string to my API controller. But the model was unknown upfront. Using JObject as an object type works perfectly.
You can serialize later on to get a string.
[FromBody] JObject unknownStringJsonObject
You can just use "Object" instead of string like this:
public async Task<IActionResult> Method([FromBody] Object plainJson)
Then to print the object:
Console.WriteLine(plainJson.ToString());
And that's it!

Categories