Search code examples
c#blazorblazor-webassembly

How to update header value when user login - blazor


I am working with a Blazor web assembly application and facing an issue. I want to update the username and profile image on the top menu when a user logs in.

So, the approach I used is, when a user logs in, store username and profile image URL in the local storage. And fetch that from the header component.

Header.razor

<span>@_username</span>                                    
<img src="@_profileImageUrl" alt="">

@code{
    string? _username;
    string? _profileImageUrl;

    public async Task GetUserInfo()
    {
        _username = await LocalStorage.GetItemAsync<string>("username");
        _profileImageUrl = await LocalStorage.GetItemAsync<string>("profileImageUrl");
        StateHasChanged();
    }

}

Login.razor

<Header @ref="_header"></Header>

@code{
    
    private Header _header { get; set; }
        
    private async Task HandleLogin()
    {
        var loginResponse = await AuthenticationService.Login(_model);
        if (loginResponse.Success)
        {
            await _header.GetUserInfo();
            var returnUrl = NavigationManager.QueryString("returnUrl") ?? "/myaccount";
            NavigationManager.NavigateTo(returnUrl);
        }
        else
        {            
            NotificationService.Notify(Notification.Error(loginResponse.Message));
        }
    }    
}

App structure

-Controls
 -Header.razor
-Pages
 -Login.razor

Local storage is getting updated on login success. But the header values aren't updated.

I have tried to cascade the values from the main layout page; it works partially. When I hard refresh the page, both the values become null.

Edit-1:

public class AuthenticationService : IAuthenticationService
{
        public async Task<Response<AuthenticationResponse>> Login(LoginViewModel loginViewModel)
        {
            var loginResponse = await _httpService.Post<AuthenticationResponse>("api/auth/login", loginViewModel);
            if (loginResponse.Success)
            {
                await _localStorage.SetItemAsync("authToken", loginResponse.Data.AccessToken);
                await _localStorage.SetItemAsync("refreshToken", loginResponse.Data.RefreshToken);
                await _localStorage.SetItemAsync("username", loginResponse.Data.FirstName);
                await _localStorage.SetItemAsync("profileImageUrl", loginResponse.Data.ProfileImageUrl);

                ((AuthStateProvider)_authStateProvider).NotifyUserAuthentication(loginResponse.Data.AccessToken);
            }

            return loginResponse;
        }
}

Edit-2
MainLayout.razor

@inherits LayoutComponentBase

<RadzenNotification />
<div class="page">
    <main>
        <Header></Header>
        <div>
            @Body
        </div>
        <Footer></Footer>
    </main>
</div>

Solution

  • The simplest way to implement this kind of feature in a header would be to put the header in a layout component.

    Additionally, create a small service like so:

    public class UserStatusService
    {
        public event EventHandler UserLoggedIn;
    
        public void UserIsLoggedIn()
        {
            UserLoggedIn?.Invoke(this, null);
        }
    }
    

    Register this in your services, and inject it into Login.razor.

    Then, from login.razor, after successful login call:

    _userStatusService.UserIsLoggedIn();
    

    In header component:

    @inject UserStatusService _userStatusService
    @implements IDisposable
    
    @code {
        string? _username;
        string? _profileImageUrl;
    
        protected override OnInitialized()
        {
            _userStatusService.UserLoggedIn += HandleLoggedIn;
        }
    
        public void Dispose()
        {
            _userStatusService.UserLoggedIn -= HandleLoggedIn;
        }
    
        void HandleLoggedIn(object sender, EventArgs e)
        {
            _username = await LocalStorage.GetItemAsync<string>("username");
            _profileImageUrl = await LocalStorage.GetItemAsync<string>("profileImageUrl");
            StateHasChanged();
        }
    }