Home > Mobile >  Scoped Service not being shared as expected
Scoped Service not being shared as expected

Time:08-20

Context

For context, my code and problem in question is an almost identical copy of the Blazor Server with EF Core Demo. The primary difference is that all my UI C# is written in base classes using the code-behind pattern.

As is seen in the demo app, my code has a filters service used to help keep state between components. On my ManageUsers.razor component, I have a child component to toggle how the names in the contacts list are displayed (NameToggle.razor). If Filters.Loading is TRUE, then the button is disabled so as not to attempt to modify a list that's actively being built.

My Issue

My issue is that whenever I use NavigationManager.NavigateTo() to navigate between the various component pages (e.g., ViewContact, EditContact) and then back to the main component page, the state of Filters.Loading on the main component page and Filter.Loading of its child components are different.

To be clearer, I've provided code snippets of three of my components below. The ManageUsers.razor components is the parent of NameToggle.razor and UserRow.razor. The UserRow component uses the NavigationManager service to navigate to another routable component, which then uses that same service to navigate back to ManageUsers.razor. However, when you navigate away and back in this manner, the button rendered by NameToggle.razor is disabled. After printing the values to the screen, I can see that even though Filters.Loading is FALSE in the ManageUsers.razor, it is TRUE in the child components like NameToggle.razor.

Things I've done so far

I have spent hours comparing my code to the demo and reading through documentation. For the life of me, I cannot figure out why this is happening. The service is indeed registered with a SCOPED lifetime. I've thought of some ideas as to why it might not be working, but again, after hours of comparing my code and reading the docs, I've come up short. Some of those ideas were that maybe using null!; on the injected properties was causing an issue. Per the documentation, I changed it to being default!;, but nothing changed.
I thought that maybe the cascading parameter of the UserTableWrapper.razor component might have been null, but I also tested that and it's never null.

Further context

Not sure if this really matters, but my solution is using clean architecture. I have my Filters interface written in my Application.dll and its implementation written in Infrastructure.dll. Additionally, I have the service registrations for the infrastructure layer in the same layer.

Code

Program.cs Snippet


    WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
    
        // Configure and add application logger as it should be for run-time.
        builder.Host.AddSerilog();
    
        // Add services to the container.
        builder.Services.AddApplicationServices();
        builder.Services.AddInfrastructureServices(builder.Configuration);
        builder.Services.AddWebUIServices();
    
        WebApplication app = builder.Build();

InfrastructureServices.cs


    using FLA.Application.Common.Interfaces;
    using FLA.Domain.Entities.Identity;
    using FLA.Infrastructure.Common.Filtering;
    using FLA.Infrastructure.Persistence;
    using FLA.Infrastructure.Persistence.Seeding;
    
    using Microsoft.AspNetCore.Identity;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace FLA.Infrastructure.DependencyInjection;
    
    /// <summary> Extensions for adding and configuring services from the Infrastructure project. </summary>
    public static class InfrastructureServices
    {
        /// <summary> Adds and configures services from the infrastructure layer to the application's services container. </summary>
        /// <param name="services"> <see cref="IServiceCollection" />: the application's services container. </param>
        /// <param name="configuration"> <see cref="IConfiguration" />: the application's configuration. </param>
        /// <returns> The <see cref="IServiceCollection" /> with the various services added and configured. </returns>
        public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
        {
            // Register db context factory and configure options.
            string             connectionString = configuration.GetConnectionString(ApplicationDbContext.ConnectionStringKey);
            MySqlServerVersion serverVersion    = new (ServerVersion.AutoDetect(connectionString));
    
            services.AddDbContextFactory<ApplicationDbContext>(options => options.UseMySql(connectionString, serverVersion,
                                                                                           mySqlOptions => mySqlOptions.MigrationsAssembly("FLA.Infrastructure")));
    
            services.AddScoped<ApplicationDbContextInitializer>();
    
            // Pager.
            services.AddScoped<IPageHelper, PageHelper>();
    
            // Filters.
            services.AddScoped<IApplicationUserFilters, ApplicationUserFilterControls>();
    
            services.AddDefaultIdentity<ApplicationUser>()
                    .AddRoles<ApplicationRole>()
                    .AddEntityFrameworkStores<ApplicationDbContext>();
    
            services.AddAuthentication();
            services.AddAuthorization();
    
            services.Configure<IdentityOptions>(options =>
            {
                // Sign In settings.
                options.SignIn.RequireConfirmedAccount = true;
    
                // Password settings.
                options.Password.RequireDigit           = true;
                options.Password.RequireLowercase       = true;
                options.Password.RequireNonAlphanumeric = true;
                options.Password.RequireUppercase       = true;
                options.Password.RequiredLength         = 8;
                options.Password.RequiredUniqueChars    = 1;
    
                // Lockout settings.
                options.Lockout.DefaultLockoutTimeSpan  = TimeSpan.FromMinutes(20);
                options.Lockout.MaxFailedAccessAttempts = 5;
                options.Lockout.AllowedForNewUsers      = true;
    
                // User settings.
                options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._@ ";
                options.User.RequireUniqueEmail        = true;
            });
    
            return services;
        }
    }

ManageUsers.razor


    @page "/ManageUsers"
    @page "/ManageUsers/{Page:int}"
    @inherits ManageUsersBase
    @attribute [ Authorize(Roles = "Administrator") ]
    
    <PageTitle>Manage Users</PageTitle>
    
    <h1>Manage Users</h1>
    
    <UserTableWrapper @ref="Wrapper"
                      FilterChanged="ReloadAsync"
                      DeleteRequested="id => Wrapper.DeleteRequestId = id">
    
        <div >
    
            <div >
    
                <div >
                    <NameToggle />
                    <span> &nbsp; @Filters.Loading</span>
                </div>
    
                <div >
                    <TextFilter />
                </div>
    
                <div >
                    <button 
                            @onclick="NewUser">➕ New User</button>
                </div>
    
            </div>
    
            <div >&nbsp;</div>
    
            <div >
    
                <div >
                    Page @Filters.PageHelper.Page of @Filters.PageHelper.PageCount: displaying @Filters.PageHelper.PageItems of @Filters.PageHelper.TotalItemCount users.
    
                    <a disabled="@(Filters.Loading || ! Filters.PageHelper.HasPrev)"
                       
                       href="@($"ManageUsers/{Filters.PageHelper.PrevPage}")">
                        Previous
                    </a>
    
                    <a disabled="@(Filters.Loading || ! Filters.PageHelper.HasNext)"
                       
                       href="@($"ManageUsers/{Filters.PageHelper.NextPage}")">
                        Next
                    </a>
                </div>
    
            </div>
    
            <div >&nbsp;</div>
    
            <div >
    
                <div >&nbsp;</div>
    
                <div 
                     @onclick="@(async () => await ToggleAsync(ApplicationUserFilterColumns.Name))">
                    <SortIndicator Column="@(ApplicationUserFilterColumns.Name)" /> &nbsp;           
  • Related