HEAD Verb in Minimal API C# - c#

HEAD
The HTTP HEAD method requests the headers that would be returned if the HEAD
request's URL was instead requested with the HTTP GET method.
For example, if a URL might produce a large download,a HEAD request could read
its Content-Length header to check the file-size without actually downloading the file.
My question , how should I get that behavior in my minimal API endpoint.
when-ever I test my API Endpoint in vs-code through Thunder-Client, it return error when I select HEAD VERB
405 Method Not Allowed
However the downloading is work through GET VERB.
I am very grateful of any example using HEAD VERB, or some settings which maybe I not know to configure.
code in this example is refer from following stack overflow question ASP.NET Minimal API How to Return/Download Files from URL
app.MapGet(#"download/{fileName}", async (string fileName) =>
{
var mimeType = "application/zip";
var path = #$"e:\{fileName}.zip";
var bytes = await File.ReadAllBytesAsync(path);
return Results.File(bytes, mimeType, $"{fileName}.zip");
})
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.WithName($"GET {EndpointCategory} by Name")
.WithTags(Tag);

Use MapMethods with corresponding verb (docs):
app.MapMethods(#"download/{fileName}", new[] { "HEAD" }, async (string fileName) => ...);

Related

React - Can't fetch image from URL but works in 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([]);
}

Asana API 403 Response in C#

I am trying to implement a Xamarin app that works with the Asana API.
I have successfully implemented the OAuth as documented in the Asana documentation here... at least I assume it is successful. I get an access token from the token endpoint in an HTTPResponse with HTTP Status "OK".
But then when I turn around and try to make an API call with that same access token, I get a 403 Forbidden error. I tried the same API call in my browser (after logging in to Asana), and it works fine, which leads me to believe that I do have access to the resource, I must have an issue with authorizing the request on my end.
The API call in question is (documented here): https://app.asana.com/api/1.0/workspaces.
My C# code is as follows (abbreviated to relevant parts, and assume that ACCESS_TOKEN contains the access token I got from the token exchange endpoint):
HttpClient client = new HttpClient();
client.BaseAddress = "https://app.asana.com/api/1.0";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", ACCESS_TOKEN);
client.DefaultRequestHeaders.Add("Accept", "application/json");
And then I use this HttpClient (named client) in the following function:
// Returns a list of the Asana workspace names for the logged in user.
private async Task<List<string>> GetWorkspacesAsync()
{
List<string> namesList = new List<string>();
// Send the HTTP Request and get a response.
this.UpdateToken(); // Refreshes the token if needed using the refresh token.
using (HttpResponseMessage response = await client.GetAsync("/workspaces"))
{
// Handle a bad (not ok) response.
if (response.StatusCode != HttpStatusCode.OK)
{
// !!!THIS KEEPS TRIGGERING WITH response.StatusCode AS 403 Forbidden!!!
// Set up a stream reader to read the response.
// This is for TESTING ONLY
using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
// Extract the json object from the response.
string content = reader.ReadToEnd();
Debug.WriteLine(content);
}
throw new HttpRequestException("Bad HTTP Response was returned.");
}
// If execution reaches this point, the Http Response returned with code OK.
// Set up a stream reader to read the response.
using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
// Extract the json object from the response.
string content = reader.ReadToEnd();
JsonValue responseJson = JsonValue.Parse(content);
foreach (JsonValue workspaceJson in responseJson["data"])
{
string workspaceName = workspaceJson["name"];
Debug.WriteLine("Workspace Name: " + workspaceName);
namesList.Add(workspaceName);
}
}
}
// I have other awaited interactions with app storage in here, hence the need for the function to be async.
return namesList;
}
Finally found the answer. It looks like I was using HttpClient incorrectly; a subtle thing that should be equivalent, but is not due to the way it is implemented.
The answer
I needed to place the final slash at the end of the BaseAddress property of HttpClient, and NOT at the start of the relative address for the specific request. This answered question explains this.
To fix my code
I needed to change the setting up of the BaseAddress:
HttpClient client = new HttpClient();
client.BaseAddress = "https://app.asana.com/api/1.0/"; // FINAL SLASH NEEDED HERE
And remove the slash from the request's relative address:
// DO NOT put slash before relative address "workspaces" here
using (HttpResponseMessage response = await client.GetAsync("workspaces"))
Why I got the original error
When HttpClient combined the BaseAddress with the relative URI I specified in GetAsync(), it dropped off some of the base address, since the final slash was not included. The resulting address from combining the BaseAddress with the relative URI was a valid URL, but not a valid page/API call in Asana. Asana thus did an automatic redirect to a login page, which, of course, the rest of the API call would be forbidden from there.
How I discovered this
In debugging, I grabbed the access token returned during my app's authorization with Asana. I then recreated the request to the "/workspaces" API myself in Postman, and the request worked as expected. This confirmed that my authorization worked fine, and the issue must be with the specific request rather than the authorization. In debugging I then looked into the HttpResponseMessage, which has a property called RequestMessage, that includes the actual URL the GetAsync() made the request against. I observed the Login URL from Asana, rather than the BaseAddress I specified... which led me to the question/
answer linked above.
Hope this explanation helps anyone who comes across a similar error!

