As title says, can find plenty of resources on posting to an endpoint (from JS, for example), but how do I construct a HttpRequest (in C# .Net core) with files?
What I've Tried
var myRequest = new DefaultHttpContext().Request;
myRequest.Form.Files // GET only!
I can't seem to set the Files collection (IFormFileCollection), I can only get it.
Further Context
This is to integration test an endpoint. Except I have to hit the method, not reach it via URL (for one reason or another). The method accepts a HttpRequest and parses out the Form.Files collection. Hence I need to add some files for my assertions.
Did you already try something like this:
using (var stream = new MemoryStream(fileBytes))
{
var bla = new DefaultHttpContext().Request;
bla.Form.Files.Append(new Microsoft.AspNetCore.Http.Internal.FormFile(stream, 0, stream.Length, "file.txt", "file.txt"));
}
you can achieve this by take advantage of the following Method
public async Task<string> UploadFile(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
{
throw new ArgumentNullException(nameof(filePath));
}
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"File [{filePath}] not found.");
}
using var form = new MultipartFormDataContent();
using var fileContent = new ByteArrayContent(await File.ReadAllBytesAsync(filePath));
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
form.Add(fileContent, "file", Path.GetFileName(filePath));
form.Add(new StringContent("789"), "userId");
form.Add(new StringContent("some comments"), "comment");
form.Add(new StringContent("true"), "isPrimary");
var response = await _httpClient.PostAsync($"{_url}/api/files", form);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<FileUploadResult>(responseContent);
return result.Guid;
}
this method was snipped from a good blog: https://codeburst.io/upload-download-files-using-httpclient-in-c-f29051dea40c
It would look something like this:
var context = new DefaultHttpContext();
var formFiles = new FormFileCollection();
var formValues = new Dictionary<string, StringValues>() { };
var formCollection = new FormCollection(formValues, formFiles);
context.Features.Set<IFormFeature>(new FormFeature(formCollection));
Fill in the formFiles and formValues variables based on what you're trying to test. The key here is setting the feature to your custom FormFeature. That will cleanly plug into the default behavior of .Form and ReadFormAsync
PS: This is for unit testing assuming you're not trying to send a real file to the server side, otherwise mustafa hassan's answer works
Related
I'm using the lastest Azure.ResourceManager SDK for .NET to list all resources in a subscription, this works fine:
var armClient = new ArmClient(new DefaultAzureCredential());
var subscription = await armClient.GetDefaultSubscriptionAsync();
await foreach(var resource in subscription.GetGenericResourcesAsync())
{
_logger.LogInformation("Got resource {id}", resource.Id);
}
However, I would like to get access to the raw HTTP response. This article explains how to do this for other, non-pageable responses. So I'm looking for a way to do this in my case, too. Something like GetRawResponse() - but I don't have a response object.
Please try something like the following:
var credential = new DefaultAzureCredential();
var armClient = new ArmClient(credential);
var subscription = await armClient.GetDefaultSubscriptionAsync();
var allResources = subscription.GetGenericResourcesAsync();
await foreach(Page<GenericResource> page in allResources.AsPages())
{
Response http = page.GetRawResponse();
Stream contentStream = http.ContentStream;
// Rewind the stream
contentStream.Position = 0;
using (StreamReader reader = new StreamReader(contentStream))
{
Console.WriteLine(reader.ReadToEnd());
}
}
For more details, please see this link: https://learn.microsoft.com/en-us/dotnet/azure/sdk/pagination
I want to download data of this website into a json file but as I am quite new to coding with C# I cant manage to get the data. I want to get Data of https://discosweb.esoc.esa.int/api/objects the authorization via token works but I dont know how I can send a request so the server gives me a json back and I cant find a solution online. I cant give you a screenshot of the API because you have to be logged in to see it. Plz ask me for detailed information if you can help me. Thank you realy for trying.
The code I want to run is here.
class Program
{
static HttpClient client = new HttpClient();
static void Main(string[] args)
{
client.BaseAddress = new Uri("https://discosweb.esoc.esa.int");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.api+json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("my_token");
var httpRequest = (HttpWebRequest)WebRequest.Create(client.BaseAddress);
var httpResponse = (HttpWebResponse)httpRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var streamReaderResult = streamReader.ReadToEnd();
}
Console.WriteLine("Status https://discosweb.esoc.esa.int : " + httpResponse.StatusCode);
}
}
Try this
var url = "https://discosweb.esoc.esa.int/api/objects";
var httpRequest = (HttpWebRequest)WebRequest.Create(url);
httpRequest.Method = "POST";
httpRequest.Headers["Authorization"] = "Basic XXXx";
httpRequest.ContentType = "";
httpRequest.Headers["Content-Length"] = "0";
var httpResponse = (HttpWebResponse)httpRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
Console.WriteLine(httpResponse.StatusCode);
Where XXXx is user:password in base64.
Here is a basic implementation for making that API call to get the JSON result. You will need to parse that JSON into something other than a string but I'll assume you can handle that part.
This uses System.Net.HttpClient which is the modern HTTP api provided by .NET. Its operations are async so hopefully your code is or can be written to properly await async operations.
//Someplace convenient, create a shared HttpClient to avoid
//creating and disposing for each request.
HttpClient client = new HttpClient();
string data = await GetObjects(client);
//Example implementation
public async Task<string> GetObjects(HttpClient client)
{
string url = "https://discosweb.esoc.esa.int/api/objects";
using (HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, url))
{
msg.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "your personal access token here");
using (var result = await client.SendAsync(msg))
{
string content = await result.Content.ReadAsStringAsync();
return content;
}
}
}
While I may be a month late, I've actually developed an SDK for this particular API.
So, if you use this SDK it's pretty simple to do what you want. You can essentially forget about handling anything HTTP related, my SDK abstracts all of that away.
For example, to fetch Sputnik's data (which has an ID of 1) you'd run.
HttpClient innerClient = new();
innerClient.BaseAddress = "https://discosweb.esoc.esa.int/api/"
innerClient.DefaultRequestHeaders.Authorization = new("bearer", yourApiKey);
DiscosClient client = new();
DiscosObject sputnik = await client.GetSingle<DiscosObject>("1");
If you're using ASP.NET, there's a set of DI extensions that can actually set it all up for you, so you can skip the first three lines.
If you do choose to use it, please let me know, as it would be nice knowing my SDK is getting some use. If you have any issues, please just reach out through the GitHub issues page and I'll try to help!
Existed asp.net core Api is look like below
public async Task<IActionResult> UploadAsync()
{
IFormFile file = null;
var files = Request.Form.Files;
if (files.Count > 0)
{
file = Request.Form.Files[0];
var fileText = new StringBuilder();
using (var reader = new StreamReader(file.OpenReadStream()))
{
while (reader.Peek() >= 0)
fileText.AppendLine(reader.ReadLine());
}
int stagingDetailId = await _stagingMarketProvider.GetV1StagingStatusDetailId();
var result = await SaveStagingMarketsAsync(_fileProvider.ReadImportedMarkets(fileText.ToString()));
return Ok(result);
}
return Ok();
}
Now to consume that api from another asp.net core webapi, I have to pass those files through Request object only, I can't change any existed Api code because of business.
Solution 1: Applicable if you want your client to get redirected to other API
Assuming the API caller understands HTTP 302 and can act accordingly, the 302 redirect should help you.
public IActionResult Post()
{
return Redirect("http://file-handler-api/action");
}
From documentation, Redirect method returns 302 or 301 response to client.
Solution 2: C# Code To Post a File Using HttpClient
Below c# code is from this blog post. This is simple code which creates HttpClient object and tries to send the file to a web API.
As you are doing this from one API to another, you will have to save file first at temporary location. That temporary location will be parameter to this method.
Also, After upload you may want to delete the file if it is not required. This private method you can call after file upload to your first API is complete.
private async Task<string> UploadFile(string filePath)
{
_logger.LogInformation($"Uploading a text file [{filePath}].");
if (string.IsNullOrWhiteSpace(filePath))
{
throw new ArgumentNullException(nameof(filePath));
}
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"File [{filePath}] not found.");
}
using var form = new MultipartFormDataContent();
using var fileContent = new ByteArrayContent(await File.ReadAllBytesAsync(filePath));
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
form.Add(fileContent, "file", Path.GetFileName(filePath));
form.Add(new StringContent("789"), "userId");
form.Add(new StringContent("some comments"), "comment");
form.Add(new StringContent("true"), "isPrimary");
var response = await _httpClient.PostAsync($"{_url}/api/files", form);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<FileUploadResult>(responseContent);
_logger.LogInformation("Uploading is complete.");
return result.Guid;
}
Hope this helps you.
Due to the fact that I need to convert this C# dll into a tlp file to be called from Visual Basic 6, I need avoid using external dependencies. I have used RestSharp to consume a WebAPI by doing the following (working):
using RestSharp;
using Newtonsoft.Json;
..
public string GetToken (string Key, string Password) {
var client = new RestClient (BaseUrl + "auth/GetToken");
var request = new RestRequest (Method.POST);
request.AddHeader ("cache-control", "no-cache");
request.AddHeader ("Content-Type", "application/json");
Dictionary<string, string> data = new Dictionary<string, string> {
{ "APIKey", Key },
{ "APIPassword", Password }
};
var dataJSON = JsonConvert.SerializeObject (data);
request.AddParameter ("undefined", dataJSON, ParameterType.RequestBody);
IRestResponse response = client.Execute (request);
GetTokenResults g = JsonConvert.DeserializeObject<GetTokenResults> (response.Content);
return g.Token;
}
where GetTokenResults was a struct that contained a declaration for the string Token. I want to achieve this same functionality without using RestSharp. Here is my unsuccessful attempt:
using System.Net.Http;
using System.Net.Http.Headers;
..
public async void GetToken (string Key, string Password) {
var client = new HttpClient ( );
client.DefaultRequestHeaders.Accept.Clear ( );
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue ("application/json"));
client.BaseAddress = new Uri (BaseUrl + "auth/GetToken");
Dictionary<string, string> data = new Dictionary<string, string> {
{ "APIKey", Key },
{ "APIPassword", Password }
};
var dataJSON = JsonConvert.SerializeObject (data);
var content = new StringContent (dataJSON, Encoding.UTF8, "application/json");
var response = await client.PostAsync ("", content);
}
I am unclear on how to achieve the same results (send API key and password, return token as string) using HttpClient as I did using RestSharp earlier. Anything that can point me in the right direction is greatly appreciated!
I think you got stung by this issue. In short, the URI in client.BaseAddress needs a slash at the end of it.
However, I wouldn't simply add it, I'd consider doing it a little different. Presumably your BaseUrl already has a trailing slash, given you're appending "auth/GetToken" to it. I'd do it this way:
client.BaseAddress = new Uri(BaseUrl);
...
var response = await client.PostAsync("auth/GetToken", content);
As you can see, HttpClient fits very cleanly with how your code is already set up, i.e. you have a "base" address with a trailing slash and you want to append to it for a specific call.
That should get you un-stuck to this point. The next thing you'll need to tackle is deserializing the JSON response so you can get the token out of it. It's similar to how you did it in RestSharp, except that response.Content is not a string in the HttpClient world, so you need one more step to get that:
var json = await response.Content.ReadAsStringAsync();
GetTokenResults g = JsonConvert.DeserializeObject<GetTokenResults>(json);
return g.Token;
Last thing you'll need to do to get this to compile is change the method signature to:
public async Task<string> GetTokenAsync
One final note: you are now in the async world, and that's a good thing, but you need to know how to use it correctly or you could end up with deadlocks and other mysterious bugs. In short, don't block on async code by calling .Result or .Wait() anywhere up the call stack. That's by far most common mistake people make. Use async/await all the way down.
I think you are missing first parameter in the method PostAsync i.e. requestUri=Client.BaseAddress (see my implementation below).
Try with this first, if did not work, read below. I have a little different implementation where I passed client.BaseAddress as first parameter and I am passing my content as ByteArrayContent. In my case I have to pass my content as "application/x-www-form-urlencoded" excerpt of my code:
var buffer = Encoding.UTF8.GetBytes(content);
var byteContent = new ByteArrayContent(buffer);
//as I can't send JSON, probably, you can skip as it's already JSON
byteContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
//requestUri=client.BaseAddress
await client.PostAsync(requestUri, byteContent).ConfigureAwait(false);
We have somewhat different need but I think you are pretty close. If it does not help, write me I will share my code. After reading the comment, I would like to share how I have made my HttpClient. The code is as it is:
using (var client = CreateMailClientForPOST($"{BaseUrl}/"))
{
//removed code, you can call above code as method like
var response= await client.DoThingAsAsync($"{client.BaseAddress}, content").ConfigureAwait(false);
}
protected HttpClient CreateMailClientForPOST(string resource)
{
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
{
handler.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
}
var client = new HttpClient(handler)
{
BaseAddress = new Uri($"https://api.address.com/rest/{resource}")
};
return client;
}
recently i discovered this amazing cms called Directus, where you can manage your database and Tables with web request and Json.
Everything worked fine creating,updating,reading...till i came to the point where i want to Create (Upload) a Image using WebRequest.
Im basicly reading a image as Base64 and writing the data along with the parameters in the Uri using a simple GET request exactly like described in API.
Regardless what i try and use the Images Never show up in my Files.
Am i doing something wrong or forgetting something?
Or does directus want something else from me?
My first try:
public static async void UploadUserImage() {
var uri = "http://IP/Directus/api/1/files?access_token=SecretApiKey";
var data = GetImageData();
var finalUri = $"{uri}&data={data}";
using (var client = new HttpClient()) {
var responseString = await client.GetStringAsync(finalUri);
Console.Write(responseString);
}
}
My Second try with Json:
public static async void UploadUserImage() {
var uri = "http://IP/Directus/api/1/files?access_token=SecretApiKey";
var data = GetImageData();
var finalUri = $"{uri}&data={data}";
var postModel = new PictureModel {
data = data,
title = "Test",
name = "test"
};
using (var client = new HttpClient())
{
// Serialize our concrete class into a JSON String
var content = JsonConvert.SerializeObject(postModel);
var contenta = new StringContent(content, Encoding.UTF8, "application/json");
var response = await client.PostAsync(finalUri, contenta);
var result = await response.Content.ReadAsByteArrayAsync();
Console.Write(System.Text.Encoding.UTF8.GetString(result));
}
}
The docs is incorrect it's actually a POST request. Thanks for pointing that out.
To upload a new file you need three provide three values:
{
"name": "image.png",
"type": "image/png",
"data": "base64content"
}
The data content has to be in this format data:<mime-type>;base64,<data-content> so it will look something like this: data:image/png;base64,ThisIsABase64Content
We are updating the docs and removing the data:image/png which is unnecessary.