2

I'm trying to use a Blazor input file, and also the Imagesharp library, to turn an IBrowserFile into an image.
My method looks like this

public async Task<byte[]> ConvertFileToByteArrayAsync(IBrowserFile file)
        {

            using var image = Image.Load(file.OpenReadStream());
            image.Mutate(x => x.Resize(new ResizeOptions
            {
                Mode = ResizeMode.Min,
                Size = new Size(128)
            }));

            MemoryStream memoryStream = new MemoryStream();
            if (file.ContentType == "image/png")
            {

                await image.SaveAsPngAsync(memoryStream);
            }
            else
            {
                await image.SaveAsJpegAsync(memoryStream);
            }
            var byteFile = memoryStream.ToArray();
            memoryStream.Close();
            memoryStream.Dispose();


            return byteFile;
            
        } 

But I'm getting the following error :

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Synchronous reads are not supported.
System.NotSupportedException: Synchronous reads are not supported.
   at Microsoft.AspNetCore.Components.Forms.BrowserFileStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.Stream.CopyTo(Stream destination, Int32 bufferSize)
   at SixLabors.ImageSharp.Image.WithSeekableStream[ValueTuple`2](Configuration configuration, Stream stream, Func`2 action)
   at SixLabors.ImageSharp.Image.Load(Configuration configuration, Stream stream, IImageFormat& format)
   at SixLabors.ImageSharp.Image.Load(Configuration configuration, Stream stream)
   at SixLabors.ImageSharp.Image.Load(Stream stream)
   at MasterMealWA.Client.Services.FileService.ConvertFileToByteArrayAsync(IBrowserFile file) in F:\CoderFoundry\Code\MasterMealWA\MasterMealWA\Client\Services\FileService.cs:line 37
   at MasterMealWA.Client.Pages.RecipePages.RecipeCreate.CreateRecipeAsync() in F:\CoderFoundry\Code\MasterMealWA\MasterMealWA\Client\Pages\RecipePages\RecipeCreate.razor:line 128
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

for the record, line 37 is "using var image......", I don't quite see where I'm using multiple streams, unless its the read stream and memory stream. however, I also don't see how to close the stream I open with file.OpenReadStream.

H H
  • 263,252
  • 30
  • 330
  • 514
sbrevolution5
  • 183
  • 2
  • 11
  • I'll admit I'm surprised that ImageSharp works at all on Blazor - I guess it's using their 100% managed codepath without P/Invoking into other graphics libraries? What's the performance like? You _might_ be better-off by doing this in raw JavaScript and `` though. – Dai Sep 04 '21 at 16:43
  • 2
    Anyway, the problem is that ASP.NET Core (by default) discourages people using non-async stream methods *on ASP.NET Core's own streams* (such as `IBrowserFile.OpenReadStream()`). To fix this use `Image.LoadAsync` instead. – Dai Sep 04 '21 at 16:44
  • so your fix below certainly worked, but you were also right about the performance impact. It takes 15-30 sec for it to process the image on the user's side. I guess I need to rethink this. – sbrevolution5 Sep 05 '21 at 01:22
  • 1
    As I said, to resize images on the client just use `` - it's _lightning fast_ and hardware-accelerated in all major browsers I know of. As for the poor performance you're seeing: that's just how Blazor works: it's performing CPU-intensive bitmap operations in WASM in a single CPU thread. As much as I'm impressed by Blazor's cool demos I'm not convinced it's a good idea to build a production web-application in it. – Dai Sep 05 '21 at 02:00
  • @HenkHolterman I accepted it because it elaborated well and explained where I went wrong. It only didn't work because I was taking the wrong approach in the first place. It answered my question properly, but the failure was on my part, not anyone providing advice here. – sbrevolution5 Sep 05 '21 at 15:33

4 Answers4

4

Background:

Proper solution:

You're calling ImageSharp's Image.Load method, which uses non-async Stream methods. The fix is to simply use await Image.LoadAsync instead:

So change your code to this:

// I assume this is a Controller Action method
// This method does not return an IActionResult because it writes directly to the response in the action method. See examples here: https://stackoverflow.com/questions/42771409/how-to-stream-with-asp-net-core

