Encoding issue with file on AWS Lambda/API Gateway - c#

I'm producing a REST API that does some file conversion / processing.
My Visual Studio 2015 and building on an AWS Serverless Core - ASP.Net Core Web API template.
I'm running some initial test methods and have encountered what appears to be an encoding issue.
My controller has the following. It simply pulls the posted file into a byte array via a memory stream and then passes it back. (The final application will process the byte array)
[HttpPost]
public IActionResult Post(IFormFile file)
{
var inputStream = new MemoryStream();
file.CopyTo(inputStream);
var fileBytes = inputStream.ToArray();
var outputStream = new MemoryStream(fileBytes);
return File(outputStream, "application/octet-stream");
}
I then have a test application I'm using to pass a file to this controller and save the return.
using (var client = new HttpClient())
{
using (var content = new MultipartFormDataContent())
{
var inputFileStream = new FileStream(Server.MapPath("~/App_Data/InputFile.pdf"), FileMode.Open, FileAccess.Read);
var inputFileBytes = new Byte[inputFileStream.Length];
inputFileStream.Read(inputFileBytes, 0, inputFileBytes.Length);
inputFileStream.Close();
content.Add(new ByteArrayContent(inputFileBytes), "file", "InputFile.pdf");
var requestUri = "http://localhost:5000/api/controller";
//var requestUri = "https://xxxxxxxxxx.execute-api.eu-west-1.amazonaws.com/Prod/api/controller";
var result = client.PostAsync(requestUri, content).Result;
var resultStream = result.Content.ReadAsStreamAsync().Result;
var memoryStream = new MemoryStream();
resultStream.CopyTo(memoryStream);
var outputFileBytes = memoryStream.ToArray();
FileStream outputFileStream = new FileStream(Server.MapPath("~/App_Data/OutputFile.pdf"), FileMode.Create, FileAccess.ReadWrite);
outputFileStream.Write(outputFileBytes, 0, outputFileBytes.Length);
outputFileStream.Close();
}
}
When I run using the localhost application, the duplicate file is saved back. However, when I publish the API to AWS, the file returned is exactly double in size to the original, certainly indicated an encoding issue.
If I pass an ANSI text file with the contents TEST then the saved file contains VEVTVA==
Can someone point me at where I should be setting any encoding settings and any suggested settings to ensure that the output stream from my HttpClient is the same as my input?

So I noticed this as well with a AWS ASP.Net Core Web API. I changed the MIME type from application/octet-stream to application/text and that seemed to fix it on AWS.
[HttpPost]
public IActionResult Post(IFormFile file)
{
var inputStream = new MemoryStream();
file.CopyTo(inputStream);
var fileBytes = inputStream.ToArray();
var outputStream = new MemoryStream(fileBytes);
return File(outputStream, "application/text");
}

Related

.NET Core 3 MVC download a file from HTTP and re-deliver to client using minimum memory

