fgilde / MudBlazor.Extensions

MudBlazor.Extensions from https://www.mudex.org is a small extension for MudBlazor from https://mudblazor.com
http://www.mudex.org
MIT License
242 stars 20 forks source link

[Bug]: MudExFileDisplay won't load file from Stream #43

Closed mreic closed 1 year ago

mreic commented 1 year ago

Contact Details

No response

What happened?

I have a Blazor Server with a upload folder where files are uploaded. If i want to display one of these files from stream using the following command:

await DialogService.ShowFileDisplayDialog(stream, uploadedFile.uploaded_file_orig_file_name, mimetype, HandleContentError, ex => ex.JsRuntime = JS);

the display control shows as follows: image

I checked if the stream is available, which it is.

Unfortunately i cannot use the newest version of MudEx because of a bug in MudBlazor since 6.5.

Expected Behavior

The file should be displayed.

Screenshots

No response

What application type are you referring to?

Other

Custom Application Type

Server

MudBlazor.Extension Version

1.7.61

MudBlazor Version

6.4.1

What browser are you using?

Chrome

Sample Solution

No response

Pull Request

No response

Code of Conduct

fgilde commented 1 year ago

Stream is fully available on client side? Can you check if it works with a data url for the stream?

DataUrl.GetDataUrl(stream.ToByteArray())

That means in your case

using Nextended.Blazor.Models;
...

await dialogService.ShowFileDisplayDialog(DataUrl.GetDataUrl(stream.ToByteArray()), uploadedFile.uploaded_file_orig_file_name, mimetype, HandleContentError, ex => ex.JsRuntime = JS);
mreic commented 1 year ago

What do you mean by "fully available on client side"? File is stored on the servers filesystem.

Testing as follows: image

This test does not work. If you uncomment the bytes-assignment and the second dialog call and comment out the first dialog call you can open small files. Large files get an "URI too Long"-Exception.

mreic commented 1 year ago

I just tested a little further: If i try opening the dialog your suggested way, then opening images are working fine, but other types, like .pdf or .json files result in a immediate download which looks like this: image Note, that the 0 Byte files in that download list have been produced while testing.

I wrote a little function myself like this: image

Using this allows me to open any files that are not too large for the URI.

fgilde commented 1 year ago

Ok understand. I also fixed stream handling but that doesnt help you at all. Because for native file display (That means without custom IMudExFileDisplay implementation like Zip or Markdown handling) also a is created from stream because depending on mimetype iframe, img tag or object tags are used and they need a url

fgilde commented 1 year ago

if youre files came from server and I would have you problems I would ensure to have a url for the file itself. For example if you have your files in a database (or whatever) i would create a Controller to read the files. I mean like this

using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
[ApiController]
public class FileController : ControllerBase
{
    private readonly IFileRepo _fileRepo;

    public FileController(IFileRepo fileRepo)
    {
        _fileRepo = fileRepo;
    }

    [HttpGet("{id}")]
    public IActionResult GetFileById(string id)
    {
        byte[] fileData = _fileRepo.GetFile(id);
        if (fileData == null || fileData.Length == 0)
        {
            return NotFound();
        }

        return File(fileData, "application/octet-stream"); // Set the appropriate MIME type if you know it.
    }
}

than you can use a url like api/file/123

Hope that helps a bit.

fgilde commented 1 year ago

Thank you very much for sharing this bug, i fixed it and it will released in 1.7.68 you can have a look at preview package 1.7.68-prev-2309141720-main

also you can see it running here https://mudex.azurewebsites.net/handle-file-as-streams (all files here are set as stream see code)

Would you explain why you cant update MudBlazor to 6.9 ?

mreic commented 1 year ago

I'll test it tomorow.. Thank you in advance!

I cant Upgrade because of a Bug i reported in MudBlazor (https://github.com/MudBlazor/MudBlazor/issues/7237). I use exactly that combination of options in MudDataGrid and unfortunately nobody is responding at this time.. but we will see. I personally cant find a working workaround for this so i am Stuck with this Version for the moment.

fgilde commented 1 year ago

I created a compatible package with MudBlazor 6.4.1 for you. Hope that helps. If it does please close this issue.

1.7.68-prev-2309150848-for-mudblazor-6.4.1.

mreic commented 1 year ago

Thanks for the Package! It helps for mostly all use-cases. Mostly, because i experienced that larger PDF-Files are not loaded. They can be downloaded, but the preview pane keeps empty.

Any Idea on this?

Update: It seems that i can update my project to something above 6.4.1 because somebody posted a workarround. I need to do some testing to validate this.

mreic commented 1 year ago

While further testing it seems that the file display components dont dispose properly. May be that its my code doing sth wrong, but RAM-usage increases on every dialog call and stays up even when GC takes place.

Sorry for asking so many questions ;)

fgilde commented 1 year ago

No problem your welcome.

For question 1: Pdf and all other native display files like image etc where tag is used needs an url, and maybe your pdf is to big for a data url. I guess you need to playaround with it, or if you can provide a sample pdf I will have a look if I can do something.

For question 2: Stream is currently not disposed on component side because I dont want to close given streams because this can cause side effects. (However for MudExFileDisplayZip they are copied and maybe I need to rethink about disposing given streams )

mreic commented 1 year ago

For your first answer: I once build a simple file preview (mostly only pdf and images) facing the nearly same problem with larger files. I found a way to do this using following function in "_host.cshtml" :

