C# Serving a stream to a browser - can it be done? - c#

I am trying to output a file to the browser from a REST API - but I don't have a physical file, instead I have a MemoryStream (and I would prefer not to write a physical file).
This works:
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open);
result.Content = new StreamContent(stream);
return result;
This does not:
var stream = new MemoryStream();
// Iterate DataReader and populate MemoryStream code omitted for brevity.
// Assume MemoryStream has been written to correctly and contains data.
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(stream);
return result;
This has consumed most of my weekend so I would be delighted if anyone can offer some definitive insight.

I've found the answer through trial and error and a lot of research:
Yes, it can be done.
Instead of using StreamContent use ByteArrayContent:
e.g.
result.Content = new ByteArrayContent( stream.GetBuffer() );
Ensure that there is no HttpResponseMessage.ContentLength set or it will fail to work (connection reset) - it took me hours to figure that out.

Related

Can save stream as local file, but when returning it as HttpResponseMessage - always empty file

I want to write export/download functionality for files from external API.
I've created separate Action for it. Using external API I can get stream for that file.
When I am saving that stream to local file, everything is fine, file isn't empty.
var exportedFile = await this.GetExportedFile(client, this.ReportId, this.WorkspaceId, export);
// Now you have the exported file stream ready to be used according to your specific needs
// For example, saving the file can be done as follows:
string pathOnDisk = #"D:\Temp\" + export.ReportName + exportedFile.FileSuffix;
using (var fileStream = File.Create(pathOnDisk))
{
await exportedFile.FileStream.CopyToAsync(fileStream);
}
But when I return exportedFile object that contains in it stream and do next:
var result = await this._service.ExportReport(reportName, format, CancellationToken.None);
var fileResult = new HttpResponseMessage(HttpStatusCode.OK);
using (var ms = new MemoryStream())
{
await result.FileStream.CopyToAsync(ms);
ms.Position = 0;
fileResult.Content = new ByteArrayContent(ms.GetBuffer());
}
fileResult.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"{reportName}{result.FileSuffix}"
};
fileResult.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return fileResult;
Exported file is always empty.
Is it problem with stream or with code that try to return that stream as file?
Tried as #Nobody suggest to use ToArray
fileResult.Content = new ByteArrayContent(ms.ToArray());
the same result.
Also tried to use StreamContent
fileResult.Content = new StreamContent(result.FileStream);
still empty file.
But when I'm using StreamContent and MemmoryStream
using (var ms = new MemoryStream())
{
await result.FileStream.CopyToAsync(ms);
ms.Position = 0;
fileResult.Content = new StreamContent(ms);
}
in result I got
{
"error": "no response from server"
}
Note: from 3rd party API I get stream that is readonly.
you used GetBuffer() to retrieve the data of the memory stream.
The function you should use is ToArray()
Please read the Remarks of the documentation of these functions.
https://learn.microsoft.com/en-us/dotnet/api/system.io.memorystream.getbuffer?view=net-6.0
using (var ms = new MemoryStream())
{
ms.Position = 0;
await result.FileStream.CopyToAsync(ms);
fileResult.Content = new ByteArrayContent(ms.ToArray()); //ToArray() and not GetBuffer()
}
Your "mistake" although it's an obvious one is that you return a status message, but not the actual file itself (which is in it's own also a 200).
You return this:
var fileResult = new HttpResponseMessage(HttpStatusCode.OK);
So you're not sending a file, but a response message. What I'm missing in your code samples is the procedure call itself, but since you use a HttpResonseMessage I will assume it's rather like a normal Controller action. If that is the case you could respond in a different manner:
return new FileContentResult(byteArray, mimeType){ FileDownloadName = filename };
where byteArray is ofcourse just a byte[], the mimetype could be application/octet-stream (but I suggest you'd actually find the correct mimetype for the browser to act accordingly) and the filename is the filename you want the file to be named.
So, if you were to stitch above and my comment together you'd get this:
var exportedFile = await this.GetExportedFile(client, this.ReportId, this.WorkspaceId, export);
// Now you have the exported file stream ready to be used according to your specific needs
// For example, saving the file can be done as follows:
string pathOnDisk = #"D:\Temp\" + export.ReportName + exportedFile.FileSuffix;
using (var fileStream = File.Create(pathOnDisk))
{
await exportedFile.FileStream.CopyToAsync(fileStream);
}
return new FileContentResult(System.IO.File.ReadAllBytes(pathOnDisk), "application/octet-stream") { FileDownloadName = export.ReportName + exportedFile.FileSuffix };
I suggest to try it, since you still report a 200 (and not a fileresult)

Form Recognizer SDK, analyze with custom model, file stream issue

When I'm trying to analyze a pdf document using FileStream from a local file, everything works fine.
But when I use a IFormFile and use method OpenReadStream() and pass that stream to the Analyze method for form analyzer, i get an exception. I also tried creating a new stream out of the IFromFile stream and that did not work either.
Any help will be much appreciated. Thank you
Working code:
using var stream = new FileStream("D:\\somefile.pdf", FileMode.Open);
var result = await _formRecognizerClient.AnalyzeWithCustomModelAsync(modelId, fileStream, "application/pdf");
Code I am trying to make work:
using var stream = file.OpenReadStream(); // file is an IFormFile
var result = await _formRecognizerClient.AnalyzeWithCustomModelAsync(modelId, stream , file.ContentType);
I have a solution for now, its not elegant but it works. I am of course very much looking for something better if anyone can help.
For now, I am creating a file, saving it and creating a FileStream out of it. Also works in docker as I'm testing using docker-compose
var iFormFileStream = file.OpenReadStream();
var stream = File.Create(string.Format("tempfilename.pdf", File.));
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(stream);
stream.Close();
using var fileStream = new FileStream("tempfilename.pdf", FileMode.Open);
var result = await _formRecognizerClient.AnalyzeWithCustomModelAsync(modelId, fileStream, "application/pdf");

Web API 2 Getting ZIP files returned strange symbols

As I was trying to get a zip file from my one of my dir through API, I always get this random or strange symbols.
var path = D:\\Source\\API\\TEST\\XMLPDF\\test.zip;
HttpResponseMessage reponse = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
reponse.Content = new StreamContent(stream);
reponse.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return reponse;
I've used the code above to return the zip file.
I've added a screenshot from the return body of postman.
Has anyone encountered this kind of issue?

Calculate SHA1 during PostAsync with StreamContent in .net core

I wanted to make an HTTP POST request with a big file so I composed C# code on .net core like following:
using (var fileStream = new FileStream(filePath, FileMode.Open))
using (var streamContent = new StreamContent(fileStream))
{
var content = new MultipartFormDataContent();
content.Add(streamContent);
resp = await httpClient.PostAsync(requestUri, content);
}
It worked well for me. But, I found that it might be happy if SHA1 can be calculated during this context then it can be used for a simple caching for the POST result.
I found some answers on SO like the following:
How do I do a SHA1 File Checksum in C#?
But, I have no idea how I can use this to my code.
Here is what I tried so far:
byte[] hash = null;
using (SHA1Managed shaForStream = new SHA1Managed())
using (var fs = new FileStream(filePath, FileMode.Open))
using (var streamContent = new StreamContent(fs))
{
var content = new MultipartFormDataContent();
content.Add(streamContent);
resp = await httpClient.PostAsync(requestUri, content);
hash = shaForStream.ComputeHash(fs);
}
It is not working. For example, the sha1 value is the same for different files.
I think I might need to study Stream more. Could you tell me which materials do I need to study for this problem?
p.s, There are a couple of ways to cache request and response i.e., request params and response, but I want to do it with a SHA1 way for testing. I am just curious how I can do calculate something and request POST with only one stream. I can do it by creating two file streams. :)

