0

I'm converting ASP.NET WebForms code to ASP.NET Core Razor pages which is new to me. I'm trying to retrieve an image MemoryStream from a business class (based on SixLabors awesome ImageSharp) and have the page render the JPEG -- no HTML, just the image. I intend to use this page elsewhere as an <img> src, like <img src="Render?imageID=42&mode=invert" />

In Render.cshtml.cs:

public class RenderModel : PageModel
{
    public void OnGet()
    {
        //snip

        Stream stream = new MemoryStream();

        using (Image image1 = Image.Load(imagePath))
        {
            SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder encoder = new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder();
            encoder.Quality = 75;

            image1.Save(stream, encoder);
            //image.Save("/temp/xxx.jpg", encoder); //test to see image. it works
        }

        Response.Clear();
        //Response.Headers.ContentLength = stream.Length;
        Response.ContentType = "image/jpeg";
        Response.Body = stream;
    }
}

...but this is not working, I get:

System.InvalidOperationException: Response Content-Length mismatch: too few bytes written (0 of 135408).

135408 is the stream.Length.

I'm probably not doing this correctly in the ASP.NET Core/Razor way. Can anyone set me straight as to how to do this? Thanks!

EDIT: commenting out the Headers.ContentLength fixes the error. But now I get a broken-image icon in the browser. Closer...

mdelvecchio
  • 567
  • 1
  • 5
  • 25
  • You can read this post, it might help: https://stackoverflow.com/questions/61351582/response-content-length-mismatch-too-few-bytes-written – Tupac Mar 01 '22 at 03:05
  • Thanks; that helps - they suggest not setting the Headers.ContentLength. The page no longer throws an exception, and renders broken image. Something isn't right, but it's closer – mdelvecchio Mar 02 '22 at 20:10
  • How do you use the Response Body in your Razor Page? And wich Version of .net core are you using? – Jonas Weinhardt Mar 03 '22 at 12:04
  • I'm using it in the OnGet() method. It's partially functional -- my Razor page now returns a content-type of JPEG to the browser; albeit a broken image. It's close but not there. (This was super easy in ASP and ASP.NET) – mdelvecchio Mar 03 '22 at 17:28
  • I'm using 6.0.2 – mdelvecchio Mar 03 '22 at 17:38

2 Answers2

2

You need to write to the Response.Body isntead of replacing it.

stream.Seek(0, SeekOrigin.Begin);
await stream.CopyToAsync(Response.Body);
await Response.Body.FlushAsync();
Kahbazi
  • 14,331
  • 3
  • 45
  • 76
  • Tried it, does not resolve the issue -- no exception but browser renders the result as a broken image. – mdelvecchio Mar 07 '22 at 18:00
  • @mdelvecchio I updated the answer. check it out see if it works. – Kahbazi Mar 08 '22 at 14:19
  • The await keyword requires me to change the OnGet() to "public async void OnGet()", but then it works! I like this answer better because I don't have to change the return type of the OnGet() from its default. And it address my original error -- replacing the Response.Body instead of writing to it. – mdelvecchio Mar 08 '22 at 17:56
  • @mdelvecchio `async void` would not work properly, because it's `fire and forget`. you need to change your method to `public async Task OnGet()` – Kahbazi Mar 08 '22 at 20:03
  • I see. Is there value in using it at all, versus simply removing the `await` keywords and the `async` keyword? It seems to render the JPEG with them removed. I use this Render.cshtml page as a src tag for `` tags – mdelvecchio Mar 08 '22 at 20:22
  • 1
    Yes there is. using async await won't block the thread while the image is sent to response. – Kahbazi Mar 08 '22 at 20:23
1

I think Razor pages are intented to return html content.

However it seems to be possible to return different types of result in OnGet e.g. you could return a new FileContentReset (FileStreamResult seems to have issues with contentlength)

// read as bytes 
public FileContentResult OnGet()
{
    var image = System.IO.File.ReadAllBytes(@"c:\temp\myimage.jpeg");
    return new FileContentResult(image.ToArray(), "image/jpeg");

}
// example if image comes from stream
public FileContentResult OnGet()
{
    using var imageStr = System.IO.File.Open(@"c:\temp\myimage.jpeg", FileMode.Open);
    using var memStr = new MemoryStream();
    imageStr.CopyTo(memStr);
    return new FileContentResult(memStr.ToArray(), "image/jpeg");

}

Even better maybe it to not use a Razor page and to add a MVC controller to return the result.

rekna
  • 5,313
  • 7
  • 45
  • 54
  • Well Razor is kind of a subset of MVC and designed for more page-centric ASP.NET development. I don't think it should be necessary to add MVC to the Razor project (if this is possible, no idea). – mdelvecchio Mar 03 '22 at 17:34
  • Interesting idea on the FileStreamResult, thanks. Unfortunately it didn't work, returns the "System.InvalidOperationException: Response Content-Length mismatch: too few bytes written (0 of XXX)." Hmm. – mdelvecchio Mar 03 '22 at 17:36
  • 1
    Just tested with FileContentResult, which seems to work: using var memoryStr = new MemoryStream(); stream.CopyTo( memoryStr ); return new FileContentResult( memoryStr.ToArray() ,new MediaTypeHeaderValue("image/jpeg")) – rekna Mar 04 '22 at 14:24
  • Hmm, you can get is to work with filestreamresult but not in the OnGet .. You have to add another method OnDownload, the link to this has to be Render?handler=Download. I think it might be that in the OnGet the html result and the image result may interfere with each other, as the OnGet maybe still tries to return the HTML result as well. – rekna Mar 04 '22 at 14:49
  • FileContentResult() doesn't take in MediaTypeHeaderValue("image/jpeg") as the second parameter, but it does take in string "image/jpeg". This now works, returning the OnGet() as type FileContentResult! – mdelvecchio Mar 07 '22 at 17:57
  • I also changed my image's stream type from Stream to MemoryStream. Do you want to post a new answer, citing FileContentResult as the return type? it's: return new FileContentResult(stream.ToArray(), "image/jpeg"); – mdelvecchio Mar 07 '22 at 18:04
  • I adjusted my original answer – rekna Mar 07 '22 at 21:07
  • Thanks! Tho it's not better to use MVC. MVC is one technique but when it comes to an empty page that just returns a byte array of an image, it's not needed to use a view and a controller just because. Simpler is better :) – mdelvecchio Mar 12 '22 at 00:20