I'm calling a POST controller action using fetch, but in the controller, the body appears to be null.
Here's my fetch code snippet - this is in a .net core Vue project. It's a typescript file.
var data = JSON.stringify(this.newProduct);
console.log(data)
fetch('api/Product/AddNewProduct', {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));
And here's the request (and payload) as I can see it in Firefox:
But in my .net core backend, when the API gets hit, I can't seem to get the value of the body or anything in the request.
[HttpPost("[action]")]
public IActionResult AddNewProduct([FromBody] string body)
{
Product newProduct;
try
{
/*Added the below snippet for testing, not sure if actually necessary */
using (var reader = new StreamReader(Request.Body))
{
var requestBody = reader.ReadToEnd();
// Do something
}
//Convert the request body to an object
newProduct = JsonConvert.DeserializeObject<Product>(body);
}
catch (Exception e)
{
return new BadRequestResult();
}
Here, in my debugger, both body and requestBody are null. Any ideas?
.NET isn't seeing you passing a string, it sees a JSON because you pass the Content-Type header of application/json so it will try to deserialize it and map it to your request object. In your case, because your parameter is string body the parser tries to map the JSON object to your string and fails - so it passes null.
You can try and change the request to pass text/plain as the content-type (or remove the content-type header) or change your API parameter to the object you are sending:
public IActionResult AddNewProduct([FromBody] Product newProduct)
{
...
}
Add to headers Accept
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
Related
I have an C# Backend+API which I access from my Vue Application. To do the Requests I use axios. I have an Endpoint in C# with the following code
// GET: api/Timezone
public HttpResponseMessage GetTimezoneData()
{
var timezones = _service.GetSmcTimezones();
var osTimezones = _service.GetOSTimezones();
var resp = JsonConvert.SerializeObject(osTimezones);
//new JsonResult { Data = osTimezones, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(resp, System.Text.Encoding.UTF8, "application/json");
return response;
}
I acces this request in the frontend with following code:
const config:AxiosRequestConfig = {
method: 'get',
url: 'https://localhost:44333/api/Timezone/',
headers: { }
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
}
}
The weird thing is, that I can access the data when using Postman, but when sending the request from the frontend the requests fails. I recieve a (failed)net::ERR_FAILED code and I dont get any response data.
Does anyone have an idea?
Have you tried to specify Content-Type header?
const config:AxiosRequestConfig = {
method: 'get',
url: 'https://localhost:44333/api/Timezone/',
headers: {
"Content-type": "application/json"
}
};
I followed a tutorial and wanted to expand the functionality of already existing code. Code: https://send.firefox.com/download/27d75a7398f351c9/#wKkjkAuvlpBVOMKx9szFHA
It's using angularJS as the frontend and webapi 2 aspnet with entitiyframework. The problem is that my http posts are sending null values. I am guessing I have to post it in the right format, but im not sure how exactly to do it.
I tried transforming it into an object but it still received as null.
controller.js
function save(contact) {
var deferred = $q.defer();
$http.post('/api/Contact/' + contact).success(function (results) {
$scope.contact = results;
deferred.resolve(results);
}).error(function (data, status, headers, config) {
deferred.reject('Failed saving contact');
});
return deferred.promise;
};
fails at db.Contacts.Add(Contact); (null error)
public HttpResponseMessage Post([FromBody] Contact Contact)
{
if (ModelState.IsValid)
{
Console.Write("post contact");
Console.Write(Contact);
db.Contacts.Add(Contact);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, Contact);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = Contact.Id }));
return response;
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
Tried sending this:
{Name: "test", Address: "test", City: "teee", Country: "tesd"}
Edit:
After trying to save the contact with the save function:
output of console log
Request URL: http://localhost:64158/api/Contact/[object%20Object]
If i open the [object%20Object] in a new tab from the network view i get this:
<Error>
<Message>The request is invalid.</Message>
<MessageDetail>
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'SampleEF6.Models.Contact Get(Int32)' in 'SampleEF6.Controllers.ContactController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
</MessageDetail>
</Error>
You're sending the data wrong. You're supposed to send the data (contact in this example) in the body of the request, instead of the url.
Try $http.post('/api/Contact/', contact)
https://docs.angularjs.org/api/ng/service/$http#post
I need to send a File (image) from the frontend in Angular along with some more parameters via a POST request to my ASP.NET Core backend to upload the file to a server. The issue is that I either get an HTTP 500 Error depending on the headers I specify or, the most usual, the backend receives the FormData as an empty object.
In Angular, I first convert the Base64 image I receive to a Blob, then a File to construct the FormData (it's what the ngx-compress-image package returns when it compresses an image. Maybe there is a better way to do this too?). Then I assign the headers and send the POST request:
export class RestApiService {
token: string = 'The session token';
userID: string = 'The user ID';
UploadImage(picAsBase64: string) {
let blob = new Blob([picAsBase64], { type: 'image/png' });
let file = new File([blob], Math.random().toString(36).substr(2, 5));
let formData: FormData = new FormData();
formData.append('pfile', file);
const headers = new HttpHeaders({
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
});
let options = { headers: headers };
let body = {
'paramh': this.token,
'pfile': formData,
'pkuserid': this.userID
}
return this.http.post('api/UploadFiles/UploadFiles/', body, options).pipe(map(data => { return data; }));
}
}
Backend:
[Route("api/[controller]")]
public class UploadFilesController : Controller
{
[HttpPost("UploadFiles")]
public async Task<IActionResult> UploadFiles([FromBody]JObject data)
{
string paramh = data["paramh"].ToString();
IFormFile pfile = data["pfile"].ToObject<IFormFile>();
string pkuserid = data["pkuserid"].ToString();
...
}
}
EDIT
Okay, so I applied Tony's solution, but it didn't work at first. After some tests, I found the solution in declaring the IFormFile variable as a List like Nishant suggested and declaring every argument with [FromForm] like so:
public async Task<IActionResult> UploadFiles([FromForm]string paramh, [FromForm] string pkuserid, [FromForm]List<IFormFile> pfiles)
BUT I still have an issue, because it turns out that my IFormFile has ContentType: application/octet-stream
I don't know if this is the usual and I must convert it from the backend to some image contenttype or something like that, or if it should come from the POST request as an image/png like I declared in the Angular Blob before creating the file.
Variable screenshot
Thank you all again and hope you can still help me with this last issue.
You have to use FormData for your object also like this
let formData: FormData = new FormData();
formData.append('pfile', file);
formData.append('paramh', this.token);
formData.append('pkuserid', this.userID);
return this.http.post('api/UploadFiles/UploadFiles/', formData, options).pipe(map(data => { return data; }));
Also you have to use [FromForm] in your controller
public async Task<IActionResult> UploadFiles([FromForm]JObject data)
Both the urls aren't matching.
I have done it by using IFormFile interface
string emailModel is the Model which will be deserialized
List of IFormFile files for the list of attachments
[HttpPost]
public async Task<IActionResult> SendEmailAsync(string emailModel, List<IFormFile> files)
{
EmailModel emailModel = JsonConvert.DeserializeObject<EmailModelWith>(emailModel);
List<Attachment> attachments = new List<Attachment>();
foreach (var file in files)
{
Attachment attachment = new Attachment(file.OpenReadStream(), file.FileName, file.ContentType);
attachments.Add(attachment);
}
return Ok();
}
I'm having trouble POSTing to my WEBAPI.
My code for my ASP.NET WEBAPI is as follows:
[RoutePrefix("api/Test")]
public class TestController : ApiController
{
// GET: api/Test
[Route]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[Route]
public void Post([FromBody]string value)
{
Debugger.Break();
}
The calls from my Vue.js app via axios are as follows:
PostTest(){
alert("Post Test Starting");
var data = new FormData();
data.set('value','test');
//var data = {'value': 'test'};
this.CallAxios('post', 'http://localhost:10000/api/Test/', data, axiosHeaders);
},
CallAxios(callMethod, callURL, callData, callHeaders)
{
axios({
method: callMethod,
url: callURL,
data: callData,
headers: callHeaders
}).then((response) => {
alert(response.data)
})
}
My Content-Type for all requests is set to application/json
If I call the PostTest Vue method using the FormData section and leaving the [FromBody] attribute on the .NET API Post method I receive the following 415 "Unsupported Media Type" error:
{"Message":"The request entity's media type 'multipart/form-data' is
not supported for this resource.","ExceptionMessage":"No
MediaTypeFormatter is available to read an object of type 'String'
from content with media type 'multipart/form-data'.",
So even though I specified application/json for the Content-Type it is still coming across as multipart/form-data
If I switch the Vue.js code to use the "var data = {'value': 'test'}" and I remove the [FromBody] attribute from the .NET Post parameter I receive the following 405 "Method Not Allowed" error:
{"Message":"The requested resource does not support http method
'POST'."}
I've been fighting with this for a while. It seems I can do one of the following options:
Using the FormData vue call, how do I get by the 415 error with the multipart/form-data issue?
Using a JSon string how do I support the POST verb in the API call?
There's two ways to accomplish this (neither of them will use FormData)
1) Sending as Content-Type: application/x-www-form-urlencoded . To do this you'll need to URL-encode the parameter with a library like qs. However, notice this special WebAPI caveat (empty key name)
import qs from 'qs';
let myValue = 'FontSpace'
let options = {
url: '/api/fonts'
, method: 'post'
, data: qs.stringify({ '': myValue })
}
let response = await this.$http(options)
If you inspect the request in devtools you'll see the parameter gets encoded as
=FontSpace
2) Sending as Content-Type application/json . To do this you'll JSON.stringify JUST the value (no key) and also explicitly specify the content-type in the headers
let myValue = 'FontSpace'
let options = {
headers: { 'Content-Type': 'application/json' }
, url: '/api/fonts'
, method: 'post'
, data: JSON.stringify(myValue)
}
let response = await axios(options)
Inspect in devtools and you'll see the the parameter gets JSON encoded
"FontSpace"
As long as your encoding and your content-type matches, WebAPI2 will accept your request.
This documentation was helpful
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 an effort to make a progress reporting process a little more reliable and decouple it from the request/response, I am performing the processing in a Windows Service and persisting the intended response to a file. When the client starts polling for updates, the intention is that the controller returns the contents of the file, whatever they are, as a JSON string.
The contents of the file are pre-serialized to JSON. This is to ensure that there is nothing standing in the way of the response. No processing needs to happen (short of reading the file contents into a string and returning it) to get the response.
I initially though this would be fairly simple, but it is not turning out to be the case.
Currently my controller method looks thusly:
Controller
Updated
[HttpPost]
public JsonResult UpdateBatchSearchMembers()
{
string path = Properties.Settings.Default.ResponsePath;
string returntext;
if (!System.IO.File.Exists(path))
returntext = Properties.Settings.Default.EmptyBatchSearchUpdate;
else
returntext = System.IO.File.ReadAllText(path);
return this.Json(returntext);
}
And Fiddler is returning this as the raw response
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Mon, 19 Mar 2012 20:30:05 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 3.0
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 81
Connection: Close
"{\"StopPolling\":false,\"BatchSearchProgressReports\":[],\"MemberStatuses\":[]}"
AJAX
Updated
The following will likely be changed later, but for now this was working when I was generating the response class and returning it as JSON like a normal person.
this.CheckForUpdate = function () {
var parent = this;
if (this.BatchSearchId != null && WorkflowState.SelectedSearchList != "") {
showAjaxLoader = false;
if (progressPending != true) {
progressPending = true;
$.ajax({
url: WorkflowState.UpdateBatchLink + "?SearchListID=" + WorkflowState.SelectedSearchList,
type: 'POST',
contentType: 'application/json; charset=utf-8',
cache: false,
success: function (data) {
for (var i = 0; i < data.MemberStatuses.length; i++) {
var response = data.MemberStatuses[i];
parent.UpdateCellStatus(response);
}
if (data.StopPolling = true) {
parent.StopPullingForUpdates();
}
showAjaxLoader = true;
}
});
progressPending = false;
}
}
The issue, I believe, is that the Json action result is intended to take an object (your model) and create an HTTP response with content as the JSON-formatted data from your model object.
What you are passing to the controller's Json method, though, is a JSON-formatted string object, so it is "serializing" the string object to JSON, which is why the content of the HTTP response is surrounded by double-quotes (I'm assuming that is the problem).
I think you can look into using the Content action result as an alternative to the Json action result, since you essentially already have the raw content for the HTTP response available.
return this.Content(returntext, "application/json");
// not sure off-hand if you should also specify "charset=utf-8" here,
// or if that is done automatically
Another alternative would be to deserialize the JSON result from the service into an object and then pass that object to the controller's Json method, but the disadvantage there is that you would be de-serializing and then re-serializing the data, which may be unnecessary for your purposes.
You just need to return standard ContentResult and set ContentType to "application/json".
You can create custom ActionResult for it:
public class JsonStringResult : ContentResult
{
public JsonStringResult(string json)
{
Content = json;
ContentType = "application/json";
}
}
And then return it's instance:
[HttpPost]
public ActionResult UpdateBatchSearchMembers()
{
string returntext;
if (!System.IO.File.Exists(path))
returntext = Properties.Settings.Default.EmptyBatchSearchUpdate;
else
returntext = Properties.Settings.Default.ResponsePath;
return new JsonStringResult(returntext);
}
Yeah that's it without no further issues, to avoid raw string json this is it.
public ActionResult GetJson()
{
var json = System.IO.File.ReadAllText(
Server.MapPath(#"~/App_Data/content.json"));
return new ContentResult
{
Content = json,
ContentType = "application/json",
ContentEncoding = Encoding.UTF8
};
}
NOTE: please note that method return type of JsonResult is not working for me, since JsonResult and ContentResult both inherit ActionResult but there is no relationship between them.
Use the following code in your controller:
return Json(new { success = string }, JsonRequestBehavior.AllowGet);
and in JavaScript:
success: function (data) {
var response = data.success;
....
}
All answers here provide good and working code. But someone would be dissatisfied that they all use ContentType as return type and not JsonResult.
Unfortunately JsonResult is using JavaScriptSerializer without option to disable it. The best way to get around this is to inherit JsonResult.
I copied most of the code from original JsonResult and created JsonStringResult class that returns passed string as application/json. Code for this class is below
public class JsonStringResult : JsonResult
{
public JsonStringResult(string data)
{
JsonRequestBehavior = JsonRequestBehavior.DenyGet;
Data = data;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Get request is not allowed!");
}
HttpResponseBase response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
response.Write(Data);
}
}
}
Example usage:
var json = JsonConvert.SerializeObject(data);
return new JsonStringResult(json);