Search code examples
c#blazor.net-8.0fluent-ui

Blazor .NET 8 - FluentDialogProvider panel isn't showing


I'm trying to get the FluentDialogProvider working so it will show a panel. This is the error I'm getting:

warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100]
      Unhandled exception rendering component: <FluentDialogProvider /> needs to be added to the main layout of your application/site. (Parameter 'OnShowAsync')      System.ArgumentNullException: <FluentDialogProvider /> needs to be added to the main layout of your application/site. (Parameter 'OnShowAsync')
         at Microsoft.FluentUI.AspNetCore.Components.DialogService.ShowDialogAsync[TData](Type dialogComponent, TData data, DialogParameters parameters) in /_/src/Core/Components/Dialog/Services/DialogService-Dialog.cs:line 17
         at Microsoft.FluentUI.AspNetCore.Components.DialogService.ShowPanelAsync[TDialog](Object data, DialogParameters parameters) in /_/src/Core/Components/Dialog/Services/DialogService-Dialog.cs:line 56
         at S2IX.NodeManager.UI.Web.Shared.Nodes.NodeSnapshots.OpenPanelRightAsync(Guid server) in E:\repos\nodemanager\NodeManager\S2IX.NodeManager.UI.Web\Shared\Nodes\NodeSnapshots.razor:line 108
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
      Unhandled exception in circuit 'BvKsO6dDjcGDj2ubE1pbKa4oPkFQ1ZRukm-frbIFvGI'.
      System.ArgumentNullException: <FluentDialogProvider /> needs to be added to the main layout of your application/site. (Parameter 'OnShowAsync')
         at Microsoft.FluentUI.AspNetCore.Components.DialogService.ShowDialogAsync[TData](Type dialogComponent, TData data, DialogParameters parameters) in /_/src/Core/Components/Dialog/Services/DialogService-Dialog.cs:line 17
         at Microsoft.FluentUI.AspNetCore.Components.DialogService.ShowPanelAsync[TDialog](Object data, DialogParameters parameters) in /_/src/Core/Components/Dialog/Services/DialogService-Dialog.cs:line 56
         at S2IX.NodeManager.UI.Web.Shared.Nodes.NodeSnapshots.OpenPanelRightAsync(Guid server) in E:\repos\nodemanager\NodeManager\S2IX.NodeManager.UI.Web\Shared\Nodes\NodeSnapshots.razor:line 108
         at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

I have registered the components in my Startup.cs as well as added the DialogueService as scoped. The is in my MainLayout.cs as well within the section. Not really sure what's going on here as I feel like I've followed the documentation correctly.

Program.cs:

builder.Services.AddScoped<DialogService>();
builder.Services.AddFluentUIComponents();

Mainlayout.cs:

@inherits LayoutComponentBase
@using S2IX.NodeManager.UI.Web.Shared.CommandBars
@using S2IX.NodeManager.Models

<FluentLayout Style="display: flex; height: auto;">
    <FluentHeader style="background-color:#29ABE2; height: 75px;">
        <img src="/Images/s2ixbrand.png" alt="S2IX" />
    </FluentHeader>

    <FluentBodyContent>
        <FluentStack Orientation="Orientation.Horizontal" Style="justify-content: space-between; ">
            @if (collapseMenu)
            {
                <FluentStack Orientation="Orientation.Vertical" Style="width: auto; height: 100vh;">
                    <NavMenu />
                </FluentStack>
            }
            <main style="width:auto;">
                <div class="content">
                    <article id="article">
                        <AppCommandBar />
                        @Body
                    </article>
                </div>
                <FluentToastProvider MaxToastCount="5" Position="ToastPosition.BottomRight" RemoveToastsOnNavigation="true" />
                <FluentDialogProvider />
                <FluentTooltipProvider />
                <FluentMessageBarProvider />
            </main>
        </FluentStack>
    </FluentBodyContent>

</FluentLayout>



@code {
        bool collapseMenu = true;

        void ToggleMenu()
        {
            collapseMenu = !collapseMenu;
            StateHasChanged();
        }
}

NodeSnapshots.cs:

@inject ApplicationStateService stateService
@inject NavigationManager navMan
@inject IDialogService DialogService

@rendermode RenderMode.InteractiveServer


