Async Download file - c#

I plan to write a function DownloadData return a byte array, another client will call it to get byte array. My point is I don't want client app is waiting file is download, so I need it download in async mode. But I so confuse how to do that.
This is my function:
public byte[] DownloadData(string serverUrlAddress, string path)
{
if(string.IsNullOrWhiteSpace(serverUrlAddress) || string.IsNullOrWhiteSpace(path))
return null;
// Create a new WebClient instance
WebClient client = new WebClient();
// Concatenate the domain with the Web resource filename.
string url = string.Concat(serverUrlAddress, "/", path);
if (url.StartsWith("http://") == false)
url = "http://" + url;
byte[] data = null;
client.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e)
{
data = e.Result;
};
while (client.IsBusy) { }
return data;
}

I wrote a method that does just that.
public async Task<byte[]> DownloadData(string url)
{
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
HttpWebRequest request = WebRequest.CreateHttp(url);
using (HttpWebResponse response = (HttpWebResponse)(await request.GetResponseAsync()))
using (Stream stream = response.GetResponseStream())
using (MemoryStream ms = new MemoryStream())
{
await stream.CopyToAsync(ms);
tcs.SetResult(ms.ToArray());
return await tcs.Task;
}
}

I know why I lost byte. On API, I return byte array, but I use HttpClient to get data. I change to HttpResponseMessage as return and accept type on both.

Related

How to download file from API using Post Method

I have an API using POST Method.From this API I can download the file via Postmen tool.But I would like to know how to download file from C# Code.I have tried below code but POST Method is not allowed to download the file.
Code:-
using (var client = new WebClient())
{
client.Headers.Add("X-Cleartax-Auth-Token", ConfigurationManager.AppSettings["auth-token"]);
client.Headers[HttpRequestHeader.ContentType] = "application/json";
string url = ConfigurationManager.AppSettings["host"] + ConfigurationManager.AppSettings["taxable_entities"] + "/ewaybill/download?print_type=detailed";
TransId Id = new TransId()
{
id = TblHeader.Rows[0]["id"].ToString()
};
List<string> ids = new List<string>();
ids.Add(TblHeader.Rows[0]["id"].ToString());
string DATA = JsonConvert.SerializeObject(ids, Newtonsoft.Json.Formatting.Indented);
string res = client.UploadString(url, "POST",DATA);
client.DownloadFile(url, ConfigurationManager.AppSettings["InvoicePath"].ToString() + CboGatePassNo.EditValue.ToString().Replace("/", "-") + ".pdf");
}
Postmen Tool:-
URL : https://ewbbackend-preprodpub-http.internal.cleartax.co/gst/v0.1/taxable_entities/1c74ddd2-6383-4f4b-a7a5-007ddd08f9ea/ewaybill/download?print_type=detailed
Header :-
Content-Type : application/json
X-Cleartax-Auth-Token :b1f57327-96db-4829-97cf-2f3a59a3a548
Body :-
[
"GLD24449"
]
using (WebClient client = new WebClient())
{
client.Headers.Add("X-Cleartax-Auth-Token", ConfigurationManager.AppSettings["auth-token"]);
client.Headers[HttpRequestHeader.ContentType] = "application/json";
string url = ConfigurationManager.AppSettings["host"] + ConfigurationManager.AppSettings["taxable_entities"] + "/ewaybill/download?print_type=detailed";
client.Encoding = Encoding.UTF8;
//var data = "[\"GLD24449\"]";
var data = UTF8Encoding.UTF8.GetBytes(TblHeader.Rows[0]["id"].ToString());
byte[] r = client.UploadData(url, data);
using (var stream = System.IO.File.Create("FilePath"))
{
stream.Write(r,0,r.length);
}
}
Try this. Remember to change the filepath. Since the data you posted is not valid
json. So, I decide to post data this way.
I think it's straight forward, but instead of using WebClient, you can use HttpClient, it's better.
here is the answer HTTP client for downloading -> Download file with WebClient or HttpClient?
comparison between the HTTP client and web client-> Deciding between HttpClient and WebClient
Example Using WebClient
public static void Main(string[] args)
{
string path = #"download.pdf";
// Delete the file if it exists.
if (File.Exists(path))
{
File.Delete(path);
}
var uri = new Uri("https://ewbbackend-preprodpub-http.internal.cleartax.co/gst/v0.1/taxable_entities/1c74ddd2-6383-4f4b-a7a5-007ddd08f9ea/ewaybill/download?print_type=detailed");
WebClient client = new WebClient();
client.Headers[HttpRequestHeader.ContentType] = "application/json";
client.Headers.Add("X-Cleartax-Auth-Token", "b1f57327-96db-4829-97cf-2f3a59a3a548");
client.Encoding = Encoding.UTF8;
var data = UTF8Encoding.UTF8.GetBytes("[\"GLD24449\"]");
byte[] r = client.UploadData(uri, data);
using (var stream = System.IO.File.Create(path))
{
stream.Write(r, 0, r.Length);
}
}
Here is the sample code, don't forget to change the path.
public class Program
{
public static async Task Main(string[] args)
{
string path = #"download.pdf";
// Delete the file if it exists.
if (File.Exists(path))
{
File.Delete(path);
}
var uri = new Uri("https://ewbbackend-preprodpub-http.internal.cleartax.co/gst/v0.1/taxable_entities/1c74ddd2-6383-4f4b-a7a5-007ddd08f9ea/ewaybill/download?print_type=detailed");
HttpClient client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = new StringContent("[\"GLD24449\"]", Encoding.UTF8, "application/json")
};
request.Headers.Add("X-Cleartax-Auth-Token", "b1f57327-96db-4829-97cf-2f3a59a3a548");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using (FileStream fs = File.Create(path))
{
await response.Content.CopyToAsync(fs);
}
}
else
{
}
}