window.openFileFromStream = async (fileName, contentStreamReference, fileType) => {
    const arrayBuffer = await contentStreamReference.arrayBuffer();
    const blob = new Blob([arrayBuffer], { type: fileType });
    const url = URL.createObjectURL(blob);
    //document.querySelector("embed").src = url;

    const elem = document.getElementById("preview-element");
    if(elem != null){
        elem.src = url;
        await new Promise(resolve => setTimeout(resolve, 1000));
        URL.revokeObjectURL(url);
    }
    else{
        URL.revokeObjectURL(url);
    }

}

I personally dont know if its a nearly clean idea to get rid of the file size issue. Maybe you can check?

Second answer: I am using this call:

string mimetype = Utilities.Utilities.GetMimeType(uploadedFile.uploaded_file_orig_file_name);

//await DialogService.ShowFileDisplayDialog($"https://localhost:1235/api/file/{uploadedFile.uploaded_file_id}", uploadedFile.uploaded_file_orig_file_name, mimetype, HandleContentError, ex => ex.JsRuntime = JS);

using (Stream stream = GetFileStream(uploadedFile))
{
    await DialogService.ShowFileDisplayDialog(stream, uploadedFile.uploaded_file_orig_file_name, mimetype, HandleContentError, ex => ex.JsRuntime = JS);
}

Seeing the code i would expect the stream to get closed and als release resources. Is there any possibilities for your component to keep data that was read from stream?

fgilde commented 1 year ago

Your stream should get disposed, but anyway I found a bug where copied streams are staying open. I currently work on a fix for that.

For point 1 it's a good idea to use object stores uri, I think I can change the behavior to work like this

fgilde commented 1 year ago

Can you test this package? 1.7.68-prev-2309182049-stre...

You can now set url handling to blob the MudExFileDisplay has now a Parameter for that called StreamUrlHandling If you use a dialog you can set it like this

    private async Task OpenAsDialog()
    {
        var parameters = new DialogParameters {
            { nameof(MudExFileDisplay.StreamUrlHandling), StreamUrlHandling.BlobUrl }
        };
        await dialogService.ShowFileDisplayDialog(SampleFile.Stream, SampleFile.Name, SampleFile.ContentType, HandleContentError, null, parameters);
    }

In this example its setup to use blob uris https://mudex.azurewebsites.net/handle-file-as-streams

mreic commented 1 year ago

Thank you for your always fast replies! Larger PDF work now! Unfortunatly .zip-files now are broken. image

The error is the same on calling the Dialog without the parameters.

mreic commented 1 year ago

Unfortunatelly, the info also causes crashes on all files in my test-environment image

fgilde commented 1 year ago

That's strange my samples with zip and other archives working well will correct stream dispose. Can you provide a sample solution with your errors? Than I can have a look

mreic commented 1 year ago

What exactly do you want to see? I want to get it as precise as possible for you!

fgilde commented 1 year ago

The zip and info bug reproducible should be enough. You have a server side rendered project? Maybe there is the difference. Because in my sample https://mudex.azurewebsites.net/handle-file-as-streams it works well

mreic commented 1 year ago

Yes, my project is rendered server side. I tried to open following zip: open3A 3.8.zip

Dialog starts showing the loading spinner while the debugger prints the error from the screenshot posted earlier ("Cannot access a closed stream)

Dialog call is exacly like you suggested:

Your code:

private async Task OpenAsDialog()
    {
        var parameters = new DialogParameters {
            { nameof(MudExFileDisplay.StreamUrlHandling), StreamUrlHandling.BlobUrl }
        };
        await dialogService.ShowFileDisplayDialog(SampleFile.Stream, SampleFile.Name, SampleFile.ContentType, HandleContentError, null, parameters);
    }

My code:

private async Task OpenAsDialog(UploadedFileModel uploadedFile)
{
    //DialogOptionsEx options = new DialogOptionsEx() { JsRuntime = JS};

    //Stream stream = null;
    try
    {
        string mimetype = Utilities.Utilities.GetMimeType(uploadedFile.uploaded_file_orig_file_name);

        //await DialogService.ShowFileDisplayDialog($"https://localhost:1235/api/file/{uploadedFile.uploaded_file_id}", uploadedFile.uploaded_file_orig_file_name, mimetype, HandleContentError, ex => ex.JsRuntime = JS);

        var parameters = new DialogParameters {
            { nameof(MudExFileDisplay.StreamUrlHandling), StreamUrlHandling.BlobUrl }
        };

        using (Stream stream = GetFileStream(uploadedFile))
        {
            await DialogService.ShowFileDisplayDialog(stream, uploadedFile.uploaded_file_orig_file_name, mimetype, HandleContentError, ex => ex.JsRuntime = JS, parameters);
            //await DialogService.ShowFileDisplayDialog(stream, uploadedFile.uploaded_file_orig_file_name, mimetype, HandleContentError, ex => ex.JsRuntime = JS);
        }
    }
    catch(Exception e)
    {
        Console.Write(e.ToString());
    }

}

Hope this helps ;)

fgilde commented 1 year ago

Sorry for Late answering today i was not at home. I see your problem. Your scope is finished to early because you are await the show of the dialog only what returns a IDialogReference from MudBlazor if you want to await the closeing to ensure dispose afterwards you need to await the result of the IDialogRefeference

       using (var stream = SampleFile.Stream) {
           var res = await dialogService.ShowFileDisplayDialog(stream, SampleFile.Name, SampleFile.ContentType, HandleContentError, null, parameters);
           await res.Result;
       }
mreic commented 1 year ago

Thanks for the Explanation! Seems like everything is working right now.