AspNet Core - Could not load the image from FileStreamResult - asp.net-mvc

Please help.
I've been searching for hours now as I cannot display the image on the website. I'm using byte[] from database then return FileStreamResult in controller.
Below is my code
.cshtml
<img src="#Url.Action("GetImage", "User")" />
User Service
I was able to call the api with byte[] value.
public async Task<HttpResponseMessage> GetImage()
{
var client = _clientFactory.CreateClient("ApiName");
var response = await client.GetAsync(apiUrl);
return response;
}
Controller
[ResponseCache(Duration = 10)]
[HttpGet]
public async Task<IActionResult> GetImage()
{
var response = await _user.GetImage();
if (response.IsSuccessStatusCode)
{
var logo = await response.Content.ReadAsByteArrayAsync();
var ms = new MemoryStream(logo);
FileStreamResult result = new FileStreamResult(ms, "image/png");
result.FileDownloadName = "logo1223.png";
return result;
}
return null;
}
API
[HttpGet("logo")]
public async Task<IActionResult> GetImage()
{
var someByte= await GetSomeImage();
return Ok(someByte);
}
when I tried to download or save the image file I get this result.
Image is not loading from the website
UPDATE
I tried to manually get the file then convert to byte[] and image is displaying properly on the website,
I checked how they upload the image... I found out
they are using below code to save image on DB.
var byteArr= Encoding.UTF8.GetBytes(Convert.ToBase64String(bArr.ToArray()))
Tried this modified controller
public async Task<FileContentResult> GetImage()
{
var img = Image.FromFile(#"C:\Downloads\test.png");
byte[] bArr = imgToByteArray(img);
var a = Encoding.UTF8.GetBytes(Convert.ToBase64String(bArr.ToArray()));
return File(bArr, "image/png"); // WORKING!!!
//return File(a, "image/png"); // NOT WORKING!!
}
Is there a way to convert it to original byte image?

The main issue is logo is saved in db using below code
var byteArr= Encoding.UTF8.GetBytes(Convert.ToBase64String(bArr.ToArray()))
Solution: Convert to original byte[] to get the image
[ResponseCache(Duration = 10)]
[HttpGet]
public async Task<IActionResult> GetImage()
{
var response = await _user.GetImage();
if (response.IsSuccessStatusCode)
{
// I created new class Logo which contains byte[] Logo
var logo = await response.Content.ReadAsAsync<Logo>();
// Convert to original byte[]
byte[] originalByte = Convert.FromBase64String(Encoding.UTF8.GetString(logo.Logo));
return File(originalByte, "image/png");
}
return null;
}

Related

How to insert HTML into Response body using .NET Core Middleware

I've been trying to cobble up some middleware that will allow me to measure the processing time on a request. This example gave me a good starting point, but I've run into trouble.
In the code below, I am able to measure the process time and insert it in a div (using HTML Agility Pack). However, the original contents of the page get duplicated. I think I'm doing something incorrectly with the context.Response.Body property in UpdateHtml(), but cannot figure out what it is. (I made some comments in the code.) If you see anything that looks incorrect, could you please let me know?
Thanks.
public class ResponseMeasurementMiddleware
{
private readonly RequestDelegate _next;
public ResponseMeasurementMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
watch.Start();
context.Response.OnStarting(async () =>
{
var responseTime = watch.ElapsedMilliseconds;
var newContent = string.Empty;
var existingBody = context.Response.Body;
string updatedHtml = await UpdateHtml(responseTime, context);
await context.Response.WriteAsync(updatedHtml);
});
await _next.Invoke(context);
}
private async Task<string> UpdateHtml(long responseTime, HttpContext context)
{
var newContent = string.Empty;
var existingBody = context.Response.Body;
string updatedHtml = "";
//I think I'm doing something incorrectly in this using...
using (var newBody = new MemoryStream())
{
context.Response.Body = newBody;
await _next(context);
context.Response.Body = existingBody;
newBody.Position = 0;
newContent = await new StreamReader(newBody).ReadToEndAsync();
updatedHtml = CreateDataNode(newContent, responseTime);
}
return updatedHtml;
}
private string CreateDataNode(string originalHtml, long responseTime)
{
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(originalHtml);
HtmlNode testNode = HtmlNode.CreateNode($"<div><h2>Inserted using Html Agility Pack: Response Time: {responseTime.ToString()} ms.</h2><div>");
var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");
htmlBody.InsertBefore(testNode, htmlBody.FirstChild);
string rawHtml = htmlDoc.DocumentNode.OuterHtml; //using this results in a page that displays my inserted HTML correctly, but duplicates the original page content.
//rawHtml = "some text"; uncommenting this results in a page with the correct format: this text, followed by the original contents of the page
return rawHtml;
}
}
For duplicated html, it is caused by await _next(context); in UpdateHtml which will invoke the rest middlware like MVC to handle the requests and response.
Withtout await _next(context);, you should not modify the Reponse body in context.Response.OnStarting.
For a workaround, I would suggest you place the ResponseMeasurementMiddleware as the first middleware and then calculate the time like
public class ResponseMeasurementMiddleware
{
private readonly RequestDelegate _next;
public ResponseMeasurementMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalBody = context.Response.Body;
var newBody = new MemoryStream();
context.Response.Body = newBody;
var watch = new Stopwatch();
long responseTime = 0;
watch.Start();
await _next(context);
//// read the new body
// read the new body
responseTime = watch.ElapsedMilliseconds;
newBody.Position = 0;
var newContent = await new StreamReader(newBody).ReadToEndAsync();
// calculate the updated html
var updatedHtml = CreateDataNode(newContent, responseTime);
// set the body = updated html
var updatedStream = GenerateStreamFromString(updatedHtml);
await updatedStream.CopyToAsync(originalBody);
context.Response.Body = originalBody;
}
public static Stream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
private string CreateDataNode(string originalHtml, long responseTime)
{
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(originalHtml);
HtmlNode testNode = HtmlNode.CreateNode($"<div><h2>Inserted using Html Agility Pack: Response Time: {responseTime.ToString()} ms.</h2><div>");
var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");
htmlBody.InsertBefore(testNode, htmlBody.FirstChild);
string rawHtml = htmlDoc.DocumentNode.OuterHtml; //using this results in a page that displays my inserted HTML correctly, but duplicates the original page content.
//rawHtml = "some text"; uncommenting this results in a page with the correct format: this text, followed by the original contents of the page
return rawHtml;
}
}
And register ResponseMeasurementMiddleware like
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<ResponseMeasurementMiddleware>();
//rest middlwares
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
For this way app.UseMiddleware<ResponseMeasurementMiddleware>();, the action will be last opertion before sending the response, and then processing time would be suitable for processing time.