how to call web api to download document to website directory using webclient

I am struggling with being able to create a file with its data based on the byte array returned from the WebAPI. The following is my code for making the call to the web api
using (var http = new WebClient())
{
string url = string.Format("{0}api/FileUpload/FileServe?FileID=" + fileID, webApiUrl);
http.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
http.Headers[HttpRequestHeader.Authorization] = "Bearer " + authCookie.Value;
http.DownloadDataCompleted += Http_DownloadDataCompleted;
byte[] json = await http.DownloadDataTaskAsync(url);
}
The api code is
[HttpGet]
[Route("FileServe")]
[Authorize(Roles = "Admin,SuperAdmin,Contractor")]
public async Task<HttpResponseMessage> GetFile(int FileID)
{
using (var repo = new MBHDocRepository())
{
var file = await repo.GetSpecificFile(FileID);
if (file == null)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
var stream = File.Open(file.PathLocator, FileMode.Open);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(file.FileType);
return response;
}
}
I receive a byte array as a response however am unable to create the corresponding file from that byte array. I have no idea how to convert the byte array into the relevant file type (such as jpg, or pdf based on file type in the web api). any help will be appreciated.
Alright so there are a few ways of solving your problem firstly, on the server side of things you can either simply send the content type and leave it at that or you can also send the complete filename which helps you even further.
I have removed the code that is specific to your stuff with basic test code, please just ignore that stuff and use it in terms of your code.
Some design notes here:
[HttpGet]
[Route("FileServe")]
[Authorize(Roles = "Admin,SuperAdmin,Contractor")]
public async Task<HttpResponseMessage> GetFileAsync(int FileID) //<-- If your method returns Task have it be named with Async in it
{
using (var repo = new MBHDocRepository())
{
var file = await repo.GetSpecificFile(FileID);
if (file == null)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
var stream = File.Open(file.PathLocator, FileMode.Open);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(file.FileType);
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { FileName=Path.GetFileName(file.PathLocator)};
return response;
}
}
Your client side code has two options here:
static void Main(string[] args)
{
using (var http = new WebClient())
{
string url = string.Format("{0}api/FileUpload/FileServe?FileID={1}",webApiUrl, fileId);
http.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
http.Headers[HttpRequestHeader.Authorization] = "Bearer " + authCookie.Value;
var response = http.OpenRead(url);
var fs = new FileStream(String.Format(#"C:\Users\Bailey Miller\Downloads\{0}", GetName(http.ResponseHeaders)), FileMode.Create);
response.CopyTo(fs); <-- how to move the stream to the actual file, this is not perfect and there are a lot of better examples
fs.Flush();
fs.Close();
}
}
private static object GetName(WebHeaderCollection responseHeaders)
{
var c_type = responseHeaders.GetValues("Content-Type"); //<-- do a switch on this and return a really weird file name with the correct extension for the mime type.
var cd = responseHeaders.GetValues("Content-Disposition")[0].Replace("\"", ""); <-- this gets the attachment type and filename param, also removes illegal character " from filename if present
return cd.Substring(cd.IndexOf("=")+1); <-- extracts the file name
}

Lost byte when download file

I have three applications.
First: IIS
Second: Service (ASP.NET MVC)
Third: Client(Winform)
Files are store on IIS. Service public an api to download file as byte array base on URL. Client call api of Service and store file by extension.
After Client call Service, I check on Service, it return 15500 bytes. But I catch on Client, it is 13 bytes.
Below is the code on Service:
[HttpGet]
public byte[] DownloadData(string serverUrlAddress, string path)
{
if (string.IsNullOrWhiteSpace(serverUrlAddress) || string.IsNullOrWhiteSpace(path))
return null;
// Create a new WebClient instance
using (WebClient client = new WebClient())
{
// Concatenate the domain with the Web resource filename.
string url = string.Concat(serverUrlAddress, "/", path);
if (url.StartsWith("http://") == false)
url = "http://" + url;
byte[] data = client.DownloadData(url);
return data;
}
}
Below is the code on Client:
static void Main(string[] args)
{
byte[] data = GetData();
File.WriteAllBytes(#"E:\a.pdf", data);
}
public static byte[] GetData()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:54220/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("API/File/DownloadData?serverUrlAddress=www.x.com&path=Data/Folder/file.pdf").Result;
if (response.IsSuccessStatusCode)
{
var yourcustomobjects = response.Content.ReadAsByteArrayAsync().Result;
return yourcustomobjects;
}
else
{
return null;
}
}
}
When returning CLR types Web API attempts to serialize the object based on either Xml or Json serializers. Neither of these are what you want. You want to return the raw stream of bytes. Try this.
[HttpGet]
public HttpResponseMessage DownloadData(string serverUrlAddress, string path)
{
if (string.IsNullOrWhiteSpace(serverUrlAddress) || string.IsNullOrWhiteSpace(path))
return null;
// Create a new WebClient instance
using (WebClient client = new WebClient())
{
// Concatenate the domain with the Web resource filename.
string url = string.Concat(serverUrlAddress, "/", path);
if (url.StartsWith("http://") == false)
url = "http://" + url;
byte[] data = client.DownloadData(url);
return new HttpResponseMessage() { Content = new StreamContent(data) };
}
}
By returning a HttpResponseMessage you have more control over exactly how Web API returns the response. By default StreamContent will set the Content-Type header to be application/octet-stream. You may want to change that to 'application/pdf' if you are always returning PDF files.

Image upload from WP7 to Web Api

I have seen few other examples online doing the same, but I am not sure why its not working for me.
I have created a simple windows phone 7 app, which uses PhotoChooserTask.
It sends image to the server using Web Api.
Here is the code in windows phone project:
void selectphoto_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
var image = new Image();
image.Source = new BitmapImage(new Uri(e.OriginalFileName));
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:59551/api/controllername");
request.Method = "POST";
request.ContentType = "multipart/form-data";
//private method to convert bitmap image to byte
byte[] str = BitmapToByte(image);
// Getting the request stream.
request.BeginGetRequestStream
(result =>
{
// Sending the request.
using (var requestStream = request.EndGetRequestStream(result))
{
using (StreamWriter writer = new StreamWriter(requestStream))
{
writer.Write(str);
writer.Flush();
}
}
// Getting the response.
request.BeginGetResponse(responseResult =>
{
var webResponse = request.EndGetResponse(responseResult);
using (var responseStream = webResponse.GetResponseStream())
{
using (var streamReader = new StreamReader(responseStream))
{
string srresult = streamReader.ReadToEnd();
}
}
}, null);
}, null);
}
On the Web API I got the following code for the POST method:
public Task<HttpResponseMessage> Post()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Image img = Image.FromFile(file.LocalFileName);
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
}
However I am not sure why IsMimeMultipartContent is returning false always. Even if I bypass this check, no file is saved in the App_Data folder.
Can anyone please help. Thanks.
EDITED
Based on Darrel's response I have modified the POST method in ApiController. But I still do not get any data. A blank image is created on the server. Here is my code:
public HttpResponseMessage Post()
{
var task = Request.Content.ReadAsStreamAsync();
task.Wait();
Stream requestStream = task.Result;
string root = HttpContext.Current.Server.MapPath("~/App_Data");
root = System.IO.Path.Combine(root, "xyz.jpg");
try
{
FileStream fs = System.IO.File.OpenWrite(root);
requestStream.CopyTo(fs);
fs.Close();
}
catch (Exception)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError));
}
HttpResponseMessage response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.Created;
return response;
}
You are not sending a representation that is multipart/form. You are just sending a stream of bytes which is application/octet-stream. Just use Request.Content.ReadAsStreamAsync() on the server and copy the stream to a file.

