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.
Related
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
{
}
}
I'm trying to post a file + some info to a WebApi I control. My problem is that I can't access the file on the WebAPI side, all other fields are OK.
Here is my Console Application code
using (HttpClient client = new HttpClient())
{
using (MultipartFormDataContent content = new MultipartFormDataContent())
{
string filename = "my_filename.png";
content.Add(new StringContent(DateTime.Now.ToString("yyyy-MM-dd")), "data");
byte[] file_bytes = webClient.DownloadData($"https://my_url/my_file.png");
content.Add( new ByteArrayContent(file_bytes), "file");
string requestUri = "http://localhost:51114/api/File";
HttpResponseMessage result = client.PostAsync(requestUri, content).Result;
Console.WriteLine("Upload result {0}", result.StatusCode);
}
}
Here is my WebAPI Code
[HttpPost]
public void Post(IFormFile file, [FromForm] DateTime data)
{
if (file == null || file.Length == 0)
{
Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
// Never reaches this point..... file is null
}
Any pointers on what I might be missing?
If i'm not mistaken, you can submit a file to a WebAPI endpoint sending it as FormData with a Content-Type : multipart/form-data, something like this.
[HttpPost]
[Route("..."]
public void ReceiveFile()
{
System.Web.HttpPostedFile file = HttpContext.Current.Request.Files["keyName"];
System.IO.MemoryStream mem = new System.IO.MemoryStream();
file.InputStream.CopyTo(mem);
byte[] data = mem.ToArray();
// you can replace the MemoryStream with file.saveAs("path") if you want.
}
You can grab out the content and convert it into a byte array in 2 lines of code, assuming you are only sending a single file (Note) its a good idea to use async for file upload so you don't consume as much cpu time:
var provider = await Request.Content.ReadAsMultipartAsync(new MultipartMemoryStreamProvider());
var file = provider.Contents.Single();
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
}
I am trying to upload a file to following the API information in this service. Easy Post API.
I am able to successfully send the first GET request with Digest authentication.
I'm getting a 403 - Unauthorized when trying to upload the file with 'PUT'.
This is the code I have. I am using a custom web client to set parameters in the web request.
public class CustomWebClient : WebClient
{
private BingMailConfigOptions ConfigOptions;
public CustomWebClient(BingMailConfigOptions configOptions) : base()
{
ConfigOptions = configOptions;
}
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest)base.GetWebRequest(address);
request.ServicePoint.Expect100Continue = false;
request.Method = "PUT";
request.Credentials = GetCredentialCache(address, ConfigOptions);
return request;
}
public static CredentialCache GetCredentialCache(Uri uri, BingMailConfigOptions options)
{
var credentialCache = new CredentialCache
{
{
new Uri(uri.GetLeftPart(UriPartial.Authority)),
"Digest",
new NetworkCredential(options.AuthUserName, options.AuthPassword, uri.GetLeftPart(UriPartial.Authority))
}
};
return credentialCache;
}
}
// in a separate class.
private void Upload(string sessionId, string filePath)
{
_log.Trace("Trying to upload the file: " + filePath);
var file = new FileInfo(filePath);
if (file.Exists)
{
using (var uploader = new CustomWebClient(ConfigOptions))
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
Uri uri = new Uri("https://bingmail.com.au/" + "direct_upload/{0}/{1}"(sessionId, HttpUtility.UrlEncode(file.Name)));
uploader.UploadFile(uri, "PUT", filePath);
}
}
else
{
throw new Exception("File Not found");
}
}
Can you please tell me what I'm doing wrong or point me in the right direction?
Thanks
I finally figured out a solution. Hope it will help someone someday.
Complete solution except some easy-to-figure-out methods are posted in this gist. Bing-Mail Easy Post Api - Version 1.3
What i did was modified the DigestAuthFixer from https://stackoverflow.com/a/3117042/959245 to support any HTTP method.
Then used that to create the session, when we create the session using DigestAuthFixer it stores the Digest-Auth headers which i can reuse when uploading the files.
using (var client = new WebClient())
{
var uri = new Uri(_easypostHosts[2] + UploadUri.FormatWith(sessionId, HttpUtility.UrlEncode(fileName)));
// get the auth headers which are already stored when we create the session
var digestHeader = DigestAuthFixer.GetDigestHeader(uri.PathAndQuery, "PUT");
// add the auth header to our web client
client.Headers.Add("Authorization", digestHeader);
// trying to use the UploadFile() method doesn't work in this case. so we get the bytes and upload data directly
byte[] fileBytes = File.ReadAllBytes(filePath);
// as a PUT request
var result = client.UploadData(uri, "PUT", fileBytes);
// result is also a byte[].
content = result.Length.ToString();
}
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.