Blazored / Modal

A powerful and customizable modal implementation for Blazor applications.
https://blazored.github.io/Modal/
MIT License
763 stars 184 forks source link

[Bug] Blazor Server - Modal cannot be closed in background thread #504

Open HenryDeveloper opened 1 year ago

HenryDeveloper commented 1 year ago

Describe the bug I am working on a Blazor Server + RabbitMQ application (with Rebus package), when a new message arrives to the Consumer I need a modal to be opened and after the process is finished the modal is closed, the modal works perfectly when the method is called Modal.Show<DisplayMessage>() but doesn't work when it needs to be closed modal.Close().

To Reproduce Steps to reproduce the behavior:

  1. Use Rebus
  2. Send a message to RabbitMQ server
  3. The message is handled by the consumer
  4. Calls an event in the UI, the modal is opened correctly, but is not closed when some code in between ends

Expected behavior The modal would have to be closed once we call the method .close

Hosting Model (is this issue happening with a certain hosting model?):

Thanks in advance for your help.

UweReisewitz commented 10 months ago

This also happens when Show() is called from a Timer Event.

Show() needs to be done from the UI Thread. I have a MAUI Blazor Hybrid App. When I call Show() from the Main Thread (via MainThread.BeginInvokeOnMainThread) then it works a expected. If I call Show() without switching to the Main Thread it behaves as described above.

Thanks in advance for your help.

Addendum: Thinking about it, maybe it's just that Show() and Close() need to be made from the same (not necessarily the UI-) Thread.

UweReisewitz commented 10 months ago

I have created a workaround in my app that enforces the Show() and Hide() to the UI-Thread by routing it to a Component and call them through InvokeAsync(). This works. Maybe Show() and Hide() should do the same.

chrissainty commented 10 months ago

Hi @UweReisewitz.

The handler for the event triggered by the Show and Hide methods does use InvokeAsync to execute the StateHasChanged call.

https://github.com/Blazored/Modal/blob/f27ab5f14f6a43739cafc6f696eb40f60920843f/src/Blazored.Modal/BlazoredModal.razor#L138

UweReisewitz commented 10 months ago

Hi, @chrissainty, the problem seems to arise when Show() and/or Close() are not called on the UI-Thread. In my case Show() was called from a different Thread (Timer Event) and Close() was called from the UI Thread. After I forced both calls into the UI Thread it worked again. I'm sorry that I am not familiar with the inner workings, therefore I can only speculate here. Would it be possible to move the current contents of Show() and Close() into new private methods and call them via InvokeAsync() from the original Methods?

jasperzeinstra commented 4 months ago

I also had this issue. I had a modal that I wanted to close based on a timer. But the timer was indeed running on a different thread. First i thought the issue was in the following piece of code. I thought StateHasChanged(); should have been replaced by InvokeAsync(StateHasChanged);.

    public async Task CloseAsync(ModalResult modalResult)
    {
        // Fade out the modal, and after that actually remove it
        if (AnimationType is ModalAnimationType.FadeInOut)
        {
            OverlayAnimationClass += " fade-out";
            StateHasChanged();

            await Task.Delay(400); // Needs to be a bit more than the animation time because of delays in the animation being applied between server and client (at least when using blazor server side), I think.
        }
        else if (AnimationType is ModalAnimationType.PopInOut)
        {
            OverlayAnimationClass += " pop-out";
            StateHasChanged();

            await Task.Delay(400);
        }

        await Parent.DismissInstance(Id, modalResult);
    }

Now I've resolved it by doing the CloseAsync itself in a InvokeAsync in the method when my Timer is finished:

InvokeAsync(ModalInstance.CancelAsync());

@chrissainty maybe you can add this trick to the documentation?