I am trying to use HttpClient with putasync to send file to server. The function looks like:
public async Task SendCsvFile(string path,string apiKey)
{
try
{
string clientKey = "";
LoggerService.Logger.CreateLog("Log");
LoggerService.Logger.Info("Start:SendCsvFile");
FileStream fileStream = null;
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
HttpClient httpClient = new HttpClient(clientHandler);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Add("x-api-key", clientKey);
string url = "https://test.csv";
var content = new MultipartFormDataContent();
var fileName = Path.GetFileName(path);
fileStream = File.OpenRead(path);
StreamContent streamContent = new StreamContent(fileStream);
content.Add(new StreamContent(fileStream), fileName, fileName);
var response = await httpClient.PutAsync(url, content);
if (response.IsSuccessStatusCode == true)
{
LoggerService.Logger.Info("File sent correctly.");
}
else
{
LoggerService.Logger.Error("Error during sending." + response.StatusCode + ";" + response.ReasonPhrase + ";");
}
fileStream.Close();
LoggerService.Logger.Info("End:SendCsvFile");
}
catch (Exception ex)
{
LoggerService.Logger.Error(ex.ToString());
//return 0;
}
//return 1;
}
File is send fine and it works however Content-disposition header is added to the file to the first line, and client doesn't want that. It's the first time I am doing anything with services and I read through a lot but still I don't know what can i change to not alter the content of csv file.
EDIT.
After I send the file header is added to the content so the file looks like that.
Screenshot from client server
All the data is fine, though the client server processes the data in a way that it should start from column names. So my question really is what can I can change to omit that first line and is it even possible. Maybe thats something obvious but i' m just a newbie in this stuff.
Changing to MultipartContent and Clearing Headers almost work but left me with boundary still visible in a file. Eventually I changed to RestSharp and adding content this way got rid of the problem.
public async Task SendCsvFile(string path, string apiKey)
{
try
{
string clientKey = "";
string url = "";
LoggerService.Logger.CreateLog("CreationAndDispatchStatesWithPrices");
LoggerService.Logger.Info("Start:SendCsvFile");
FileStream fileStream = null;
var fileName = Path.GetFileName(path);
fileStream = File.OpenRead(path);
var client = new RestClient(url);
// client.Timeout = -1;
var request = new RestRequest();
request.AddHeader("x-api-key", clientKey);
request.AddHeader("Content-Type", "text/csv");
request.AddParameter("text/csv", File.ReadAllBytes(path), ParameterType.RequestBody);
RestResponse response = client.Put(request);
if (response.IsSuccessful == true)
{
LoggerService.Logger.Info("File sent correctly.");
}
else
{
LoggerService.Logger.Error("Error sending file." + response.StatusCode + ";" + response.ErrorMessage + ";");
}
fileStream.Close();
LoggerService.Logger.Info("End:SendCsvFile");
}
catch (Exception ex)
{
LoggerService.Logger.Error(ex.ToString());
//return 0;
}
//return 1;
}
Related
I have quite simple system: ASP .NET Core server which is hosted on domain.ru. In API controller there I have 2 methods:
[HttpGet("{id}")]
public string Get(int id)
{
try
{
using (FileStream fstream = new FileStream(string.Format(#"{0}\data{1}.txt", _path, id.ToString()), FileMode.OpenOrCreate))
{
byte[] array = System.Text.Encoding.Default.GetBytes(id.ToString());
fstream.Write(array, 0, array.Length);
return "It's ok!";
}
}
catch
{
return "Something went wrong";
}
}
[HttpPost]
public string Post(string resolvedString)
{
try
{
using (FileStream fstream = new FileStream(string.Format(#"{0}\dataPost.txt", _path), FileMode.OpenOrCreate))
{
byte[] array = System.Text.Encoding.Default.GetBytes(resolvedString);
fstream.Write(array, 0, array.Length);
return "It's ok!";
}
}
catch
{
return "Something went wrong";
}
}
So basically both of them are just creating text files in the _path directory. The part that i can't understand is when I try to call Get method by url domain.ru/api/values/1 I can see the file which was created in _path directory and I have response "It's ok!". That's how I call Get:
var client = new HttpClient();
client.BaseAddress = new Uri(uri);
HttpResponseMessage response = await client.GetAsync("api/values/1");
string result = await response.Content.ReadAsStringAsync();
textBox1.Text = result.ToString();
But when I try the same with Post I get either Bad Request when I do it with C# or "Something went wrong" when I do it with Postman.
That's the way how I call Post
var client = new HttpClient();
client.BaseAddress = new Uri(uri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
MultipartFormDataContent content = new MultipartFormDataContent();
StringContent str = new StringContent("1");
content.Add(str);
HttpResponseMessage response = await client.PostAsync("api/values/", content);
string returnString = await response.Content.ReadAsStringAsync();
MessageBox.Show(returnString);
Here's what the request shows when I try to manually debug this
And the most fun part. When I try to do all the same actions when my server is hosted on IIS (localserver) it works just fine! I' really don't know what I am doing wrong. Please, help.
UPD. Thanks to Jonathan, I asked my hoster to disable ModSecurity in Plesk and the above code started to work after replacing [HttpPost] by [HttpPost("{resolvedString}")]. So far so good!
Then I tried to send a zip archive to the server. Here is the server's controller code:
[HttpPost]
public string ImportZip(IFormFile file)
{
DirectoryInfo dirInfo = new DirectoryInfo(_extractPath);
try
{
foreach (FileInfo myfile in dirInfo.GetFiles())
{
myfile.Delete();
}
string path = _path + "tmp.zip";
if (Request.HasFormContentType)
{
var form = Request.Form;
foreach (var formFile in form.Files)
{
using (var fileStream = new FileStream(path, FileMode.Create))
{
formFile.CopyTo(fileStream);
}
ZipFile.ExtractToDirectory(_path + "tmp.zip", _extractPath);
}
}
return "It's OK! At least we've entered the method.";
}
catch
{
return "Oh no no no...";
}
}
And that's how I call it from the client:
string filepath = _zipFile;
string filename = _fileName;
var client = new HttpClient();
client.BaseAddress = new Uri(uri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
MultipartFormDataContent content = new MultipartFormDataContent();
ByteArrayContent fileContent = new ByteArrayContent(File.ReadAllBytes(filepath));
content.Add(fileContent, "file", filename);
HttpResponseMessage response = await client.PostAsync("File/ImportZip/", content);
string result = await response.Content.ReadAsStringAsync();
textBox1.Text = result;
Once again, it works as it should when I run both server and client on my computer. I can see downloaded archive and extracted files in destination directories.
But when I upload my server to hosting and try to execute my query once again, I get the same error:
an example of an error
Well, seems like I found an answer myself. Will leave it here so it can help someone (maybe me in the future).
Code of the client's send method:
string uri = "https://example.com/controller/action/";
string zipFile = #"C:\Path\To\Your\File.txt";
string response;
using (WebClient client = new WebClient())
{
response = Encoding.Default.GetString(client.UploadFile(uri, zipFile));
}
MessageBox.Show(response);
Here we just composing a request and sending a file. The path and url are hardcoded for ex.
Code of the server's save method:
[HttpPost]
public string ImportZip(IFormFile file)
{
try
{
string path = _path + "tmp.zip";
if (Request.HasFormContentType)
{
var form = Request.Form;
foreach (var formFile in form.Files)
{
using (var fileStream = new FileStream(path, FileMode.Create))
{
formFile.CopyTo(fileStream);
}
}
return "Done";
}
return "Empty request";
}
catch
{
return "No access";
}
}
As long as I send only one file and also I know its extension and I want it to be called "tmp", I hardcode it's name and extension. You can take file's default name/extension to save it as is.
Then I save all the files in request into a chosen _path directory.
Basically, that's it.
My current code downloads a SharePoint file from an absolute URL of the file and writes to local.
I want to change it to use the folder URL instead and downloads file in the folder base on some filter.
Can it be done?
Below is my current code snippet:
string fullFilePath = DownloadSPFile("http://MySharePointSite.com/sites/Collection1/Folder1/File1.docx/");
public static string DownloadSPFile(string urlPath)
{
string serverTempdocPath = "";
try
{
var request = (HttpWebRequest)WebRequest.Create(urlPath);
var credentials = new NetworkCredential("username", "password", "domain");
request.Credentials = credentials;
request.Timeout = 20000;
request.AllowWriteStreamBuffering = false;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream stream = response.GetResponseStream())
{
serverTempdocPath = Path.Combine(AppConfig.EmailSaveFilePath + "_DOWNLOADED.eml");
using (FileStream fs = new FileStream(serverTempdocPath, FileMode.Create))
{
byte[] read = new byte[256];
int count = stream.Read(read, 0, read.Length);
while (count > 0)
{
fs.Write(read, 0, count);
count = stream.Read(read, 0, read.Length);
}
}
}
}
}
catch (Exception ex)
{
AppLogger.LogError(ex, "");
throw ex;
}
return serverTempDocPath;
}
If your sharepoint site enables sharepoint rest api, you can get the details very easy.
Get list of files
url: http://site url/_api/web/GetFolderByServerRelativeUrl('/Folder Name')/Files
method: GET
headers:
Authorization: "Bearer " + accessToken
accept: "application/json;odata=verbose" or "application/atom+xml"
and pass query for that
"?filter=$top=1&$orderby=Created desc"
More information
Has you try to format your urlPath ?
like:
string urlPath = "path/to/folder/";
string fileFilter = "Cat.png";
string finalPath= String.format("{0}{1}", urlPath, fileFilter);
(Or anything like this)
(Hope I understand your question and I was able to help you ^^)
I have a Web Api controller, that gets file. (Server)
[HttpGet]
[Route("api/FileDownloading/download")]
public HttpResponseMessage GetDocuments()
{
var result = new HttpResponseMessage(HttpStatusCode.OK);
var fileName = "QRimage2.jpg";
var filePath = HttpContext.Current.Server.MapPath("");
var fileBytes = File.ReadAllBytes(#"c:\\TMP\\QRimage2.jpg");
MemoryStream fileMemStream = new MemoryStream(fileBytes);
result.Content = new StreamContent(fileMemStream);
var headers = result.Content.Headers;
headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
headers.ContentDisposition.FileName = fileName;
headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
headers.ContentLength = fileMemStream.Length;
return result;
}
And Xamarin Android client, that downloading the file using the controller (http://localhost:6100/api/FileDownloading/download)
public void DownloadFile(string url, string folder)
{
string pathToNewFolder = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, folder);
Directory.CreateDirectory(pathToNewFolder);
try
{
WebClient webClient = new WebClient();
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
string pathToNewFile = Path.Combine(pathToNewFolder, Path.GetFileName(url));
webClient.DownloadFileAsync(new Uri(url), null);
}
catch (Exception ex)
{
if (OnFileDownloaded != null)
OnFileDownloaded.Invoke(this, new DownloadEventArgs(false));
}
}
Everithing works fine, but on my Android device in file explorer i have file with "download" file name instead of "QRimage2.jpg". How can I get actual file name using this controller?
You will need use the web response to read the content disposition. So, we can't use DownloadFileAsync directly.
public async Task<string> DownloadFileAsync(string url, string folder)
{
var request = WebRequest.Create(url);
var response = await request.GetResponseAsync().ConfigureAwait(false);
var fileName = string.Empty;
if (response.Headers["Content-Disposition"] != null)
{
var contentDisposition = new System.Net.Mime.ContentDisposition(response.Headers["Content-Disposition"]);
if (contentDisposition.DispositionType == "attachment")
{
fileName = contentDisposition.FileName;
}
}
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentException("Cannot be null or empty.", nameof(fileName));
}
var filePath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, folder, fileName);
using (var contentStream = response.GetResponseStream())
{
using (var fileStream = File.Create(filePath))
{
await contentStream.CopyToAsync(fileStream).ConfigureAwait(false);
}
}
return filePath;
}
Is this always going to be a jpg? If so I'd change the MediaTypeHeaderValue to image/jpeg - By doing that you are telling the browser the exact type of file, instead of a generic file. I'm thinking this is the issue since you are telling the Android Browser it's just a generic binary file.
Do I need Content-Type: application/octet-stream for file download?
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 writing a Web API service where I want to accept a file (image) and a serialized object (JSON) that contains key information about the image. Not having issues with the image part but when I add string content containing the deserialized object I am having issues in trying to determine which is which and act accordingly.
The client code looks like:
HttpClient client = new HttpClient();
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(new StreamContent(File.Open("c:\\MyImages\\Image00.jpg", FileMode.Open)), "image_file", "Image00.jpg");
ImageKeys ik = new ImageKeys { ImageId = "12345", Timestamp = DateTime.Now.ToString() };
JavaScriptSerializer js = new JavaScriptSerializer();
if (ik != null)
{
content.Add(new StringContent(js.Serialize(ik), Encoding.UTF8, "application/json"), "image_keys");
}
string uri = "http://localhost/MyAPI/api/MyQuery/TransferFile";
var request = new HttpRequestMessage()
{
RequestUri = new Uri(uri),
Method = HttpMethod.Post
};
request.Content = content;
string responseStr = "";
try
{
HttpResponseMessage result = client.SendAsync(request).Result;
string resultContent = string.Format("{0}:{1}", result.StatusCode, result.ReasonPhrase);
//
// Handle the response
//
responseStr = resultContent;
}
catch (Exception ex)
{
responseStr = ex.Message;
}
listBox1.Items.Add(responseStr);
So I include the image file first followed by a serialized object as StringContent. On the server side I am using the following code to parse the message.
HttpRequestMessage request = this.Request;
HttpResponseMessage ret = new HttpResponseMessage();
//
// Verify that this is an HTML Form file upload request
//
if (!request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = "c:\\tmp\\uploads";
if (!Directory.Exists(root))
{
Directory.CreateDirectory(root);
}
//
// Create a stream provider for setting up output streams that saves the output under c:\tmp\uploads
// If you want full control over how the stream is saved then derive from MultipartFormDataStreamProvider
// and override what you need.
//
MultipartFormDataStreamProvider streamProvider = new MultipartFormDataStreamProvider(root);
try
{
await request.Content.ReadAsMultipartAsync(streamProvider);
foreach (var file in streamProvider.Contents)
{
if (file.Headers.ContentDisposition.Name == "image_file")
{
FileInfo finfo = new FileInfo(streamProvider.FileData.First().LocalFileName);
string destFile = Path.Combine(root, streamProvider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", ""));
//
// File.Move cannot deal with duplicate files
// Ensure that the target does not exist.
//
if (File.Exists(destFile))
{
File.Delete(destFile);
}
File.Move(finfo.FullName, destFile);
}
else if (file.Headers.ContentDisposition.Name == "image_keys")
{
// deserialize key class
string str = file.ReadAsStringAsync().Result;
JavaScriptSerializer js = new JavaScriptSerializer();
ImageKeys ik = js.Deserialize<ImageKeys>(str);
}
}
ret.StatusCode = HttpStatusCode.OK;
ret.Content = new StringContent("File uploaded.");
}
catch (Exception ex)
{
ret.StatusCode = HttpStatusCode.UnsupportedMediaType;
ret.Content = new StringContent("File upload failed.");
}
return ret;
The foreach loop tries to process each item in the multipart content as a file but I want to treat the various content types separately but it is not clear to me how they are delineated.
Thanks
You can cast Content to MultipartFormDataContent and iterate thru it. Based on content type you can read it as a file or string. Example for string content type:
var dataContents = request.Content as MultipartFormDataContent;
foreach (var dataContent in dataContents)
{
var name = dataContent.Headers.ContentDisposition.Name;
var value = dataContent.ReadAsStringAsync().Result;
...
}