C# File Download is Corrupt - c#

I've got some C# in a utility for a Web API project. The upload portion of the code works fine; I've verified the file that gets to the server matches the file that was uploaded. However, something is happening in the download that causes the client to view the file as corrupted, and when I do a diff I can see that something went wrong.
Unfortunately, I can't figure out what I'm doing wrong. The relevant parts of the utility are as follows:
public static HttpResponseMessage StreamResponse(this HttpRequestMessage request, Stream stream)
{
if (stream.CanSeek) stream.Position = 0;// Reset stream if possible
HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK);
response.Content = new StreamContent(stream);
if (stream is FileStream)
{// If this is a FileStream, might as well figure out the content type
string mimeType = MimeMapping.GetMimeMapping(((FileStream)stream).Name);
response.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType);
}
return response;
}
public static HttpResponseMessage DownloadAs(this HttpResponseMessage response, string fileName)
{
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = fileName;
response.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MimeMapping.GetMimeMapping(fileName));
return response;// For chaining or whatnot
}
My usage in the API controllers is return ResponseMessage(Request.StreamResponse(stream).DownloadAs("Filename.ext"));
I've double checked code for downloading, and this seems to match up with what I've found. What am I doing wrong or what am I missing? It looks like something's wrong with the encoding or charset, but I can't tell what the solution is.

Finally figured out the issue thanks to this Q&A. I was missing the responseType option/parameter in my $http call in the client-side code.

Related

.NET HttpClient Image Upload to PlantNet API

I am trying to make a Request to the PlantNet API via .NET HttpClient. I have a FileStream and I am using the StreamContent and when I look via debugger at the content before it is sent it's looking good. However PlantNet response is Unsupported file type for image[0] (jpeg or png).
I tried everything that came in my mind, the same request from VS Code Rest Client is working (with the same file), does anyone have any ideas if the StreamContent is messing somehow with the file data?
HttpResponseMessage responseMessage;
using (MultipartFormDataContent content = new("abcdef1234567890")) //Fixed boundary for debugging
{
content.Add(new StringContent("flower"), "organs");
using Stream memStream = new MemoryStream();
await stream.CopyToAsync(memStream, cancellationToken);
StreamContent fileContent = new(memStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
content.Add(fileContent, "images", fileName);
responseMessage = await _httpClient.PostAsync(url, content, cancellationToken);
}
Note: stream is the stream of the file, in this case it comes from an ASP.NET Core API controller usingIFormFile.OpenReadStream() but I also tried opening the file directly via
new FileStream("path", FileMode.Open, FileAccess.Read)
In the Debugger content.ReadAsStringAsync() resolves to the following
--abcdef1234567890
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=organs
flower
--abcdef1234567890
Content-Type: image/jpeg
Content-Disposition: form-data; name=images; filename=test-flower.jpeg; filename*=utf-8''test-flower.jpeg
--abcdef1234567890--
which is looking absolutely fine for me, so my guess is, that somehow the file binary data may be corrupt in the content or something?
When I use the above for VS Code rest client with the same file it works and I get a successful response from the PlantNet API.
(Background: I am using .NET 6 on Fedora Linux)
Ok I solved it by removing the copy to the memory stream. This was needed as at first for debugging I opened the file directly and received exceptions if I didn't do it.
The code that is working for me is
HttpResponseMessage responseMessage;
using (MultipartFormDataContent content = new("abcdef1234567890"))
{
content.Add(new StringContent("flower"), "organs");
StreamContent fileContent = new(stream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
FileName = fileName,
Name = "images"
};
content.Add(fileContent, "images", fileName);
responseMessage = await _httpClient.PostAsync(url, content, cancellationToken);
}

Forwarding a stream in c# api

In my service we need to get a zip file created by another service and return it.
This is my code (code has been simplified for the question):
[HttpGet("mediafiles/{id}")]
public async Task<IActionResult> DownloadMediaFiles(int id)
{
var fileIds = _myProvider.GetFileIdsForEntityId(id); // result be like "1,2,3,4"
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"http://file-service/bulk/{fileIds}");
var stream = await response.Content.ReadAsStreamAsync();
return File(stream, "application/octet-stream", "media_files.zip");
}
With the id I can gather the info I need to create the fileIds string and call the other service.
Here's the api on the other service (code has been simplified for the question):
[HttpGet("bulk/{idList}")]
public async Task<IActionResult> DownloadBulk(string idList)
{
var ids = string.IsNullOrEmpty(idList) ? new List<int>() : idList.Split(',').Select(x => Convert.ToInt32(x));
using var memoryStream = new MemoryStream();
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var index = archive.CreateEntry("hello.txt");
using (var entryStream = index.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write("hello");
}
}
var byteArray = memoryStream.ToArray();
return File(byteArray, "application/octet-stream", "media_files.zip");
}
but when the client tries to open the zip we get
Exception has occurred. ArchiveException (FormatException: Could not
find End of Central Directory Record)
I'm absolutely not confident about these two lines of the /mediafiles/{id}
var stream = await response.Content.ReadAsStreamAsync();
return File(stream, "application/octet-stream", "media_files.zip");
And probably the issue might be there.
I just need to forward back the file-service response, but I don't know why
I believe the problem you're experiencing is that in DownloadMediaFiles(int id) you are using an HttpClient that gets disposed when leaving the function scope. The stream you created from the response therefore is closed and disposed of as well, before the response payload has finished writing its contents to the client. The client therefore receives an incomplete zip-file that you can't open. See here for reference.
In this answer there's a simple solution you could use, which is simply to read the response stream (the response stream from $"http://file-service/bulk/{fileIds}") into a byte array and then pass it to the response to the client:
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"http://file-service/bulk/{fileIds}");
var byteArr = await response.Content.ReadAsByteArrayAsync();
return File(byteArr, "application/octet-stream", "media_files.zip");
You might realize that this means loading the whole file into memory, which can quickly become an issue if you plan on working with large files or even with medium sized files if the API is supposed to be used by a lot of clients simultaneously. Your web application would most likely run out of memory at some point.
Instead, I came upon this article which shows how you can return the contents of the stream from a request using an HttpClient. You should be able to stick with the first section of that article (all the ZIP-file and callback-based response stuff is unrelated).
To recap on that article all you need is something like this:
// Your ControllerClass.cs
private static HttpClient Client { get; } = new HttpClient();
[HttpGet("mediafiles/{id}")]
public async Task<IActionResult> DownloadMediaFiles(int id)
{
var fileIds = _myProvider.GetFileIdsForEntityId(id); // result be like "1,2,3,4"
var stream = await Client.GetStreamAsync($"http://file-service/bulk/{fileIds}");
return File(stream, "application/octet-stream", "media_files.zip");
}
You'll notice, that the stream object is not disposed of here but ASP.Net Core does this for you as part of writing the response payload to the client. The Client which is stored in a static global variable is not disposed of either, which means you can reuse it between requests (it's usually recommended not to instantiate a new HttpClient everytime you need it). ASP.Net Core 2.1 and up has special support for dependency injecting the client for you through the IHttpClientFactory interface. I would suggest you do that instead of a static variable. Read here for the most basic usage of injecting the client factory.
Now you should be able to enjoy streaming the file contents directly from your "other service" without loading it into memory in your API web application.