I have a .NET Core 3 MVC app that needs to read a file from one location over HTTP and then re-deliver it back out to the response. Some of these files will be ~200MB in size.
What I have works, but it reads the whole file into memory before sending the File result out to the client. Is there a way to make it essentially a passthrough where the read stream flows into the response stream so that very little memory is required on the server?
This is what I have now but I do not think will perform well with large files:
if (requestedFile != null)
{
using (var client = new System.Net.Http.HttpClient())
{
using (var result = await client.GetAsync(requestedFile.DownloadUrl))
{
if (result.IsSuccessStatusCode)
{
var bytes = await result.Content.ReadAsByteArrayAsync();
return File(bytes, "application/zip", "largefile.zip");
}
}
}
}
I have also tried this which results in a runtime error of "Cannot access a closed Stream":
using (var client = new System.Net.Http.HttpClient())
{
using (var httpResponseMessage = await client.GetAsync(requestedFile.DownloadUrl))
{
return File(await httpResponseMessage.Content.ReadAsStreamAsync(), "application/zip", "largefile.zip");
}
}
Edit:
Solution after some trial and error was remocing all using statements and letting the FileStreamResult close the stream on its own. So I ended up with:
var client = new HttpClient();
var result = await client.GetAsync(requestedFile.DownloadUrl);
var stream = await result.Content.ReadAsStreamAsync();
return new FileStreamResult(stream, "application/zip")
{
FileDownloadName = "largefile.zip"
};
One of the overloads for File is a Stream. Just get that URL as a Stream or read the response body as a stream and immediately return that in the overload:
var client = new System.Net.Http.HttpClient();
var result = await client.GetAsync(requestedFile.DownloadUrl);
var stream = await result.Content.ReadAsStreamAsync();
return File(stream,"application/pdf", "Invoice.pdf");
Note: this will fail if you wrap the Stream in a using block as the FileResult already closes the Stream.
For Export Excel
var client = new System.Net.Http.HttpClient();
var comp = client.GetAsync($"RewardEmployee/ExportExcelCalculate?rewardConfigId={id}").Result;
`var stream = await result.Content.ReadAsStreamAsync();
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"ExportExcelCalculateRewardEmployee.xlsx");

Sending byte[] representing zip file from one API to another

I am trying to send a zip file I create in one API (lets call this 'API 1') to another (aka 'API 2').
Originally I attempted to just send the byte[] as the response in API 1 but when accessing the response within API 2, the byte[] was a different size and the file was corrupt. I then attempted to convert the byte[] in API 1 to a base64 string and then decode in API 2, but realised the strings were not the same and again, the file was corrupt.
Here is my code as it currently stands...
API 1 Code
[HttpPost]
public IActionResult BuildZipFile(Custom myObject)
{
CODE HERE WHICH GENERATES ZIP FILE
byte[] zipFile = System.IO.File.ReadAllBytes(ZIP FILE PATH);
return Ok(Convert.ToBase64String(zipFile));
}
API 2 Code
[HttpPost]
public IActionResult RetrieveZipFile(Custom myObject)
{
RestClient client = new RestClient(#"https://localhost:44323/api/ProcessBuild");
RestRequest request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", JsonConvert.SerializeObject(myObject), ParameterType.RequestBody);
var response = client.Execute(request);
byte[] retrievedZipFile = Convert.FromBase64String(response.Content);
return File(retrievedZipFile, "application/zip", process.Name + ".zip");
}
Currently this returns a zip file which is corrupt. Any help on this would be greatly appreciated!
I am not sure about the mime type. This should work fine. I am not familiar with RestSharp.
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return File(System.IO.File.OpenRead(filePath), "application/zip");
}

No MediaTypeFormatter is available to read an object of type 'StreamContent' from content with media type 'application/octet-stream'

I am trying to call a method that returns a file in my asp.net web api from win app, but getting the following error:
No MediaTypeFormatter is available to read an object of type 'StreamContent' from content with media type 'application/octet-stream'
I have checked every bit of my code, but i don't know how to fix the issue.
HttpClient _client = new HttpClient();
_client.BaseAddress = new Uri("http://localhost:49681/api/ZipFile/");
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
// _client.DefaultRequestHeaders.Accept.Add(new ContentDispositionHeaderValue("attachment"));
HttpResponseMessage resp = _client.GetAsync("DownloadFile?fileName=/" + startPath).Result;
if (resp.StatusCode == System.Net.HttpStatusCode.OK)
{
//var file = resp.Content.ReadAsAsync<string>().Result;
var fileDownloaded = resp.Content.ReadAsAsync<StreamContent>().Result;
Stream fileStream = File.Create(#"c:\test\" + fileDownloaded);
//ZipFile.ExtractToDirectory(file, extractPath);
//MessageBox.Show(resp);
}
else
{
MessageBox.Show(resp.StatusCode.ToString());
}
You can't just magically convert the content of the HTTP response to StreamContent using ReadAsAsync<StreamContent> - you need to tell the system how to do so via a MediaTypeFormatter (that you would have to write as there is no ReadAsStreamContentAsync). However it would be much easier to just read the content as a byte array (or stream - using ReadAsStreamAsync) and then create the file on disk using that:
var fileDownloaded = await response.Content.ReadAsByteArrayAsync();
using (FileStream fileStream = File.Open(#"c:\test\" + yourFileName, FileMode.OpenOrCreate))
{
fileStream.Seek(0, SeekOrigin.End);
await fileStream.WriteAsync(fileDownloaded, 0, fileDownloaded.Length);
}

c# Multipart Form Data Encoding

I'm working on a C# application that submits some small files to a device via multipart form data. I've been unable to get the device to actually accept the data that I'm sending it and I have a feeling that it has to do with the actual encoding of the file as it's being transmitted somehow, but I'm not entirely sure. It returns a 200/OK, as a valid POST, but doesn't like the payload.
I've been using Chrome Dev Tools for the devices web interface to match as closely as possible. One thing I've noticed is that Dev Tools doesn't show me the actual payload, just the following as if it can't display it:
------WebKitFormBoundaryWwZOi6WAr7yb3yRE
Content-Disposition: form-data; name="[Redacted]"; filename="[AlsoRedacted]"
Content-Type: application/octet-stream
------WebKitFormBoundaryWwZOi6WAr7yb3yRE--
Alternatively, the MessageBox I've been using to help debug seem to have no issue displaying it.
The relevant portions of the code are below and would appreciate any suggestions.
using (FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read))
{
using (MultipartFormDataContent Content = new MultipartFormDataContent(String.Format("----------{0:N}", Guid.NewGuid())))
{
StreamContent ContentStream = new StreamContent(fs);
Content.Add(ContentStream, "filename", System.IO.Path.GetFileName(Filename));
ContentStream.Headers.ContentDisposition.Name = "UploadEdid";
ContentStream.Headers.ContentDisposition.FileNameStar = null;
ContentStream.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
String ContentString = Content.ReadAsStringAsync().Result;
System.Windows.MessageBox.Show(ContentString, ContentString.Length.ToString());
using (HttpResponseMessage Response = await HttpClient.PostAsync(RequestUri, Content))
{
if (Response.IsSuccessStatusCode)
{
HttpLoggedIn = true;
string ResponseString = await Response.Content.ReadAsStringAsync();
ParseHttpResponse(ResponseString);
}
else
{
HttpLoggedIn = false;
}
}
}
}

Consuming ASP.NET Web API from classic ASP to download file

I have a legacy Classic ASP application that serves as a repository for a bunch of files available for download. I would prefer to not rewrite the entire application at this time but instead want to pull out the file download logic into an ASP.NET Web API.
I have created the service and when I hit the service via my browser the download starts just fine with the hardcoded filename I have in the app and I am able to save. I am having trouble consuming the service from my asp app however. If I modify the code below to just return a JSON value, I am able to return the value to the asp app. So I at least know there is communication happening. Just not sure how to handle a file download.
HERE IS MY SERVICE LOGIC:
// GET api/values/5
public HttpResponseMessage Get(int id)
{
//return "value";
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
string filePath = "H:/Temp/Filename.exe";
MemoryStream memoryStream = new MemoryStream();
FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read);
byte[] bytes = new byte[file.Length];
file.Read(bytes, 0, (int)file.Length);
memoryStream.Write(bytes, 0, (int)file.Length);
file.Close();
httpResponseMessage.Content = new ByteArrayContent(memoryStream.ToArray());
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "Filename.exe" };
httpResponseMessage.StatusCode = HttpStatusCode.OK;
return httpResponseMessage;
}
HERE IS A SUBSET OF MY CLASSIC ASP LOGIC:
Response.Addheader "Content-Disposition", "attachment; filename=Filename.exe"
Response.ContentType = "application/octet-stream" 'EXE
Set HttpReq = Server.CreateObject("MSXML2.ServerXMLHTTP")
HttpReq.open "GET", "http://webservice/api/values/1", False
HttpReq.send
Response.BinaryWrite HttpReq.responsestream
What about something like:
Set HttpReq = Server.CreateObject("ADODB.Stream")
HttpReq.open "GET", "http://webservice/api/values/1", False
Response.BinaryWrite HttpReq.read

Categories

Resources