ASP.NET Web API to return images

I've been spinning my tires trying to use ASP.NET Web API to return images. I've seen a number of examples, but I keep running into problems.
After searching for a solution, most examples suggest using HttpResponseMessage and setting the Content and Content-Type header correctly. For example in the following posts:
WebApi: How to handle Images ASP .Net Web API downloading images as binary
Here's what I am doing now:
[System.Web.Http.HttpGet]
[ActionName("ConvertHTMLToImage")]
public HttpResponseMessage ConvertHTMLToImage(string htmlString)
{
var path = #"C:\temp\mona-lisa.png";
var response = Request.CreateResponse(HttpStatusCode.OK);
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
response.Content = new StreamContent(fileStream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
return response;
}
I'm getting mixed results. When I go the URL for that resource I have problems with Chrome (This webpage is not available), Firefox (The connection was reset), and Safari (Safari can’t open the page). Much to my surprise, it works fine in IE. I can also successfully view the image using the composer in Fiddler. If I keep Fiddler open and access the resource using Chrome/Firefox/Safari, I see 504 errors. Not sure what the means, I can't remember ever having seen that error type before.
I also noticed some strange behavior in the debugger with the browsers that aren't working. If I call ConvertHTMLToImage from IE and Fiddler, I see it stop at my break-point once and then the image is sucessfully returned to the client. On Chrome/Firefox/Safari there are multiple calls into the method. Sometimes twice sometimes 3 times. I have no explanation for this. I don't get any errors that I can detect other than the browser doesn't show the image.
I've done this same thing using aspx pages, HttpHhandlers, and other .NET methods so I know there are work-arounds, but I really would like to know what I'm doing wrong. This seems like something that should be easily accomplished using the Web API.
This solved the issue for me WebApi: How to handle Images
In short
[System.Web.Http.HttpGet]
[ActionName("ConvertHTMLToImage")]
public HttpResponseMessage ConvertHTMLToImage()
{
string filePath = #"C:\temp\mona-lisa.png";
var result = new HttpResponseMessage(HttpStatusCode.OK);
FileStream fileStream = new FileStream(filePath, FileMode.Open);
Image image = Image.FromStream(fileStream);
MemoryStream memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Jpeg);
result.Content = new ByteArrayContent(memoryStream.ToArray());
result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
return result;
}
I used this recently in my project:
public HttpResponseMessage Get(int IRN)
{
try
{
Multimedia image = new Multimedia();
MemoryStream imageStream = image.GetMedia(IRN);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(imageStream);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
return response;
}
catch (Exception ex)
{
return HandleError(ex);
}
}
GetMedia function:
.....
MemoryStream mediaStream = new MemoryStream();
...
FileStream temp = resource["file"] as FileStream;
mediaStream.SetLength(temp.Length);
temp.Read(mediaStream.GetBuffer(), 0, (int)temp.Length);
temp.Close();
....
return mediaStream;
resource["file"] is an array which contained a filestream object.
Hope this helps.
Try this(replace jpg with png as desired):
var result = new HttpResponseMessage(HttpStatusCode.OK);
String filePath = HostingEnvironment.MapPath("~/Images/123.jpg");
FileStream fileStream = new FileStream(filePath, FileMode.Open);
Image image = Image.FromStream(fileStream);
MemoryStream memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Jpeg);
var byteArrayContent = new ByteArrayContent(memoryStream.ToArray());
byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
result.Content = byteArrayContent;
return result;

Categories