microsoft / fluentui-blazor

Microsoft Fluent UI Blazor components library. For use with ASP.NET Core Blazor applications
https://www.fluentui-blazor.net
MIT License
3.91k stars 377 forks source link

Retrieve IToastService trough IServiceProvider #2691

Open nullreferencez opened 2 months ago

nullreferencez commented 2 months ago

I'm attempting to retrieve the IToastService from a singleton class through IServiceProvider using the code below. However, although I do obtain an instance of IToastService , it does not display the toast.


  private readonly IServiceProvider _serviceProvider;

  public TaskService(IServiceProvider serviceProvider)
  {
          _serviceProvider = serviceProvider;
  }

  private async Task ProcessCompletedTask(PendingTask task)
  {
      //other code above
      using (var scope = _serviceProvider.CreateScope())
      {
          var toastService = scope.ServiceProvider.GetRequiredService<IToastService>();
          toastService?.ShowSuccess($"Uploaded and processed {document.Name}");
      }
   }
vnbaaij commented 2 months ago

HI,

Please supply us with ready-to-run reproduction code in the form of something we can copy/paste, a (zipped) project structure or a GitHub repository.

We do not have capacity to craft or compose a reproduction for every issue that gets raised.

If no code or repository is provided, this issue will be closed in 3 days

Help us to help you. Thanks.

Hwiet commented 2 months ago

IToastService is injected as a scoped service if you look at the implementation of AddFluentUIComponents. As is, not cannot be injected into a singleton service.

In addition, from my experience (with FluentUI and even with MudBlazor), I had to re-inject the ToastService as a singleton class in order for it to be injected into a class that is not a Blazor component, even if the class was scoped or transient.

vnbaaij commented 2 months ago

Good catch @Hwiet!

Closing this as it is an unsupported scenario.

nullreferencez commented 2 months ago

IToastService is injected as a scoped service if you look at the implementation of AddFluentUIComponents. As is, not cannot be injected into a singleton service.

In addition, from my experience (with FluentUI and even with MudBlazor), I had to re-inject the ToastService as a singleton class in order for it to be injected into a class that is not a Blazor component, even if the class was scoped or transient.

You are correct brother.

It appears that AddFluentUIComponents needs to be fixed to become a singleton, making it accessible throughout the Blazor application. I have tested it thoroughly and observed no negative effects from converting it into a singleton.

vnbaaij commented 2 months ago

I'm re-opening this with a vNext label so we can see if it is indeed do-able to change these to singleton

vnbaaij commented 2 months ago

In the meantime, nothing is stopping you from creating your own AddFluentUIComponents-alternative that injects the required/needed services as singleton into the service collection...

nullreferencez commented 2 months ago

In the meantime, nothing is stopping you from creating your own AddFluentUIComponents-alternative that injects the required/needed services as singleton into the service collection...

So I indeed changed it to a AddSingleton however there is a strange behavior. I can create Toasts and remove them from another singleton class however it fails to update a Toast without error message.

Now when I looked into it when updating a Toast instance if you create a new ToastParameters like below it wont update.

            _toastService.UpdateToast($"{task.Id}_processing", new ToastParameters<ProgressToastContent>()
            {
                Id = $"{task.Id}_processing",
                Intent = ToastIntent.Progress,
                Title = "Generating Requirements Matrix",
                Timeout = 0,
                Content = new ProgressToastContent()
                {
                    Details = $"Requirements found: {task.Progress}",
                },
            });

The issue seems to stem from that it must be the same original ToastParameters object.

For now I added a new function to at least get my use case working with adding the function below to FluentToastProvider.cs.

    private void UpdateToastProgressContent(string? toastId, ProgressToastContent Content)
    {
        _ = InvokeAsync(() =>
        {
            ToastInstance? toastInstance = _toastList.SingleOrDefault(x => x.Id == toastId);
            if (toastInstance is not null)
            {
                toastInstance.Content = Content;
                StateHasChanged();
            };
        });
    }
mmaderic commented 2 months ago

I've also encountered a similar issue. I have a scenario where I have to use a dedicated service scope.

Example: var service = (_scope ??= serviceScopeFactory.CreateAsyncScope()).ServiceProvider.GetRequiredService<TService>();

If the resolved service has IToastService as a dependency, it won't show any messages.

vnbaaij commented 2 months ago

I've also encountered a similar issue. I have a scenario where I have to use a dedicated service scope.

Example: var service = (_scope ??= serviceScopeFactory.CreateAsyncScope()).ServiceProvider.GetRequiredService<TService>();

If the resolved service has IToastService as a dependency, it won't show any messages.

You can't just have IToastService alone. You als need to have the implementation. As can be seen in AddFluentUIComponents, we add services.AddScoped<IToastService, ToastService>();

mmaderic commented 2 months ago

I understand, but messages won't be shown if the consuming service has IToastService as a dependency and that consuming service is being resolved from a dedicated scope created somewhere in the code.

I assume this is a limitation because the component responsible for rendering the messages will have a different instance of IToastService than the one created here from the dedicated scope or at least subscribe to its events.

We cannot have IToastService as a singleton, and it seems designed to work within the rendered UI context scope. It will be best to rethink my approach and stop having direct UI dependencies in my data processing services, which I create in a dedicated scope.

I've taken this approach because of the server-side simultaneous data processing between the components and EF's limitations regarding that. Still, I was looking to use the toast for error messages, but I will have to change it, and it completely makes sense to me now.

dominicusmento commented 1 month ago

Same problem here.. I need to access IDialogService from singleton (wasm) and if used with using (var scope = serviceProvider.CreateScope()), and as expected it throws: System.ArgumentNullException: needs to be added to the main layout of your application/site.. We need at least additional AddFluentUIComponents methods for singleton, or bool parameter in currentMethod to include as singleton..

mmaderic commented 2 weeks ago

That is true, @dominicusmento. A useful use case for a singleton is Wasm applications. My context is server-rendered, so it must be scoped there.

svrooij commented 1 week ago

May I suggest a AddFluentUIComponents(Lifetime serviceLifeTime = Lifetime.Scoped)? This way the developer can pick which ever lifetime that suits their needs?

nullreferencez commented 5 days ago

When is the next major release planned is there a roadmap?

vnbaaij commented 5 days ago

When is the next major release planned is there a roadmap?

Next major version is v5. We are currently working on that and have talked about it before in many issues. It will use the next version of the Web Components (v3).

It will be released when it is ready. We are doing this as a side-project so no timelines set (as always).