Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
1 task done
mreic opened this issue Sep 14, 2023 · 22 comments
Closed
1 task done

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

mreic opened this issue Sep 14, 2023 · 22 comments
Labels
bug Something isn't working

Comments

@mreic
Copy link

mreic commented Sep 14, 2023

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

  • I agree to follow this project's Code of Conduct
@mreic mreic added the bug Something isn't working label Sep 14, 2023
@fgilde
Copy link
Owner

fgilde commented Sep 14, 2023

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
Copy link
Author

mreic commented Sep 14, 2023

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
Copy link
Author

mreic commented Sep 14, 2023

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
Copy link
Owner

fgilde commented Sep 14, 2023

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
Copy link
Owner

fgilde commented Sep 14, 2023

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
Copy link
Owner

fgilde commented Sep 14, 2023

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
Copy link
Author

mreic commented Sep 14, 2023

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

I cant Upgrade because of a Bug i reported in MudBlazor (MudBlazor/MudBlazor#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
Copy link
Owner

fgilde commented Sep 15, 2023

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
Copy link
Author

mreic commented Sep 18, 2023

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
Copy link
Author

mreic commented Sep 18, 2023

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
Copy link
Owner

fgilde commented Sep 18, 2023

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
Copy link
Author

mreic commented Sep 18, 2023

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
Copy link
Owner

fgilde commented Sep 18, 2023

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
Copy link
Owner

fgilde commented Sep 18, 2023

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
Copy link
Author

mreic commented Sep 19, 2023

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
Copy link
Author

mreic commented Sep 19, 2023

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

@fgilde
Copy link
Owner

fgilde commented Sep 19, 2023

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
Copy link
Author

mreic commented Sep 19, 2023

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

@fgilde
Copy link
Owner

fgilde commented Sep 19, 2023

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
Copy link
Author

mreic commented Sep 19, 2023

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
Copy link
Owner

fgilde commented Sep 19, 2023

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
Copy link
Author

mreic commented Sep 20, 2023

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

@mreic mreic closed this as completed Sep 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants