React - Can't fetch image from URL but works in C# - c#

I've a problem which I find a bit weird but it is surely obvious for you guys. I'm trying to fetch an image as blob and then convert it to base64 to store it in Azure storage later on. I get the URL for the image from an object and want to download it as base64 to my react app. I'm able to get the image and put it into a html image tag as source , and it works just fine, the image shows up. On my server I can get the image via a HTTP client request and get it just fine too.
In C#:
using (var client = new HttpClient())
using (HttpResponseMessage response2 =
await client.GetAsync("URL HERE"))
{
byte[] fileContents = await response2.Content.ReadAsByteArrayAsync()>
}
When I'm trying to fetch the image in my react app like this:
const response = await fetch('URL HERE');
OR
const response = await fetch('URL HERE', {
method: 'GET',
mode: 'no-cors',
headers: {},
});
OR
let file = await fetch('URL HERE',
{
method: 'GET',
mode: 'no-cors',
})
.then((r) => r.blob())
.then((blobFile) => {
return new File([blobFile], 'FileName', { type: 'image/png' });
});
Why is this happening in React but in C# everyting works fine and how to solve it? It eaither shows as CORS-error OR the blob is empty, size 0. If I click the link I reach the image so the URL is fine too.
Any suggestions?
Many thanks!

no-cors option is rarely a good way to fix cors errors. When you use React you use browser's system and browser send you a cors errors when it not receive a correct response. For example, if your server sends you a 500 error, oftenly you will not receive the 500 error but a cors error.
Did you try to make the same request from React with Postman ? I think, if it works woth C#, it will probaly works with Postman but just to try.
Did you make some console logs like this :
let file = await fetch('url')
.then(r => {
console.log(r);
return r.blob();
)
.then((blobResponse) => {
console.log(blobResponse);
return blobResponse; // if you want to dl the file make a new File is probably not necessary : URL.createObjectURL(blob)
});
Maybe it is a real CORS error and so you should check your server and API configuration. There is a lot of issues about it on internet.
If it is, try something like this on your API response : (do not keep 'Access-Control-Allow-Origin', '*' on production)
$response->headers->set('Access-Control-Expose-Headers', 'Access-Control-*');
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Headers', '*');
$response->headers->set('Access-Control-Allow-Methods', 'HEAD, PUT, GET, POST, DELETE, OPTIONS');
$response->headers->set('Allow', 'HEAD, PUT, GET, POST, DELETE, OPTIONS');
Maybe you need too, to make a response for the OPTIONS http request. Browser send it before your request to ensure that your request will be received :
if ($request->getMethod() == 'OPTIONS') {
return $this->jsonResponse([]);
}

Related

How to delete ephemeral Slack message

When a user uses a slash command, I post an ephemeral message to just that user. When they click a button I want that ephemeral message to either delete or update. Right now I'm just trying to get the delete part working.
In the Slack API documentation it says to send a HTTP POST to the response_url. The only response_url I get when a user clicks a button is something like this: https://hooks.slack.com/actions/XXXXXXXXX/YYYYYYYYYYYY/ZZZZZZZZZZZZZZZZZZZZZZZZ
When I send a POST to that url, I get an error(I'm not sure what the error is, I just know that my code fails on a try/catch).
The JSON I'm sending to that URL looks like this:
{
"response_type" = "ephemeral",
"replace_original" = "true",
"delete_original" = "true",
"text" = ""
};
I see in the Slack API documentation it says I should be sending that JSON to a URL that begins with https://hooks.slack.com/services/ however I'm not receiving any response_url that has the /services/.
Here is the C# code I am using to send the response:
var replaceMsg = new NameValueCollection();
replaceMsg["delete_original"] = "true";
replaceMsg["replace_original"] = "true";
replaceMsg["response_type"] = "ephemeral";
replaceMsg["text"] = "";
var responseReplace = client.UploadValues(button.response_url, "POST", replaceMsg);
Edit: It looks like I'm getting a 404 error when I try to send that
Exception: System.Net.WebException: The remote server returned an error: (404) Not Found.
However, when I paste the exact URL into my browser I don't get a 404, I see a JSON.
Can anyone guide me in the right direction? Thanks.
I did something very similar not too long ago and was able to get it working, though it was tricky.
This post helped me big time: How to remove Ephemeral Messages
I would do two things:
1) make sure you know what that POST response failure is as it may contain useful information.
2) It looks like you're sending in the string "true" instead of the boolean values, I'm guessing you probably want to send the boolean values instead:
{
"response_type" = "ephemeral",
"replace_original" = true,
"delete_original" = true,
"text" = ""
};
#theMayer and #Aaron were correct about the headers being wrong.
Setting the headers solved the issue, like so:
var client = new WebClient();
client.Headers.Add("Content-Type", "text/html");
var response = client.UploadData(button.response_url, "POST", Encoding.Default.GetBytes("{\"response_type\": \"ephemeral\",\"replace_original\": \"true\",\"delete_original\": \"true\",\"text\": \"\"}"));

React Axios - C# WebAPI request token fails without a server error

I have the following code:
var qs = require('qs');
const ROOT_URL = 'http://localhost:56765/';
const data = qs.stringify({ username, password, grant_type: 'password' });
axios.post(`${ROOT_URL}token`, data)
.then(response => {
debugger;
dispatch(authUser());
localStorage.setItem('token', response.data.token);
})
.catch((error) => {
debugger;
dispatch(authError(error.response));
});
When I run this code, I hit the debugger and the error object has Error: Network Error at createError (http://localhost:3000/static/js/bundle.js:2188:15) at XMLHttpRequest.handleError (http://localhost:3000/static/js/bundle.js:1724:14) in the catch block. However, in the network tab in Chrome, the Status code is 200, but when I click on the response/preview tabs of this request there is no data. If I click on continue, I actually get the token and other data in the response/preview tab as expected, but at this point it has already reached the catch block so will not hit the then block.
I have even debugged the back-end and it doesn't send back an error, so I assume this is a front end error. Does anyone know what is causing this?
Edit:
Just to add more details, if I change the request to work with fetch instead of axios, I am getting a different error TypeError: Failed to fetch. The code used for the call is:
fetch(`${ROOT_URL}token`, {
method: 'POST',
body: data
})
Also, this is an example of the postman request working correctly:
Finally found my answer. It turns out I had not enabled CORS in my WebAPI back-end for the /token endpoint. The way I was able to resolve this was by enabling CORS in the MyProject\Providers\ApplicationOAuthProvider.cs by adding the line context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); to the top of the method named GrantResourceOwnerCredentials. I do think there are better ways to enable CORS for the entire project which I will look into next!
Got some help from here about how to enable the CORS: Enable CORS for Web Api 2 and OWIN token authentication
It should work with fetch by adding content-type :
fetch(`${ROOT_URL}token`, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'},
body: data
})
You can try this in axios too but it seems related to an axios bug, you can give a try by adding the params property :
axios.post(`${ROOT_URL}token`, data, params: data)

OneDrive API Resumable upload url not responding

