In the app, I have a requirement to display School dropdown in every page of Blazor app and School should be selected to the School assigned to the logged in user. Admins can view all the schools in the dropdown.
I can create a scoped service that has the selected school property and gets updated when another school is selected in the reusable school dropdown component.
School dropdown maintains the selection in all pages of the application but gets reset when an user opens a new tab. Agree this is the expected behavior of the scoped service. Don’t want to create Singleton scope as it’s for all users which doesn’t my requirement.
In traditional ASP.NET web forms this selected school is maintained in a session state.
Looking for the ways to implement this requirement in Blazor.
1
I have a requirement to display School dropdown in every page of
Blazor app and School should be selected to the School assigned to the
logged in user. Admins can view all the schools in the dropdown.
You can still use session to achieve it.
After login success, use session to store the user information, and try to put the select element (dropdown) in the layout page, in the OnInitializedAsync method, get user information from session and then set the default selected value.
Refer to the following sample:
-
In the Program.cs file, register the session middleware and sevice:
builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); ... app.UseSession();
-
Create a UserSessionData class to store the required user information.
public class UserSessionData { public string Username { get; set; } public List<string> Roles { get; set; } // Only include serializable data public bool IsAuthenticated { get; set; } }
-
In the Login form submit method, after login success, use session to store the user information:
public async Task LoginUser() { // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, set lockoutOnFailure: true var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { Logger.LogInformation("User logged in."); //get current user cliamsprincipal var user = await _userManager.FindByEmailAsync(Input.Email); var claimsPrincipal = await SignInManager.CreateUserPrincipalAsync(user); var currentuser = new UserSessionData() { Username = Input.Email, IsAuthenticated = claimsPrincipal.Identity.IsAuthenticated, }; HttpContext.Session.Set<UserSessionData>("MyUser", currentuser); RedirectManager.RedirectTo(ReturnUrl); }
-
In the NavMenu.Razor page (I put the select element in this component), inject the IHttpContextAccessor and get the session data in the OnInitializedAsync method, and based on the user info to set the default selected value:
@using System.Security.Claims @using BlazorServerCore8App2.Data @using BlazorServerCore8App2.Services @implements IDisposable @inject NavigationManager NavigationManager @inject IHttpContextAccessor HttpContextAccessor <div class="top-row ps-3 navbar navbar-dark"> <div class="container-fluid"> <a class="navbar-brand" href="">BlazorServerCore8App2</a> </div> </div> <input type="checkbox" title="Navigation menu" class="navbar-toggler" /> <div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()"> <nav class="flex-column"> <div class="nav-item px-3"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home </NavLink> </div> <div class="nav-item px-3"> <NavLink class="nav-link" href="counter"> <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter </NavLink> </div> <div class="nav-item px-3"> <NavLink class="nav-link" href="weather"> <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather </NavLink> </div> <div class="nav-item px-3"> <label for="dropdown" style="color:white">Class: </label> <select id="dropdown" @bind="model.SelectedValue"> <option value="0">Select Class</option> @foreach (var item in Options) { <option value="@item.Value">@item.Text</option> } </select> </div> <AuthorizeView> <Authorized> <div class="nav-item px-3"> <NavLink class="nav-link" href="Account/Manage"> <span class="bi bi-person-fill-nav-menu" aria-hidden="true"></span> @context.User.Identity?.Name </NavLink> </div> <div class="nav-item px-3"> <form action="Account/Logout" method="post"> <AntiforgeryToken /> <input type="hidden" name="ReturnUrl" value="@currentUrl" /> <button type="submit" class="nav-link"> <span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true"></span> Logout </button> </form> </div> </Authorized> <NotAuthorized> <div class="nav-item px-3"> <NavLink class="nav-link" href="Account/Register"> <span class="bi bi-person-nav-menu" aria-hidden="true"></span> Register </NavLink> </div> <div class="nav-item px-3"> <NavLink class="nav-link" href="Account/Login"> <span class="bi bi-person-badge-nav-menu" aria-hidden="true"></span> Login </NavLink> </div> </NotAuthorized> </AuthorizeView> </nav> </div> @code { private string? currentUrl; private void OnLocationChanged(object? sender, LocationChangedEventArgs e) { currentUrl = NavigationManager.ToBaseRelativePath(e.Location); StateHasChanged(); } public void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; } private DropdownModel model = new(); private List<DropdownOption> Options = new(); private ClaimsPrincipal user; protected override async Task OnInitializedAsync() { currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); NavigationManager.LocationChanged += OnLocationChanged; // Simulating data retrieval await Task.Delay(100); // Mimic async operation if needed // Load options (this could come from a service or API) Options = new List<DropdownOption> { new DropdownOption { Value = "1", Text = "Option 1" }, new DropdownOption { Value = "2", Text = "Option 2" }, new DropdownOption { Value = "3", Text = "Option 3" } }; UserSessionData currentuser = HttpContextAccessor.HttpContext.Session.Get<UserSessionData>("MyUser"); if (currentuser != null && currentuser.IsAuthenticated) { if (currentuser.Username.Contains("aa")) { // Set default selected value model.SelectedValue = "2"; // Dynamically set the default } else { // Set default selected value model.SelectedValue = "3"; // Dynamically set the default } } //cookie based. // //get current user information // var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); // user = authState.User; // //check user is authenticated or not // if(user.Identity.IsAuthenticated){ // if (user.Identity.Name.Contains("aa")) // { // // Set default selected value // model.SelectedValue = "2"; // Dynamically set the default // } // else // { // // Set default selected value // model.SelectedValue = "3"; // Dynamically set the default // } // } } public class DropdownModel { public string SelectedValue { get; set; } } public class DropdownOption { public string Value { get; set; } public string Text { get; set; } } }
The output as below:
Note: The above sample is a Blazor Server app. If you application is a Blazor WebAssembly application, try to use localStorage and sessionStorage.
More detail information about using session, see Session and state management in ASP.NET Core and ASP.NET Core Blazor state management.
1