HttpContext.Current.Request.Files is always empty

I see lots of examples of WebAPIs accepting files. However, I have yet to find a solution as to why, no matter what I've tried, to get my HttpContext.Current.Request.Files to ever have a file in it that I am posting to the Web API through Postman. (I have also posted that image through a Console Application and got the same result)
[HttpPost]
[ActionName("Post")]
[ResponseType(typeof(PeliquinApiRsp))]
public IHttpActionResult Post(int personId)
{
var empPicture = PeliquinIOC.Resolve<IEmpPictureBL>(UserId, UserName, PropertyCode);
if (!(IsAllowed(SysPrivConstants.SYSPRIV__TYPE_PERSONNEL, SysPrivConstants.SYSPRIV__FUNC_PERSONNEL_CARDHOLDER, SysPrivConstants.SYSPRIV__LEVEL_FULL)))
return (Unauthorized());
var apiRsp = new PeliquinApiRsp();
var httpRequest = HttpContext.Current.Request;
if (httpRequest.Files.Count == 0)
return BadRequest();
empPicture.Post(httpRequest.Files[0].InputStream, personId);
apiRsp.SetStatus();
apiRsp.SetData("EmpPicture");
return (Ok(apiRsp));
}
It is a very simple method. I've been using Postman to post a Binary .jpg file. I have set the content-type = "multipart/form-data". No error is throw in my code, other than the .Files = 0.
I have some additional settings in my WebApiConfig for the rest of the API, but I'll include them just in case, along with the route for this particular method:
config.Formatters.Remove( config.Formatters.XmlFormatter );
config.Formatters.JsonFormatter.SupportedMediaTypes.Add( new MediaTypeHeaderValue( "application/json" ) );
config.Formatters.FormUrlEncodedFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
config.Routes.MapHttpRoute(
name: "EmpPicturePost",
routeTemplate: "api/EmpPicture/{personId}",
defaults: new { controller = "EmpPicture", action = "Post" },
constraints: new { personId = #"^\d+$", httpMethod = new HttpMethodConstraint(HttpMethod.Post, HttpMethod.Options) }
);
I am at wit's end trying to figure out why something so simple, just doesn't work. I've tried quite a few other way of doing this, that were all more MVC-like, but this isn't an MVC app, and they threw different errors. Thanks for any help.
I also struggle with this issue for a while but eventually found a solution. In my case i was calling the endpoint from an iOS app and the files where always empty. I found that it I need to have a http header that matches the file field name in the form so the request would have to have an http header for example:
file: newImage.jpg
and inside the body of the request you would need something like
Content-Disposition: form-data; name="file"; filename="newImage.jpg"
Content-Type: image/jpg
Please be aware that this is not a valid request just a snippet. You still need more http headers and the boundaries around the fields in the body. My point is that it seem the requirement is that http header key and the form field name match and both exist.
I suspect in all likely hood the root cause of your problem is because of this what you've mentioned in your post -
I've been using Postman to post a Binary .jpg file. I have set the
content-type = "multipart/form-data"
You should not be doing so explicitly in postman tool. Postman tool is smart enough to auto-detect Content-Type on its own. Please see this answer of mine to help you more with it.
Setting up Content-Type to "multipart/form-data" involves a complex concept of boundaries of multiple parts as detailed here. Heavy lifting of setting up the boundaries is done automatically for you by postman tool which is why it doesn't want you to set the content-type explicitly in this case.
I think Postman is not capable of filling the HttpContext.Current.Request.Files
collection. We fixed the problem by using:
await Request.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
Ivo

Authenticating requests to external REST service

I'm really new to web development, and I don't really have a good grip on the main concepts of web. However, I've been tasked with writing an asp.net application where users can search documents by querying an external RESTful web service. Requests to this REST service must be authenticated by HTTP Basic Authentication.
So far so good, I've been able to query the service using HttpWebRequest and HttpWebResponse, adding the encoded user:pass to the request's authorization header, deserialize the Json response and produce a list of strings with url's to the pdf documents resulting from the search.
So now I'm programmatically adding HyperLink elements to the page with these urls:
foreach (string url in urls) {
HyperLink link = new HyperLink();
link.Text = url;
link.NavigateUrl = url;
Page.Controls.Add(link);
}
The problem is that requests to these documents has to be authorized with the same basic http authentication and the same user:pass as when querying the REST service, and since I'm just creating links for the user to click, and not creating any HttpWebRequest objects, I don't know how to authenticate such a request resulting from a user clicking a link.
Any pointers to how I can accomplish this is very much appreciated. Thanks in advance!
You probably want to do the request server-side, as I think you're already doing, and then show the results embedded in your own pages, or just stream the result directly back to the users.
It's a bit unclear what it is you need (what are the links, what do you show the users, etc.), so this is the best suggesting I can do based on the info you give.
Update:
I would create a HttpHandler (an .ashx file in an ASP.NET project), and link to that, with arguments so you can make the request to the REST service and get the correct file, then stream the data directly back to the visitor. Here's a simple example:
public class DocumentHandler : IHttpHandler {
public Boolean IsReusable {
get { return true; }
}
public void ProcessRequest(HttpContext context) {
// TODO: Get URL of the document somehow for the REST request
// context.Request
// TODO: Make request to REST service
// Some pseudo-code for you:
context.Response.ContentType = "application/pdf";
Byte[] buffer = new WebClient().DownloadData(url);
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.End();
}
}
I hope you can fill in the blanks yourself.

HttpWebRequest not being received correctly in MVC ASP.NET

This is me publicly documenting my mistake so that if I or anyone does it again, they don't have to spend 3 hours tearing their hair out trying to fix such a simple thing.
Context
I was sending an HttpRequest from one C# MVC ASP.NET application to another.
The applications require an HTTPS connection, and we are using URLRewrite to redirect an HTTP request to an HTTPS url.
One application was sending a POST request with some JSON data in the body, pretty standard stuff. The other application was set up to receive this data with an MVC controller class (CollectionAction and Insert methods for GET and POST respectively).
Symptoms of the problem
The receiving application was running the GET method (CollectionAction) instead of the POST action (ItemAction). The reason for this was that the request coming in to the application was in fact a GET request, and to top it off the JSON data was missing too.
I sent the header "x-http-method" to override the request method from GET to POST (I was already setting the request httpmethod to POST but this was being ignored). This worked but still I had no data being sent.
So now I am stuck pulling my hair out, because I can see a POST request with content-length and data being sent out and I have a GET request with no data or content-length coming in (but the headers were preserved)
Turns out I was using UriBuilder to take a base URL and apply a resource path to it. For example I would have "google.com" in my web.config and then the UriBuilder would take a resource like Pages and construct the url "google.com/Pages". Unfortunately, I was not initializing the UriBuilder with the base URL, and instead was using a second UriBuilder to extract the host and add that to the path like so:
public Uri GetResourceUri(string resourceName)
{
var domain = new UriBuilder(GetBaseUrl());
var uribuilder = new UriBuilder()
{
Path = domain.Path.TrimEnd('/') + "/" + resourceName.TrimStart('/'),
Host = domain.Host
};
var resourceUri = uribuilder.Uri;
return resourceUri;
}
The problem with this code is that the scheme is ignored (HTTP:// vs HTTPS://) and it defaults to HTTP. So my client was sending out the request to an HTTP url instead of the required HTTPS url. This is the interesting part, URLRewrite was kicking in and saying that we needed to go to an HTTPS url instead so it redirected us there. But in doing so, it ignored the Http-Method and the POST data, which just got set to defaults GET and null. This is what the 2nd application could see at the receiving end.
So the function had to be rewritten to this which fixed the problem:
public Uri GetResourceUri(string resourceName)
{
var baseUrl = GetBaseUrl();
var domain = new UriBuilder(baseUrl);
var uribuilder = new UriBuilder(baseUrl)
{
Path = domain.Path.TrimEnd('/') + "/" + resourceName.TrimStart('/'),
};
var resourceUri = uribuilder.Uri;
return resourceUri;
}

Categories

Resources