After struggling for a long time i finally got a code for downloading a file with using authentication and sending a header with a range in a windows store application , now the next level is to get a progress bar of the download , so any idea of the classes and methods that should be used for getting the download operation?
This is my code:
var httpClientHandler = new HttpClientHandler();
httpClientHandler.Credentials = new System.Net.NetworkCredential("", "");
var client = new HttpClient(httpClientHandler);
System.Net.Http.HttpRequestMessage request = new System.Net.Http.HttpRequestMessage(HttpMethod.Post, new Uri(""));
request.Headers.Range = new RangeHeaderValue(0,null);
HttpResponseMessage response = await client.SendAsync(request);
Stream stream = await response.Content.ReadAsStreamAsync();
StorageFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.GenerateUniqueName);
await Windows.Storage.FileIO.WriteBytesAsync(file, ReadStream(stream));
This task will be easier if you switch from System.Net.Http.HttpClient to Windows.Web.Http.HttpClient.
using Windows.Web.Http;
using Windows.Web.Http.Filters;
private async void Foo(StorageFolder folder, string fileName)
{
Uri uri = new Uri("http://localhost");
var filter = new HttpBaseProtocolFilter();
filter.ServerCredential =
new Windows.Security.Credentials.PasswordCredential(uri.ToString(), "foo", "bar");
var client = new HttpClient(filter);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Headers.Add("Range", "bytes=0-");
// Hook up progress handler.
Progress<HttpProgress> progressCallback = new Progress<HttpProgress>(OnSendRequestProgress);
var tokenSource = new CancellationTokenSource();
HttpResponseMessage response = await client.SendRequestAsync(request).AsTask(tokenSource.Token, progressCallback);
IInputStream inputStream = await response.Content.ReadAsInputStreamAsync();
StorageFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.GenerateUniqueName);
// Copy from stream to stream.
IOutputStream outputStream = await file.OpenAsync(FileAccessMode.ReadWrite);
await RandomAccessStream.CopyAndCloseAsync(inputStream, outputStream);
}
private void OnSendRequestProgress(HttpProgress obj)
{
Debug.WriteLine(obj);
}
Otherwise, take a look at this other answer.
Related
I'm fairly new to use HTTPClient and sending REST requests to APIs, I'm currently practicing multipart upload using this Google Drive API endpoint:
POST https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart
There's an instruction that states there to split the request body into two parts, I tried to recreate this but was unable to do so.
https://developers.google.com/drive/api/guides/manage-uploads#multipart
Here's my current code:
async void UploadFile(StorageFile fileName)
{
using (HttpClient client = new HttpClient())
{
// Opens files and convert it to stream
var resultStream = await fileName.OpenReadAsync();
var fileStreamContent = new StreamContent(resultStream.AsStream());
// Create file MetaData
var fileMetaData = JsonConvert.SerializeObject(
new { name = fileName.Name, mimetype = fileName.ContentType });
// Create POST request
var requestMessage = new HttpRequestMessage(HttpMethod.Post, uploadFileEndpoint);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(tokenType, accessToken);
// Add request body
requestMessage.Content = new StringContent(fileMetaData, Encoding.UTF8, "application/json");
requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("multipart/related");
var response = await client.SendAsync(requestMessage);
string responseString = await response.Content.ReadAsStringAsync();
output(responseString);
}
}
Any help would be greatly appreciated, thank you!
According to the documentation on Perform a multipart upload (HTTP tab), you need the MultipartFormDataContent as suggested by #Jeremy.
There are a few things needed to perform/migrate:
Add AuthenticationHeaderValue into client.DefaultRequestHeaders.Authorization.
Create a StreamContent instance, fileStreamContent (which you have done) and specify its Headers.ContentType.
Create a StringContent instance, stringContent (which you have done).
Append both StreamContent and StringContent into the MultipartFormDataContent instance, formData.
Specify the formData's Headers.ContentType as requested in API docs.
Post the formData with await client.PostAsync(/* API Url */, formData);
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(tokenType, accessToken);
// Opens files and convert it to stream
var resultStream = await fileName.OpenReadAsync();
var fileStreamContent = new StreamContent(resultStream.AsStream());
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue(fileName.ContentType);
// Create file MetaData
var fileMetaData = JsonConvert.SerializeObject(new { name = fileName.Name, mimetype = fileName.ContentType });
var stringContent = new StringContent(fileMetaData, Encoding.UTF8, "application/json");
// Create POST request
MultipartFormDataContent formData = new MultipartFormDataContent();
formData.Add(stringContent, "metadata");
formData.Add(fileStreamContent, "media");
formData.Headers.ContentType = new MediaTypeHeaderValue("multipart/related");
var response = await client.PostAsync(uploadFileEndpoint, formData);
string responseString = await response.Content.ReadAsStringAsync();
}
I am in the process of creating a proxy server that makes a request to a PDF Blob link then takes the request to setup its HttpResponse Header which we sent to the client. This diagram should explain
As of now, I am successful at making the request to get the pdf content however I am not sure how to send that back to the user. I have followed other Stackoverflow post such as this one : https://stackoverflow.com/a/43232581/10541061
I turn the response message in step 3 of the diagram to a stream and sent it back in the new HttpResponseMessage content.But instead of PDF content , I get a json file
What I want to return to the client
What I am actually returning to the client
Here is the code I am using to create this proxy endpoint
[AllowAnonymous]
[HttpGet("openPDF")]
public async Task<HttpResponseMessage> OpenPDF([FromQuery] string url)
{
var _httpClient = _httpClientFactory.CreateClient();
var response = await _httpClient.GetAsync(url);
var stream = await response.Content.ReadAsStreamAsync();
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.OK);
message.Content = new StreamContent(stream);
message.Content.Headers.ContentLength = stream.Length;
message.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return message;
}
EDIT
Ok so this actually sends back the PDF when I write the proxy like this
[AllowAnonymous]
[HttpGet("openPDF")]
public async Task<FileStreamResult> OpenPDF([FromQuery] string url)
{
var fileStream = new MemoryStream();
var _httpClient = _httpClientFactory.CreateClient();
var file = await _httpClient.GetStreamAsync(url).ConfigureAwait(false);
await file.CopyToAsync(fileStream);
fileStream.Position = 0;
return File(fileStream, "application/pdf", "filename.pdf");
}
The problem is I want to update the content-disposition to inline so I can force this to open in the browser instead of downloading.So I decided to take the filestream and injecting that in the httpResponseMessage.content instead but that still didn't work. It would continue to send me a json file
[AllowAnonymous]
[HttpGet("openPDF")]
public async Task<HttpResponseMessage> OpenPDF([FromQuery] string url)
{
var fileStream = new MemoryStream();
var _httpClient = _httpClientFactory.CreateClient();
var file = await _httpClient.GetStreamAsync(url).ConfigureAwait(false);
await file.CopyToAsync(fileStream);
fileStream.Position = 0;
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.OK);
message.Content = new StreamContent(fileStream);
message.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return message;
}
To be honest, I thought defining the content-type should suffice but guess not
This is pretty straight forward for .NET 6... suspect it should be roughly the same for .NET 4x... This uses the NuGet package Azure.Storage.Blobs
https://github.com/Azure/azure-sdk-for-net/blob/Azure.Storage.Blobs_12.13.1/sdk/storage/Azure.Storage.Blobs/README.md
[HttpGet("stream")]
public async Task GetBlobAsync()
{
var url = new Uri("https://path.to.blob.content/xxx");
var blobClient = new BlobClient(url);
Response.Headers.Add("Content-Type", "application/pdf");
Response.Headers.Add("Content-Disposition", #"attachment;filename=""intended file name.pdf""");
await blobClient.DownloadToAsync(Response.Body);
}
for .NET 4x.
try to add:
result.Content.Headers.ContentDisposition =
new ContentDispositionHeaderValue("inline")
{
FileName = "filename.pdf"
};
static async void DownloadData(TextBox textboxURL, TextBlock outputView)
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(textboxURL.Text);
client.Timeout = TimeSpan.FromMinutes(1);
var request = new HttpRequestMessage(HttpMethod.Get, textboxURL.Text);
/// Fixed thanks to: http://stackoverflow.com/questions/18720435/httpclient-buffer-size-limit-exceeded
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
/// Version = response.Version.ToString();
response.EnsureSuccessStatusCode();
// Result = await response.Content.ReadAsStringAsync();
// Task<Stream> inputStream = response.Content.ReadAsStreamAsync();
/// DUPE CODE: var sendTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
/// NEED TO READ UP ON THIS: response..Result.EnsureSuccessStatusCode();
var httpStream = await response.Content.ReadAsStreamAsync();
var picker = new FileSavePicker()
{
SuggestedStartLocation = PickerLocationId.Downloads,
SuggestedFileName = "DOWNLOADING.BIN"
};
picker.FileTypeChoices.Add("Any", new List<string>() { "." });
/// picker.FileTypeChoices.Add("Any", new List<string>() { "*" });
StorageFile storageFile = await picker.PickSaveFileAsync();
// Woohoo! Got it working using await, and removing the Task<> wrapper!
using (var reader = new StreamReader(httpStream))
{
Stream fileStream = await storageFile.OpenStreamForWriteAsync();
httpStream.CopyTo(fileStream);
fileStream.Flush();
}
}
}
catch (Exception ex)
{
outputView.Text = "Error, try again!";
var dlg = new Windows.UI.Popups.MessageDialog(ex.Message, "Error");
await dlg.ShowAsync();
}
}
You're using the synchronous Stream.CopyTo method here:
httpStream.CopyTo(fileStream);
I think you just want:
await httpStream.CopyToAsync(fileStream);
However, you should also remove the StreamReader part - you're not using the StreamReader, and it's possible that it will try to read some data to detect the encoding. However, you should use a using statement for the storage file. So basically, change this:
using (var reader = new StreamReader(httpStream))
{
Stream fileStream = await storageFile.OpenStreamForWriteAsync();
httpStream.CopyTo(fileStream);
fileStream.Flush();
}
to:
using (Stream fileStream = await storageFile.OpenStreamForWriteAsync())
{
await httpStream.CopyToAsync(fileStream);
}
Issue
I am trying to upload a photo to the Facebook API WITH a message.
Code Snippet - Upload
requestUri = "https://graph.facebook.com/v2.0/me/photos?access_token=MyAccessToken"
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "\"files\"",
FileName = "\"image.jpg\""
};
streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
var messageContent = new StringContent("message=HelloWorld");
var resultJson = webRequestClient.Post(requestUri, new MultipartFormDataContent()
{
messageContent,
streamContent,
});
Code - webRequestClient
public string Post(string uri, HttpContent postData)
{
return PostAsync(uri, postData).Result;
}
public async Task<string> PostAsync(string uri, HttpContent httpContent)
{
string resultStream;
using (var httpClient = new HttpClient())
{
var response = await httpClient.PostAsync(uri, httpContent);
response.EnsureSuccessStatusCode();
resultStream = await response.Content.ReadAsStringAsync();
}
return resultStream;
}
Notes
If I remove the "messageContent" : It uploads he picture
If I use MultipartContent : It uploads the picture but ignores my "message"
Don't bother for now why I don't do use the async functionality
When it fails I get "bad" requests
When I append the "message=helloworld" in the requestUri, it works, but that isn't the most flexible solution for in my architecture to deal with this issue.
Check this it will solve you problem, either you have to send the image via stream then you don't need to tell that the type is "image/jpeg" explicitly.
protected async void TakePictureAndUpload()
{
var ui = new CameraCaptureUI();
var file = await ui.CaptureFileAsync(CameraCaptureUIMode.Photo);
if (file != null)
{
byte[] myPicArray = await GetPhotoBytesAsync(file);
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://yourdomainname.com");
MultipartFormDataContent form = new MultipartFormDataContent();
HttpContent content = new ByteArrayContent(myPicArray);
form.Add(content, "media", "filename.jpg");
content = new StringContent("my-username");
form.Add(content, "username");
HttpResponseMessage response = await httpClient.PostAsync("directory/my-site.php", form);
}
}
public async Task<byte[]> GetPhotoBytesAsync(StorageFile file)
{
IRandomAccessStream fileStream = await file.OpenAsync(FileAccessMode.Read);
var reader = new Windows.Storage.Streams.DataReader(fileStream.GetInputStreamAt(0));
await reader.LoadAsync((uint)fileStream.Size);
byte[] pixels = new byte[fileStream.Size];
reader.ReadBytes(pixels);
return pixels;
}
I need create POST request from WinRT app,which should contain StorageFile.
I need to do this exactly in style like this : post request with file inside body.
Is it possible? I know about HttpClient.PostAsync(..), but I can't put StorageFile inside request body. I want to send mp3 file to Web Api
On server side I get file like this:
[System.Web.Http.HttpPost]
public HttpResponseMessage UploadRecord([FromUri]string filename)
{
HttpResponseMessage result = null;
var httpRequest = HttpContext.Current.Request;
if (httpRequest.Files.Count > 0)
{
foreach (string file in httpRequest.Files)
{
var postedFile = httpRequest.Files[file];
var filePath = HttpContext.Current.Server.MapPath("~/Audio/" + filename + ".mp3");
postedFile.SaveAs(filePath);
}
result = Request.CreateResponse(HttpStatusCode.Created);
}
else
{
result = Request.CreateResponse(HttpStatusCode.BadRequest);
}
return result;
}
You can send it as a byte[] using the ByteArrayContent class as a second parameter:
StroageFile file = // Get file here..
byte[] fileBytes = null;
using (IRandomAccessStreamWithContentType stream = await file.OpenReadAsync())
{
fileBytes = new byte[stream.Size];
using (DataReader reader = new DataReader(stream))
{
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(fileBytes);
}
}
var httpClient = new HttpClient();
var byteArrayContent = new ByteArrayContent(fileBytes);
await httpClient.PostAsync(address, fileBytes);
If you're uploading files of any appreciable size, then it's best to use the Background Transfer API so that the upload doesn't get paused if the app is suspended. Specifically see BackgroundUploader.CreateUpload which takes a StorageFile directly. Refer to the Background Transfer sample for both the client and server sides of this relationship, as the sample also includes a sample server.
To use less memory you can pipe the file stream to the HttpClient stream directly.
public async Task UploadBinaryAsync(Uri uri)
{
var openPicker = new FileOpenPicker();
StorageFile file = await openPicker.PickSingleFileAsync();
if (file == null)
return;
using (IRandomAccessStreamWithContentType fileStream = await file.OpenReadAsync())
using (var client = new HttpClient())
{
try
{
var content = new HttpStreamContent(fileStream);
content.Headers.ContentType =
new HttpMediaTypeHeaderValue("application/octet-stream");
HttpResponseMessage response = await client.PostAsync(uri, content);
_ = response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
// Handle exceptions appropriately
}
}
}