How to download file in asp.net core

My download method is
public async Task<IActionResult> Download(string filename)
{
if (filename == null)
return Content("filename not present");
var path = Path.Combine(
Directory.GetCurrentDirectory(), "wwwroot" + #"\UploadFiles", filename);
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
return File(memory, GetContentType(path), Path.GetFileName(path));
}
and view.chtml and route with filepath
<a asp-action="Download"
asp-route-filename="#item.UploadFilePath">
Download
</a>
#item.UploadFilePath is database saved path.please help me.
you can try this also
return File(path, MediaTypeNames.Application.Octet, Path.GetFileName(path));
You can try this,
byte[] fileBytes = System.IO.File.ReadAllBytes(Filepath);
return File(fileBytes, "application/x-msdownload", FileName);
use this code for download file from wwwroot
public IActionResult DownloadAttachment(string attachment)
{
var path = Path.Combine(
Directory.GetCurrentDirectory(), "wwwroot\\UploadFile\\Journal",attachment);
byte[] fileBytes = System.IO.File.ReadAllBytes(path);
return File(fileBytes, "application/x-msdownload", attachment);
}
Try this on URL /Files?fileName=image.png, file image.png must be placed in wwwroot.
public class FilesModel : PageModel
{
private readonly IWebHostEnvironment hostingEnvironment;
public FilesModel(IWebHostEnvironment hostingEnvironment)
{
this.hostingEnvironment = hostingEnvironment;
}
public PhysicalFileResult OnGet(string fileName)
{
string path = Path.Combine(hostingEnvironment.WebRootPath, "Files", fileName);
return new PhysicalFileResult(path, "image/png"); // Change to the right mime type
}
}

How to generate interchangeable download links?

i'm tying to make DL link so others couldn't dl the same file by sharing it
so far i've found this code
public FileResult Download()
{
byte[] fileBytes = System.IO.File.ReadAllBytes(#"c:\folder\myfile.ext");
string fileName = "myfile.ext";
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
it won't make interchangeable links ,how can we do that?
Try this Example:
public ActionResult Download()
{
var filePath=#"c:\folder\myfile.ext";
var fileBytes = System.IO.File.ReadAllBytes(filePath);
var response = new FileContentResult(fileBytes, "application/octet-stream")
{
FileDownloadName = Path.GetFileName(filePath)
};
return response;
}

Using CSVHelper to output stream to browser

I'm trying to use CSVHelper to generate a CSV file and send it back to a browser, so the user can select a save location and filename and save the data.
The website is MVC based. Here' the jQuery button code I'm using to make the call (data is some serialised Json representation of a DTO list):
$.ajax({
type: "POST",
url: unity.baseUrl + "common/ExportPayments",
data: data
});
Here's the controller code:
[HttpPost]
public FileStreamResult ExportPayments()
{
MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms);
CsvWriter writer = new CsvWriter(sw);
List<Payment_dto> pd = _commonService.GetPayments();
foreach (var record in pd)
{
writer.WriteRecord(record);
}
sw.Flush();
return new FileStreamResult(ms, "text/csv");
}
Which seems to achieve precisely nothing - invoking the method steps into the correct bit of code but the response is empty, let alone offering the user a file dialog to save the data. I've stepped through this code, and it brings back data from the service, writes it, and throws no errors. So what am I doing wrong?
EDIT: Returning this ...
return File(ms.GetBuffer(), "text/csv", "export.csv");
... gives me a response, consisting of the csv-formatted data that I'm expecting. But the browser still doesn't seem to know what to do with it - no download option is offered to the user.
Try below code:
public FileStreamResult ExportPayments()
{
var result = WriteCsvToMemory(_commonService.GetPayments()());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(IEnumerable<Payment_dto> records)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream.ToArray();
}
}
Update
Below is how to pass a complex type model to an action method which is using GET HTTP method. I don't prefer this approach, it just gives you an idea there is an approach to achieve this.
Model
public class Data
{
public int Id { get; set; }
public string Value { get; set; }
public static string Serialize(Data data)
{
var serializer = new JavaScriptSerializer();
return serializer.Serialize(data);
}
public static Data Deserialize(string data)
{
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<Data>(data);
}
}
Action:
[HttpGet]
public FileStreamResult ExportPayments(string model)
{
//Deserialize model here
var result = WriteCsvToMemory(GetPayments());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
View:
#{
    var data = new Data()
    {
        Id = 1,
        Value = "This is test"
    };
}
#Html.ActionLink("Export", "ExportPayments", new { model = Data.Serialize(data) })
ASP.NET Core solution:
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8); // No 'using' around this as it closes the underlying stream. StreamWriter.Dispose() is only really important when you're dealing with actual files anyhow.
using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture, true)) // Note the last argument being set to 'true'
csvWriter.WriteRecords(...);
streamWriter.Flush(); // Perhaps not necessary, but CsvWriter's documentation does not mention whether the underlying stream gets flushed or not
memoryStream.Position = 0;
Response.Headers["Content-Disposition"] = "attachment; filename=somename.csv";
return File(memoryStream, "text/csv");
Try in the controller:
HttpContext.Response.AddHeader("content-disposition", "attachment; filename=payments.csv");
Could also user dynamic keyword for converting any data
Code from #Lin
public FileStreamResult ExportPayments()
{
var result = WriteCsvToMemory(_commonService.GetPayments()());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(dynamic records)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream.ToArray();
}
}

