Getting hold of raw POST data when using [FromBody] - asp.net-core

I have a controller running on ASP.NET Core 1.0 RC2 and I'd like to dump the raw POST data out to telemetry as ApplicationInsights doesn't do this for you. My code looks like this
[HttpPost]
[Produces("application/json")]
public IActionResult Post([FromBody] RequestClass RequestData)
{
var stream = this.HttpContext.Request.Body;
stream.Position = 0;
using (var reader = new StreamReader(stream))
{
string body = reader.ReadToEnd();
Telemetry.TrackTrace(body, Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Information);
}
return Ok();
}
But the string "body" always comes up empty. If I remove the [FromBody] decoration from the function signature, then this code works, but the RequestData object only contains null, which isn't what I want.
The only thing I can think of is converting RequestData back to a Json string, but this feels clunky and slow.
(EDIT: The POST data is Json)

You need to enable buffering the request body:
services.Configure<FormOptions>(options => options.BufferBody = true);
https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.AspNetCore.Http/Features/FormOptions.cs#L20

The most simple way I found to solve this issue is to use jObject as the Model.
And send the request with Content-Type: application/json at the header.
Use something like NewtonSoft's json dll this:
[HttpPost]
public IActionResult Post([FromBody] jObject RequestData)
{
string str = RequestData["key"];
return Ok();
}
This worked for me

The request stream has already been read, so what you can do here is EnableRewind on the Request
See solution here for reading json from body. Same should apply to your issue.

Related

415 Unsupported Media Type in ASP.NET core web api

I am trying to experiment with asp.net core web api so I made some simple api with a controller like this:
[ApiController]
[Route("MyController")]
public class MyController : ControllerBase
{
[HttpGet]
[Route("GetResult")]
public IActionResult GetResult(string param1, string param2= null, SomeClassObj obj = null)
{ .... }
}
I ran the api locally and sent this postman GET request:
https://localhost:5001/MyController/GetResult?param1=someString
I got the error: 415 Unsupported Media Type
What am I missing here so it could work?
I was getting the same error after invoking the WEB API from .NET MVC.
As suggested by #zhulien, I have changed from [FromBody] to [FromForm] in WebAPI, it works fine for me.
.NET Core WebAPI method.
public async Task<IActionResult> Login([FromForm] LoginModel loginInfo)
{ // JWT code here }
.Net Core MVC Action Method.
public async void InvokeLoginAPIAsync(string endPoint, string userName, string pwd)
{
configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
baseUrl = configuration["Application:BaseAPI"] ?? throw new Exception("Unable to get the configuration with key Application:BaseAPI");
string targetUrl = string.Format("{0}/{1}", baseUrl, endPoint);
using (HttpClient deviceClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, targetUrl);
var data = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("userName", userName),
new KeyValuePair<string, string>("password", pwd)
};
request.Content = new FormUrlEncodedContent(data);
using (var response = await deviceClient.SendAsync(request))
{
if (response.StatusCode == HttpStatusCode.OK)
{
TempData["Response"] = JsonConvert.SerializeObject(response.Content);
}
}
}
}
Which version of .NET Core are you using?
Try doing the request from the browser and see if you have the same result.
Also, are you sure you're doing a GET and not a POST request in Postman? You shouldn't get 415 errors for GET requests, especially when you're not sending any body.
This error mainly occurs when you try to send a body and you haven't specified the media-type through the Content-Type header.
Ensure that the request is GET and your body is empty.
Solution after post edit:
As you're trying to parse a DTO object(SomeClassObj), you should specify where the values should come from. In order to fix your specific case, add the [FromQuery] attribute before SomeClassObj.
Your code should look like this:
[ApiController]
[Route("MyController")]
public class MyController : ControllerBase
{
[HttpGet]
[Route("GetResult")]
public IActionResult GetResult(string param1, string param2= null, [FromQuery]SomeClassObj obj = null)
{ .... }
}
This tells the parser to fetch the data from the query string. This will fix the 415 issue. However, if you want to bind to complex types, especially on get, checkout those topics: ASP.NET CORE 3.1 Model Binding and this issue as you will most probably encounter issues with parsing your DTO object.
Use [FromForm] attribute before each argument in the controller function.

Consume API into aspnet core 2.2

I have a web API which returns about 1000 records into json. Now I want to get all those records from json response and deserialize into a model
The code i have written so far is like following:
public async Task<IActionResult> GetList()
{
Facility facilityInfo = new Facility();
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.GetAsync("http://localhost:55555/api/Facilities"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
facilityInfo = JsonConvert.DeserializeObject<Facility>(apiResponse);
}
}
//-----------other parts of code-----------------
return View();
}
The problem is that it returns null facilityInfo
Thank you
You're deserializing something that has a property called items which is a list of Facilities. So if you have the following structure:
public class Facilities
{
public List<Facility> items;
}
And then
Facilities facilities = JsonConvert.DeserializeObject<Facilities>(apiResponse);
The facilities.items property will contain the data.

Post value always null between Python request and ASP.Net Core API