I have been developing a OneDrive desktop client app because the one built into windows has been failing me for reasons I cannot figure out. I'm using the REST API in C# via an HttpClient.
All requests to the onedrive endpoint work fine (downloading, uploading small files, etc.) and uploading large files worked fine up until recently (about two days ago). I get the upload session URL and start uploading data to it, but after uploading two chunks to it successfully (202 response), the third request and beyond times out (via the HttpClient), whether it be a GET to get the status, or a PUT to upload data. The POST to create the session still works.
I have tried: getting a new ClientId, logging into a new Microsoft account, reverting code to a known working state, and recloning git repository.
In PostMan, I can go through the whole process of creating a session and uploading chunks and not experience this issue, but if I take an upload URL that my application retrieves from the OneDrive API and try to PUT data to it in PostMan, the server doesn't respond (unless the request is invalid, then it sometimes tells me). Subsequent GET requests to this URL also don't respond.
Here is a log of all requests going to the OneDrive API after authentication: https://pastebin.com/qRrw2Sb5
and here is the relevant code:
//first, create an upload session
var httpResponse = await _httpClient.StartAuthenticatedRequest(url, HttpMethod.Post).SendAsync(ct);
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
return new HttpResult<IRemoteItemHandle>(httpResponse, null);
}
//get the upload URL
var uploadSessionRequestObject = await HttpClientHelper.ReadResponseAsJObjectAsync(httpResponse);
var uploadUrl = (string)uploadSessionRequestObject["uploadUrl"];
if (uploadUrl == null)
{
Debug.WriteLine("Successful OneDrive CreateSession request had invalid body!");
//TODO: what to do here?
}
//the length of the file total
var length = data.Length;
//setup the headers
var headers = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("Content-Length", ""),
new KeyValuePair<string, string>("Content-Range","")
};
JObject responseJObject;
//the response that will be returned
HttpResponseMessage response = null;
//get the chunks
List<Tuple<long, long>> chunks;
do
{
HttpResult<List<Tuple<long, long>>> chunksResult;
//get the chunks
do
{
chunksResult = await RetrieveLargeUploadChunksAsync(uploadUrl, _10MB, length, ct);
//TODO: should we delay on failure?
} while (chunksResult.Value == null);//keep trying to get thre results until we're successful
chunks = chunksResult.Value;
//upload each fragment
var chunkStream = new ChunkedReadStreamWrapper(data);
foreach (var fragment in chunks)
{
//setup the chunked stream with the next fragment
chunkStream.ChunkStart = fragment.Item1;
//the size is one more than the difference (because the range is inclusive)
chunkStream.ChunkSize = fragment.Item2 - fragment.Item1 + 1;
//setup the headers for this request
headers[0] = new KeyValuePair<string, string>("Content-Length", chunkStream.ChunkSize.ToString());
headers[1] = new KeyValuePair<string, string>("Content-Range", $"bytes {fragment.Item1}-{fragment.Item2}/{length}");
//submit the request until it is successful
do
{
//this should not be authenticated
response = await _httpClient.StartRequest(uploadUrl, HttpMethod.Put)
.SetContent(chunkStream)
.SetContentHeaders(headers)
.SendAsync(ct);
} while (!response.IsSuccessStatusCode); // keep retrying until success
}
//parse the response to see if there are more chunks or the final metadata
responseJObject = await HttpClientHelper.ReadResponseAsJObjectAsync(response);
//try to get chunks from the response to see if we need to retry anything
chunks = ParseLargeUploadChunks(responseJObject, _10MB, length);
}
while (chunks.Count > 0);//keep going until no chunks left
Everything does what the comments say or what the name suggests, but a lot of the methods/classes are my own, so i'd be happy to explain anything that might not be obvious.
I have absolutely no idea what's going on and would appreciate any help. I'm trying to get this done before I go back to school on Saturday and no longer have time to work on it.
EDIT: After waiting a while, requests can be made to the upload URL again via PostMan.
EDIT 2: I can no longer replicate this timeout phenomenon in Postman. Whether I get the upload URL from my application, or from another Postman request, and whether or not the upload has stalled in my application, I can seem to upload all the fragments I want to through Postman.
EDIT 3: This not-responding behavior starts before the content stream is read from.
Edit 4: Looking at packet info on WireShark, the first two chunks are almost identical, but only "resend" packets show up on the third.
So after 3 weeks of varying levels of testing, I have finally figured out the issue and it has almost nothing to do with the OneDrive Graph api. The issue was that when making the Http requests, I was using the HttpCompletionOption.ResponseHeadersRead but not reading the responses before sending the next one. This means that the HttpClient was preventing me from sending more requests until I read the responses from the old ones. It was strange because it allowed me to send 2 requests before locking up.

HTTP Post to Web API 2 - Options request received and handled no further request received

