Declined
Last Updated: 27 Aug 2020 09:17 by ADMIN
Matt
Created on: 18 Aug 2020 19:27
Category: Drawer
Type: Bug Report
0
How do I reload the Drawer data after Azure AD authentication?

I hate to refer to this as a bug, as I'm sure there is something we are missing. Here's are the bullet points of the scenario:

  • We are using Telerik Drawer as our "NavMenu" component
  • We are using Azure AD for Authentication

In the OnLogInSucceeded section of our Authentication.razor, we are building the user specific menu from SQL tables, structuring it in a hierarchical DrawerItem list.

When I pass that back to the Data property of the TelerikDrawer, the display does not update. StateHasChanged doesn't have any impact. We have tried tying it to a "service" to listen for changes. We have even called an event that would manually make the update.


MAIN LAYOUT

@inherits LayoutComponentBase
@inject NavigationManager navigationManager
@inject PublicClient Http
@inject IMatToaster toast

@using G2_Field.Shared
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

@inject IAuthorizationService AuthorizationService
@inject Employee dbUSER

@attribute [Authorize]


    <TelerikRootComponent>
        <div>
            <NavMenu @ref="nm" items="@Data" />
        </div>

        <div class="app-main">
            <div class="app-bar">
                <div style="float:left">
                    <TelerikButton OnClick="@ToggleDrawer" Icon="@Telerik.Blazor.IconName.Menu" Class="k-flat" />
                </div>
                <div style="float:right">
                    <LoginDisplay />
                </div>
            </div>
            <div>
                @Body
            </div>
        </div>
    </TelerikRootComponent>
    <MatToastContainer />

    @code {
        public TelerikDrawer<DrawerItem> Drawer { get; set; }
        public DrawerItem SelectedItem { get; set; }
        public bool Expanded { get; set; } = true;
        NavMenu nm;

        public List<DrawerItem> Data { get; set; } =
        new List<DrawerItem>
        {
            new DrawerItem { Text = "HOME", Icon = Telerik.Blazor.IconName.Window, Description = "Home", URL="" }
        };

        public async Task ToggleDrawer() 
        {
            await nm.ToggleDrawer();
        }

        public async Task OnSidebarChange(List<DrawerItem> di)
        {
            Data = di;
            Console.WriteLine(di.Last().Children.Last().Text);
            await nm.RefreshMenu(di);
        }...

AUTHENTICATION

@page "/authentication/{Action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using G2_Field.Shared
@using System.Security.Claims
@inject Employee dbUSER
@inject PublicClient Http
@using System.Threading.Tasks

<RemoteAuthenticatorView Action="@Action" OnLogInSucceeded=@OnLogInSucceeded>
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView> 

@code{
    [Parameter] public string Action { get; set; }
    AppCategories[] iMenu = new AppCategories[0];
    [CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }

    [CascadingParameter] public Task<AuthenticationState> AuthenticationState { get; set; }
    [CascadingParameter] public List<DrawerItem> d { get; set; }
    [CascadingParameter(Name = "theMainLayout")] public MainLayout ml { get; set; }

    public async void OnLogInSucceeded()
    {
        var user = (await AuthenticationState).User;
        var un = (await AuthenticationState).User.Claims.ToList();
        var username = "";
        if (user.Identity.IsAuthenticated)
        {
            // Do some stuff
            Console.WriteLine("Log In Succeeded Event Fired");

            foreach (Claim u in un)
            {
                //Console.WriteLine(u.Type + " = " +  u.Value.ToString());
                if (u.Type == "preferred_username") { username = u.Value.ToString(); }
            }
            dbUSER = await Http.Client.GetJsonAsync<Employee>("/api/Index/GetCurrentEmployee/" + username);

            Console.WriteLine("EmpUserName=" + dbUSER.EmpUserName);

            var gUserName = dbUSER.EmpUserName;
            iMenu = await Http.Client.GetJsonAsync<AppCategories[]>("/api/Index/GetUserAppCategories/" + gUserName);

            d = new List<DrawerItem>();

            d.Add(new DrawerItem { Text = "HOME", Icon = "fa-home", Description = "Home", URL = "" });
            foreach (var app in iMenu)
            {
                List<DrawerItem> y = new List<DrawerItem>();
                foreach (var cat in app.ItemList)
                {
                    DrawerItem z = new DrawerItem
                    {
                        Text = cat.MenuItemTitle,
                        Icon = cat.MenuItemIcon,
                        Description = cat.MenuItemDescription,
                        URL = cat.MenuItemURL
                    };
                    y.Add(z);
                }

                DrawerItem x = new DrawerItem {
                    Text = app.MenuCategoryTitle,
                    Icon = Telerik.Blazor.IconName.Menu,
                    Description = app.MenuCategoryTitle,
                    Children = y
                };
                d.Add(x);
            }
            StateHasChanged();
            = new MainLayout();
            ml.OnSidebarChange(d);

        }
    }...



NAVMENU

@inject NavigationManager navigationManager
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using G2_Field.Shared


<TelerikDrawer @ref="@Drawer" Data="@items" MiniMode="false" Mode="@DrawerMode.Overlay" TItem="DrawerItem" SelectedItemChanged="@OnItemSelect" @bind-Expanded="@Expanded">
    <Template Context="AuthDrawerContext">
        <div class="k-drawer-items" role="menubar" aria-orientation="vertical">
            <ul>
                @foreach (var item in AuthDrawerContext)
                {
                    var selectedClass = item == SelectedItem ? "k-state-selected" : string.Empty;
                    <li @onclick="@(() => OnItemSelect(item))" class="k-drawer-item @selectedClass">
                        <div class="k-level-@(item.Level)">
                            <TelerikIcon Icon="@item.Icon"></TelerikIcon>
                            <span class="k-item-text">@item.Text</span>
                        </div>

                        @if (item.Expanded && (item.Children?.Any() ?? false))
                        {
                            <span class="k-icon k-i-arrow-chevron-down" style="position:absolute; right:0; line-height: inherit; margin: 0 8px"></span>
                        }
                        else if (!item.Expanded && (item.Children?.Any() ?? false))
                        {
                            <span class="k-icon k-i-arrow-chevron-right" style="position:absolute; right:0; line-height: inherit; margin: 0 8px"></span>
                        }
                    </li>
                }

            </ul>
        </div>
    </Template>
</TelerikDrawer>


@code {
    [Parameter] public List<DrawerItem> items { get; set; }
    public TelerikDrawer<DrawerItem> Drawer { get; set; }
    public DrawerItem SelectedItem { get; set; }
    public bool Expanded { get; set; } = true;
    public List<DrawerItem> Data { get; set; } = new List<DrawerItem> { new DrawerItem { Text = "HOME", Icon = Telerik.Blazor.IconName.Window, Description = "Home", URL = "" } };

    public async Task ToggleDrawer() => await Drawer.ToggleAsync();

    protected override void OnInitialized()
    {
        items = Data;
        SelectedItem = items.First();
        StateHasChanged();
    }

    public void OnHover()
    {

    }

    public async Task RefreshMenu(List<DrawerItem> di)
    {
        items = di;
    } 

       

 

4 comments
ADMIN
Marin Bratanov
Posted on: 27 Aug 2020 09:17

Thank you for sharing your experience with the community, Matt.

 

Regards,
Marin Bratanov
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

Matt
Posted on: 26 Aug 2020 21:37

Marin, we finally doubled back and got the Drawer to work. I've found, though, that the example for https://demos.telerik.com/blazor-ui/drawer/hierarchical-drawer has a minor gotcha that caused us a headache. On the line 

if (item == selectedItem && selectedItem.Expanded && (item.Children?.Any() ?? false))

we found that the Expanded property of item may not match the selected item at the time. What we ended up doing is giving the DrawerItem an ID and made it 

if (item.ID == selectedItem.ID && selectedItem.Expanded && (item.Children?.Any() ?? false))

Matt
Posted on: 19 Aug 2020 13:25
Thanks Marin. I will try those options. We had the everything menu based in the main layout to begin with. That was why we put the nav menu back in its own component.
ADMIN
Marin Bratanov
Posted on: 19 Aug 2020 07:16

Hi Matt,

The Drawer is not hierarchical by nature and the template must take care of such things. What is important to keep in mind is that now you have a component between the data source (the List<DrawerItem>) and the template where you loop over that list. This means that you need to have that component update (re-render) in order for your template to re-render and receive the new data collection. In Blazor, that happens when the reference to such a complex object (a collection or a model) changes - so you basically need to call up a new List<DrawerItem>(theCollection) and pass it to the Data of the drawer. This is the way the framework knows such parameters have changed and raises the ParanetersSet event of the component consuming them: https://docs.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-3.1#after-parameters-are-set.

Without being able to run this, I can see two potential places where this could work out so you can try them out individual and choose the one that better fits your needs and architecture if both work:

  • in the main layout:
            public async Task OnSidebarChange(List<DrawerItem> di)
            {
                Data = new List<DrawerItem>(di);
            }
  • OR, in the nav menu:
    public async Task RefreshMenu(List<DrawerItem> di)
        {
            items = new List<DrawerItem>(di);
        } 
    which assumes that this method will be called from the parent - MainLayout:
            public async Task OnSidebarChange(List<DrawerItem> di)
            {
                Data = di; // using a new List<T>() here might be enough, though, using both might cause two re-renders, follow the situation through the lifecycle events in NavMenu
                await nm.RefreshMenu(di);
            }

Ultimately, the exact way data is passed around and where you need it depends on the app. From what I see, I am not sure the reference and Data collections are needed at all in the main layout, firing up the event in the NavMenu component itself should let you update its data there without passing parameters around, but that depends on where and how you need the authorization to happen.

 

Regards,
Marin Bratanov
Progress Telerik