How to pass a pdf from WebAPI and read the pdf from the MVC Controller?

I have a Web API service that should return a PDF.
I am then trying to call that WebAPI method to read the PDF.
Here is my API Method:
[HttpPost]
[Route("GetTestPDF")]
public HttpResponseMessage TestPDF()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(new FileStream(#"C:\MyPath\MyFile.pdf", FileMode.Open, FileAccess.Read));
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "MyFile.pdf";
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
return Request.CreateResponse(HttpStatusCode.OK, response);
}
However when I go to read the response I don't see the pdf contents. I am not sure where I am going wrong with this.
Controller Method:
public ActionResult GetPDF()
{
var response = new HttpResponseMessage();
using (HttpClient httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(#"my local host");
response = httpClient.PostAsync(#"api/job/GetTestPDF", new StringContent(string.Empty)).Result;
}
var whatisThis = response.Content.ReadAsStringAsync().Result;
return new FileContentResult(Convert.FromBase64String(response.Content.ReadAsStringAsync().Result), "application/pdf");
}
When I examine whatisThis variable I see the content type and the content dispostion correctly set from my API. However I don't see the content of the PDF.
How can I read the PDF content?
EDIT:
If I read the content as a string in my MVC site I see. (I don't see the actual content of the file)
{"Version":{"_Major":1,"_Minor":1,"_Build":-1,"_Revision":-1},"Content":{"Headers":[{"Key":"Content-Disposition","Value":["attachment; filename=MyFile.pdf"]},{"Key":"Content-Type","Value":["application/pdf"]}]},"StatusCode":200,"ReasonPhrase":"OK","Headers":[],"RequestMessage":null,"IsSuccessStatusCode":true}
I stepped through the WebAPI and it is successfully reading and setting the response.Content with the file contents.
Still not sure if this is an issue on the WebAPI side or the MVC side.
I'll post this initially as an answer because it's easier to format code!
I made an API endpoint to return a PDF file, and if I call it from a browser the file opens as expected.
As your API doesn't appear to do this, let's assume that the problem is there, and hence this.
Here is the endpoint code, that is very similar to yours, but missing the ContentDisposition stuff:
public HttpResponseMessage Get()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
FileStream fileStream = File.OpenRead("FileName.pdf");
response.Content = new StreamContent(fileStream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return response;
}

Is it wise to pass a Stream response from an API around in ASP.NET? [duplicate]

I'm working on a web service using ASP.NET MVC's new WebAPI that will serve up binary files, mostly .cab and .exe files.
The following controller method seems to work, meaning that it returns a file, but it's setting the content type to application/json:
public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
var path = #"C:\Temp\test.exe";
var stream = new FileStream(path, FileMode.Open);
return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}
Is there a better way to do this?
Try using a simple HttpResponseMessage with its Content property set to a StreamContent:
// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;
public HttpResponseMessage Post(string version, string environment,
string filetype)
{
var path = #"C:\Temp\test.exe";
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
A few things to note about the stream used:
You must not call stream.Dispose(), since Web API still needs to be able to access it when it processes the controller method's result to send data back to the client. Therefore, do not use a using (var stream = …) block. Web API will dispose the stream for you.
Make sure that the stream has its current position set to 0 (i.e. the beginning of the stream's data). In the above example, this is a given since you've only just opened the file. However, in other scenarios (such as when you first write some binary data to a MemoryStream), make sure to stream.Seek(0, SeekOrigin.Begin); or set stream.Position = 0;
With file streams, explicitly specifying FileAccess.Read permission can help prevent access rights issues on web servers; IIS application pool accounts are often given only read / list / execute access rights to the wwwroot.
For Web API 2, you can implement IHttpActionResult. Here's mine:
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
class FileResult : IHttpActionResult
{
private readonly string _filePath;
private readonly string _contentType;
public FileResult(string filePath, string contentType = null)
{
if (filePath == null) throw new ArgumentNullException("filePath");
_filePath = filePath;
_contentType = contentType;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(_filePath))
};
var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return Task.FromResult(response);
}
}
Then something like this in your controller:
[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
var serverPath = Path.Combine(_rootPath, imagePath);
var fileInfo = new FileInfo(serverPath);
return !fileInfo.Exists
? (IHttpActionResult) NotFound()
: new FileResult(fileInfo.FullName);
}
And here's one way you can tell IIS to ignore requests with an extension so that the request will make it to the controller:
<!-- web.config -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
For those using .NET Core:
You can make use of the IActionResult interface in an API controller method, like so.
[HttpGet("GetReportData/{year}")]
public async Task<IActionResult> GetReportData(int year)
{
// Render Excel document in memory and return as Byte[]
Byte[] file = await this._reportDao.RenderReportAsExcel(year);
return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
}
This example is simplified, but should get the point across. In .NET Core this process is so much simpler than in previous versions of .NET - i.e. no setting response type, content, headers, etc.
Also, of course the MIME type for the file and the extension will depend on individual needs.
Reference: SO Post Answer by #NKosi
While the suggested solution works fine, there is another way to return a byte array from the controller, with response stream properly formatted :
In the request, set header "Accept: application/octet-stream".
Server-side, add a media type formatter to support this mime type.
Unfortunately, WebApi does not include any formatter for "application/octet-stream". There is an implementation here on GitHub: BinaryMediaTypeFormatter (there are minor adaptations to make it work for webapi 2, method signatures changed).
You can add this formatter into your global config :
HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));
WebApi should now use BinaryMediaTypeFormatter if the request specifies the correct Accept header.
I prefer this solution because an action controller returning byte[] is more comfortable to test. Though, the other solution allows you more control if you want to return another content-type than "application/octet-stream" (for example "image/gif").
For anyone having the problem of the API being called more than once while downloading a fairly large file using the method in the accepted answer, please set response buffering to true
System.Web.HttpContext.Current.Response.Buffer = true;
This makes sure that the entire binary content is buffered on the server side before it is sent to the client. Otherwise you will see multiple request being sent to the controller and if you do not handle it properly, the file will become corrupt.
The overload that you're using sets the enumeration of serialization formatters. You need to specify the content type explicitly like:
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
You could try
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
You can try the following code snippet
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
Hope it will work for you.

Byte array different from an Web Api Method in C#?

So I have a web api which returns a byte array like this
public byte[] Validate()
{
byte[] buffer = licensePackage.ToByteArray();
return buffer;
}
The thing is when I get it on client it is different size and different byte array, I googled and found this link helpful http://www.codeproject.com/Questions/778483/How-to-Get-byte-array-properly-from-an-Web-Api-Met.
But can I know why this happens? Also, what is an alternate way to send back that file contents from the server?
With the given information I think it must have something to do with content negotiation. I can't tell the reason, but what I'm sure it's that there is a different approach to serve files behind a Web Api.
var response = Request.CreateResponse(HttpStatusCode.OK);
var stream = new FileStream(pathToFile, FileMode.Open, FileAccess.Read);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return ResponseMessage(response);
With this solution, you serve the file contents returning an IHttpActionResult instance. And, returning a response with StreamContent you are returning a stream that must not be modified by Web Api Content Negotation.

Categories