I have a web application using MVC and AngularJS, which connects to a Web API 2 api, that I have set up in a separate project.
Currently I am able to retrieve information from the Api with no problems.
However when I try to do a HTTP Post I am getting no response, originally I was getting a problem with the pre-flight request failing, I have now handled this in my controller, however it does not send the proper request after it has got an OK message back.
I have included my code for the Angular Factory and the C# Controller in the API.
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class RegisterController : ApiController
{
public string Post()
{
return "success";
}
public HttpResponseMessage Options()
{
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
}
}
var RegistrationFactory = function($http, $q, ApiAddress) {
return function(model) {
// $http.post(ApiAddress.getApiAddress() + '/Register/Post', model.ToString());
$http({
method: "POST",
url: ApiAddress.getApiAddress() + '/Register/Post',
data: model,
headers: { 'Content-Type': 'application/json; charset=utf-8' }
}).success(function(data) {
$location.path("/");
});
}
};
RegistrationFactory.$inject = ['$http', '$q', 'ApiAddress'];
Edit:
I am still not having any joy with this, however I tested in Internet Explorer and it works with no problems at all.
I have got it working in chrome by starting with web security disabled, however obviously this is not ideal as it will not work on a user PC with security enabled.
I see that you have done adaptation for CORS on the server side. But I cannot see any client side (javascript) adaptation. May be you should add the code below before calling the service.
$http.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
Let me know if this fixes the issue. Worked for me in all scenarios :)
It's strange that your GETs work, but your POSTs don't.
I would recommend running the code in Google Chrome with web security enabled (so we can watch it go wrong) and with the F12 Developer Options shown.
Select the Network tab, run your code, and watch what happens when the POST is called.
Does your service return a "200 OK" status, or some other value ?
Does any kind of Response get returned ?
It might be worth trying this, and appending a screenshot of the results in your original question. It might help to identify the cause.
I am still not having any joy with this, however I tested in Internet
Explorer and it works with no problems at all.
Btw, you don't have any single sign-on stuff setup in your company, do you ? We've had issues where IE works fine, but other browsers don't allow single sign-on. Just a thought...
CORS requires a OPTIONS-preflight which has HTTP headers in its response that tell the browser whether it is allowed to access the resource.
E.g. HTTP Response Headers:
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Origin: *
Because you have a custom Options handler in your C# controller, it seems those HTTP headers are not returned, stopping the browser to make the call after the preflight.
Avoid the Options method, and you should be good.

c# asp.net MVC Download a file via Ajax

I'm pretty sure this isn't possible but I thought I'd ask...
I have a FileResult which returns a file, when it works there's no problem. When there's an error in the FileResult for some reason, I'd like to display an exception error message on the users screen, preferably in a popup.
Can I do an Ajax post which returns a file when successful and displays a message if not?
I think it is not possible cause in order to handle ajax post, you will have to write a javascript handler on the client side and javascript cannot do file IO on client side.
However, what you can do is, make an ajax request to check if file exists and can be downloaded. If, not, respond to that request negatively which will popup a dialog on client side. If successful, make a file download request.
Not specifically related to MVC but...
it can be done using XMLHttpRequest in conjunction with the new HTML5 File System API that allows you to deal with binary data (fetched from http response in your case) and save it to the local file system.
See example here: http://www.html5rocks.com/en/tutorials/file/xhr2/#toc-example-savingimages
Controller (MyApiController) Code:
public ActionResult DownloadFile(String FileUniqueName)
{
var rootPath = Server.MapPath("~/UploadedFiles");
var fileFullPath = System.IO.Path.Combine(rootPath,FileUniqueName);
byte[] fileBytes = System.IO.File.ReadAllBytes(fileFullPath);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, "MyDownloadFile");
}
Jquery Code:
$(document).on("click"," a ", function(){ //on click of anchor tag
var funame=$(this).attr('uname'); /*anchor tag attribute "uname" contain file unique name*/
var url = "http://localhost:14211/MyApi/DownloadFile?FileUniqueName= " + funame;
window.open(url);
});

Categories

Resources