public async Task ResizeImageAsync( IBrowserFile file )
{
    await using( Stream stream = file.OpenReadStream() )
    using( Image image = await Image.LoadAsync( stream ) )
    {
        ResizeOptions ro = new ResizeOptions
        {
            Mode = ResizeMode.Min,
            Size = new Size(128)
        };

        image.Mutate( img => img.Resize( ro ) );

        if( file.ContentType == "image/png" ) // <-- You should not do this: *never trust* the client to be correct and truthful about uploaded files' types and contents. In this case it's just images so it's not that big a deal, but always verify independently server-side.
        {
            this.Response.ContentType = "image/png";
            await image.SaveAsPngAsync( this.Response.Body );
        }
        else
        {
            this.Response.ContentType = "image/jpeg";
            await image.SaveAsJpegAsync( this.Response.Body );
        }
}

Alternative (non-)solution: Procrastinate:

Just disable ASP.NET Core's prohibition on non-async IO:

public void ConfigureServices(IServiceCollection services)
{
    // If using Kestrel:
    services.Configure<KestrelServerOptions>(options =>
    {
        options.AllowSynchronousIO = true;
    });

    // If using IIS:
    services.Configure<IISServerOptions>(options =>
    {
        options.AllowSynchronousIO = true;
    });
}
Dai
  • 141,631
  • 28
  • 261
  • 374
  • You really shouldn't have even mentioned the "Procrastinate" option. You probably just talked the OP out of doing the right thing. – Bennyboy1973 Sep 04 '21 at 22:24
  • For the record, I'm not reading the procrastinate option because the first one seems to work. Thanks a ton! – sbrevolution5 Sep 05 '21 at 01:14
  • This is incorrect. The exception is from Wasm. There is no asp.net Server here and no Response and no Response.Stream. – H H Sep 05 '21 at 05:29
  • You are right about using the Canvas instead though. – H H Sep 05 '21 at 06:54
  • @HenkHolterman You are correct - but I’m also correct: the Stream class in the WASM has the same async-only checks as the server-side stream classes. It’s really… uh… cool, to see that level of fidelity in such extremely different environments. – Dai Sep 05 '21 at 10:01
3

Image.Load is a synchronous operation. Try with the async version instead:

using var image = await Image.LoadAsync(file.OpenReadStream());
PEK
  • 3,688
  • 2
  • 31
  • 49
2

You have to call async methods, like LoadAsync, DisposeAsync() instead of the synchronous Dispose(). Use await using xxx to await the call to DisposeAsync().

public async Task<byte[]> ConvertFileToByteArrayAsync(IBrowserFile file)
{
    await using var image = await image.LoadAsync(file.OpenReadStream());
    image.Mutate(x => x.Resize(new ResizeOptions
    {
        Mode = ResizeMode.Min,
        Size = new Size(128)
    }));

    MemoryStream memoryStream = new MemoryStream();
    if (file.ContentType == "image/png")
    {

        await image.SaveAsPngAsync(memoryStream);
    }
    else
    {
        await image.SaveAsJpegAsync(memoryStream);
    }
    var byteFile = memoryStream.ToArray();
    memoryStream.Close();
    await memoryStream.DisposeAsync();

    return byteFile;
}
Peter Bons
  • 26,826
  • 4
  • 50
  • 74
  • This is incorrect. The exception is because ASP.NET Core is providing the `Stream` exposed by `file.OpenReadStream()` - and by default ASP.NET Core throws on non-async `Read`/`Write` calls to discourage people from using non-async IO. The exception has nothing to do with not using `DisposeAsync`. – Dai Sep 04 '21 at 16:47
  • @Dai I might have missed the synchronous load but I did recieve the same error in another scenario in which using the async dispose did solve that problem. – Peter Bons Sep 04 '21 at 17:36
0

I think they changed something. For me only this code worked.

Image image = Image.LoadAsync(file.OpenReadStream().Result)

  • Your response does not answer the question, and the line of code you have supplied has an unmatched closing brace. Overall this response is unhelpful. – Theo Aug 09 '23 at 19:58
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 09 '23 at 19:58
  • @Theo "overall" this response is the only one that will work on the current version of Blazor. – Jester1337 Aug 10 '23 at 03:09