@if (!nodesFound)
{
    @if (!showDialog)
    {
        <FluentProgressRing>Loading Nodes...</FluentProgressRing>
    }
    else
    {
        <p>No Nodes found</p>
    }
}
else
{
    <FluentDataGrid Items="@FilteredItems" ResizableColumns="false" Pagination="@pagination" Style="overflow:auto;">
        <PropertyColumn Title="Name" Property="@(n => n.Name)" Sortable="true">
            <ColumnOptions>
                <div class="search-box">
                    <FluentSearch type="search" Autofocus="true" @bind-Value=nodeFilter @oninput="HandleNameFilter" @bind-Value:after="HandleClear" Placeholder="Node name..." />
                </div>
            </ColumnOptions>
        </PropertyColumn>
        <PropertyColumn Title="Description" Property="@(n => n.Description)" Sortable="false" />
        <TemplateColumn Title="Servers" Align="Align.Start">
            <p @onclick="@(() => OpenPanelRightAsync(@context.Servers[0]))">@context.Servers.Count</p>
        </TemplateColumn>
        <TemplateColumn Title="" Align="Align.Start">
            <FluentIcon Value="@(new Icons.Regular.Size24.Edit())" OnClick="@(() => Edit(@context))" />
        </TemplateColumn>
    </FluentDataGrid>

}



@code {
    // UI
    private PaginationState pagination = new PaginationState { ItemsPerPage = 20 };
    private bool showDialog = false;
    private IDialogReference? _dialog;

    // Nodes
    IQueryable<S2IXNodeDto>? nodesGrid;
    bool nodesFound = false;

    // Filter
    string nodeFilter = string.Empty;
    IQueryable<S2IXNodeDto>? FilteredItems => nodesGrid?.Where(n => n.Name.Contains(nodeFilter, StringComparison.CurrentCultureIgnoreCase));

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            stateService.SetAddNode(false);
            stateService.SetEditNode(false);
            stateService.SetAddSelectedNode(null);

            using (HttpClient httpClient = new HttpClient())
            {
                NodeClient nodeClient = new NodeClient(httpClient);
                var result = await nodeClient.GetAllNodes();

                nodesGrid = result.OrderBy(n => n.Name).AsQueryable();
                nodesFound = nodesGrid.Any();

                if (!nodesFound)
                {
                    showDialog = true;
                }
            }
        }
        StateHasChanged();
    }

    private void Edit(S2IXNodeDto nodeDto)
    {
        stateService.SetAddSelectedNode(nodeDto);
        stateService.SetEditNodeNetwork(true);
        navMan.Refresh(true);
    }
    private void HandleClear()
    {
        if (string.IsNullOrEmpty(nodeFilter))
        {
            nodeFilter = string.Empty;
        }
    }
    private void HandleNameFilter(ChangeEventArgs args)
    {
        if (args.Value is string value)
        {
            nodeFilter = value;
        }
    }
    private async Task OpenPanelRightAsync(Guid server)
    {
        using (HttpClient httpClient = new HttpClient())
        {
            ServerClient serverClient = new ServerClient(httpClient);
            ServerDto result = await serverClient.GetServer(server.ToString());

            _dialog = await DialogService.ShowPanelAsync<SimpleServerPanel>(result, new DialogParameters<ServerDto>()
            {
                Content = result,
                Title = $"Hello {result.Name}",
                Alignment = HorizontalAlignment.Right,
                PrimaryAction = "Close"
            });
            DialogResult panel = await _dialog.Result;
            HandlePanel(panel);
        }
    }

    private static void HandlePanel(DialogResult result)
    {
        if (result.Cancelled)
        {
            return;
        }

        if (result.Data is not null)
        {
            var server = result.Data as ServerDto;
            return;
        }
    }
}

SimpleServerPanel.razor:

@implements IDialogContentComponent<ServerDto>

<FluentDialogBody>
    <h3>Servers</h3>

    <FluentTextField @bind-Value="@Content.Name">This is a test:</FluentTextField>

</FluentDialogBody>



@code 
{
    [Parameter]
    public ServerDto Content { get; set; } = default!;
}

Any help would be greatly appreciated!


Solution

  • The answer was that within my MainLayout, I had to add @rendermode="RenderMode.InteractiveServer" to the FluentDialogProvider e.g.:

    <FluentDialogProvider @rendermode="RenderMode.InteractiveServer"/>
    

    It's a shame that this isn't mentioned in the documentation.