Download an image from internet an convert it to byte[] in WinRT

I'm trying to download an image from the internet, through the URL, to my Windows8-app and convert it to a byte[]. (BitmapImage isn't serializable)
Unfortunatly when I try to process this code, it crashes on the bytearray initialization since the Stream isn't seekable.
Is there ANY way to accomplish this? I've red that there isn't a stream yet that is seekable in WinRT...
private async Task<byte[]> DownloadImageFromWebsite(string url)
{
//BitmapImage result = null;
byte[] result = null;
try
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
WebResponse response = await request.GetResponseAsync();
Stream imageStream = response.GetResponseStream();
result = new byte[imageStream.Length];
await imageStream.ReadAsync(result, 0, (int)imageStream.Length);
response.Dispose();
}
catch (Exception ex)
{
return null;
}
return result;
}
Your problem is this line:
result = new byte[imageStream.Length];
For an HTTP response stream, you don't know how big it will be until it's read.
If the server sends a Content-Length header, then you should be able to read it and size your array using it. However, you'll still have to fall back to reading the entire stream (of unknown size) into memory, as such:
private async Task<byte[]> DownloadImageFromWebsiteAsync(string url)
{
try
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
using (WebResponse response = await request.GetResponseAsync())
using (var result = new MemoryStream())
{
await imageStream.CopyToAsync(result);
return result.ToArray();
}
}
catch (WebException ex)
{
return null;
}
}
P.S. I recommend you use HttpClient instead of HttpWebRequest.

Categories