I have some simple Python
import requests
response = requests.post(url+'users/', data = 'hallo')
And a simple ASP.Net Core API
public void Post(string value)
{
var newUser = JsonConvert.DeserializeObject<User>(value);
// [...]
}
When I breakpoint the C# code the incoming parameter 'value' is always null. I have tried sending JSON instead of a simple string, setting the headers to 'text/plain', 'application/json', or 'application/x-www-form-urlencoded' in Python but the result is the same. I have tried decorating the parameter [FromBody] in the ASP.Net Core API, but then I get a 400 error ("The input was invalid").
What am I missing?
(EDIT. Here is a hacky fix, definitely not an answer, but it may help people see what's wrong.
public void Post(string value)
{
Request.EnableRewind();
var body = "";
using (var reader = new StreamReader(Request.Body))
{
Request.Body.Seek(0, SeekOrigin.Begin);
body = reader.ReadToEnd();
}
value = body;
var newUser = JsonConvert.DeserializeObject<User>(value);
// [...]
}
Then value is set correctly. But if the value is sitting there in the body of the post, it is frustrating that using [FromBody] results in a 400 error.)
Hi dumbledad I had this same issue, I guess you would have solved it but I wanted to post the solution here.
I had to send the data to params instead of data.
So on the Python side:
payload = {"type": "2"}
myheaders={'Content-type':'application/json', 'Accept':'application/json'}
r = requests.post(url=myurl,headers=myheaders, params=payload)
On the c# side:
[Route("MyFunction")]
[HttpPost]
public ActionResult GetOpenFootball_(string type){}
This mapped the value "2" to the string parameter called "type".
EDIT
I managed to get objects through the [From Body] tag in this way:
I changed the header to:
_headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
I put my parameters into a json such as:
jsondata = {"username":"selzero"}
Then I plugged that into the json property in request like:
r = requests.post(url=_url, headers=_headers,json=jsondata)
This works, MVC controller [FromBody] likes it.
If you make your request like this:
response = requests.post(url, data={'value' : 'hallo'})
Then this will associate 'hallo' with the method-parameter value:
[HttpPost]
public void Post(string value)
{
Console.WriteLine(value);
}
If you need to send more data, you could also wrap your request in an object and fetch it by model-binding. See more here: Model Validation in ASP.NET Web API. It looks like that's what you're attempting to do anyways here:
var newUser = JsonConvert.DeserializeObject<User>(value);
You can achieve this automatically like this:
response = requests.post(url, json={ 'FirstName' : 'john', 'LastName' : 'doe' })
Notice the use of json=. And in your controller:
public class User {
public string FirstName { get; set; }
public string LastName { get; set; }
}
[HttpPost]
public void Post([FromBody] User user)
{
Console.WriteLine(user.FirstName);
Console.WriteLine(user.LastName);
}
// Outputs:
// john
// doe

Return an image from asp.net web api core as IActionResult

What is the best way to return an image file as IActionResult while using asp.net web api core?
I tried returning a base64 string and it works fine. But not considered as efficient.
Is there a way using which we can return an image file object itself as IActionResult.
You can use the various overloads of the File() function in controllers that inherit from Controller or ControllerBase.
For example, you can do:
return File("~/Images/photo.jpg", "image/jpeg");
This uses a virtual path, other options include giving it a byte array or a Stream. You can also give a download file name as a third argument if that is needed.
[Route("getProductImage/v1")]
[HttpGet]
public async Task<IActionResult> getProductImage(GetProductImageQueryParam parammodel)
{
using (HttpClient client = new HttpClient())
{
MNimg_URL = MNimg_URL + parammodel.modelname;
HttpResponseMessage response = await client.GetAsync(MNimg_URL);
byte[] content = await response.Content.ReadAsByteArrayAsync();
//return "data:image/png;base64," + Convert.ToBase64String(content);
return File(content, "image/png", parammodel.modelname);
}
}
In .net core web api you can use the above code
here GetProductImageQueryParam is a class with input parameters
A File result is called FileContentResult in NET Core 3.x.
You can return image using return file with stream or bytes format or using its image path.
There are few overloaded methods for return File(//parameters); which you can use it in mvc controller's action method.
API Controller
[Route("api/[controller]")]
public class FileController : Controller {
//GET api/file/id
[HttpGet("{id}"]
public async Task<IActionResult> GetFile(string id) {
var stream = await {{//__get_stream_here__//}};
var response = File(stream, "application/octet-stream"); // FileStreamResult
return response;
}
}
or
var imageFileStream = System.IO.File.OpenRead("// image path");
return File(imageFileStream, "image/jpeg");
Hope this will help you.

Rest API Calls with RestSharp calling ASP.NET Web API

I'm currently testing out writing a RESTful API with ASP.NET Web API. I'm using RestSharp on a client to simulate different calls.
I want to submit an application ID query string, and the body should be a collection of type "Log". Every time, the application ID get's posted by the body received by the server is always NULL.
Code on the server:
public class LogsController : ApiController
{
public HttpStatusCode Post(Guid ID, [FromBody] List<Log> logs)
{
if (logs != null)
return HttpStatusCode.OK;
else
return HttpStatusCode.PreconditionFailed;
}
}
public class Log
{
public Guid ErrorId { get; set; }
}
Code on the client:
static void Main(string[] args)
{
var client = new RestClient("http://localhost:36146/api");
var json = JsonConvert.SerializeObject(new List<Log>()
{
new Log { ErrorId = Guid.NewGuid()}
});
var request = new RestRequest("Logs", Method.POST);
request.RequestFormat = DataFormat.Json;
request.AddParameter("ID", Guid.NewGuid(), ParameterType.QueryString);
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json; charset=utf-8");
request.AddBody(json);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Console.Read();
}
public class Log
{
public Guid ErrorId { get; set; }
}
I thought I got this working, however no matter what I do now the "logs" parameter on the server is always NULL.
I think I've found the issue.
RestSharp implicitly uses the JsonSerializer when populating the body of the request. As I was also called the Serializer I think it caused issues with the formatting.
I've removed that call to the serializer and now I'm receiving a 200 back from the server.
Happy days.