Using memorystream and DotNetZip in MVC gives "Cannot access a closed Stream"

I'm trying to create a zipfile in a MVC method using the DotNetZip components.
Here is my code:
public FileResult DownloadImagefilesAsZip()
{
using (var memoryStream = new MemoryStream())
{
using (var zip = new ZipFile())
{
zip.AddDirectory(Server.MapPath("/Images/"));
zip.Save(memoryStream);
return File(memoryStream, "gzip", "images.zip");
}
}
}
When I run it I get a "Cannot access a closed Stream" error, and I'm not sure why.
Don't dispose the MemoryStream, the FileStreamResult will take care once it has finished writing it to the response:
public ActionResult DownloadImagefilesAsZip()
{
var memoryStream = new MemoryStream();
using (var zip = new ZipFile())
{
zip.AddDirectory(Server.MapPath("~/Images"));
zip.Save(memoryStream);
return File(memoryStream, "application/gzip", "images.zip");
}
}
By the way I would recommend you writing a custom action result to handle this instead of writing plumbing code inside your controller action. Not only that you will get a reusable action result but bear in mind that your code is hugely inefficient => you are performing the ZIP operation inside the memory and thus loading the whole ~/images directory content + the zip file in memory. If you have many users and lots of files inside this directory you will very quickly run out of memory.
A much more efficient solution is to write directly to the response stream:
public class ZipResult : ActionResult
{
public string Path { get; private set; }
public string Filename { get; private set; }
public ZipResult(string path, string filename)
{
Path = path;
Filename = filename;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var response = context.HttpContext.Response;
response.ContentType = "application/gzip";
using (var zip = new ZipFile())
{
zip.AddDirectory(Path);
zip.Save(response.OutputStream);
var cd = new ContentDisposition
{
FileName = Filename,
Inline = false
};
response.Headers.Add("Content-Disposition", cd.ToString());
}
}
}
and then:
public ActionResult DownloadImagefilesAsZip()
{
return new ZipResult(Server.MapPath("~/Images"), "images.zip");
}
Couldn't comment.
Darin's answer is great! Still received a memory exception though so had to add response.BufferOutput = false; and because of that had to move content-disposition code higher.
So you have:
...
var response = context.HttpContext.Response;
response.ContentType = "application/zip";
response.BufferOutput = false;
var cd = new ContentDisposition
{
FileName = ZipFilename,
Inline = false
};
response.Headers.Add("Content-Disposition", cd.ToString());
using (var zip = new ZipFile())
{
...
Just in case it wasn't obvious :)

Resources