HttpContext is null when injected into a singleton dependency

Viewed 388

I'm looking to create a global class in my Blazor application that contains a function that gets the user's Department through the user's username which I get from Windows authentication but I can't seem to access the HttpContextAccessor through my global class. It acts like it has access to HttpContext when I inject it but when it runs, I get the error

System.NullReferenceException: 'Object reference not set to an instance of an object.'

and the accessor is null when you look at it in the local variables.

I've done a lot of googling but couldn't find anything that melded well with what I'm doing and my current knowledge of how these things work.

Here's my global class:

public class Global
{
    [Inject]
    IHttpContextAccessor HttpContextAccessor { get; set; }

    public string Identity;
    public string Department;

    public Global()
    {
        Identity = HttpContextAccessor.HttpContext.User.Identity.Name;
        
        CalculateDepartment(Identity)
    }

    private void CalculateDepartment (string identity) {
        //Calculate what department the person is in based on user ID
        Department = CalculatedDepartment;
    }
}

Here is my startup:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddServerSideBlazor(o => o.DetailedErrors = true);
        services.AddTelerikBlazor();
        services.AddHttpContextAccessor();
        services.AddSingleton<Global>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.ApplicationServices.GetRequiredService<Global>();
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute();
            endpoints.MapControllers();

            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        });
    }
}

Google said to use services.AddScoped<Global> but I found that this didn't work with my CalculateDepartment function and when I used services.AddSingleton<Global> it worked so I kept it that way.

It appears to be doing this to anything I try to inject in this way into this file. I can inject things into any other page but not this class apparently. There were a few people simply saying to inject it into the constructor but that didn't help me much as I'm fairly new to this and I couldn't get the examples that I found of that to work. That could be the solution though, maybe I just need to do it in a way that would work. There could just be a better way of making a global class too.

2 Answers

Based on what I've surmised from your question - your looking to get access to the the HttpContext in Blazor Server. If so, then this code - credit to Robin Sue - gets the context for you:

            // Server Side Blazor doesn't register HttpClient by default
            // Thanks to Robin Sue - Suchiman https://github.com/Suchiman/BlazorDualMode
            if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
            {
                // Setup HttpClient for server side in a client side compatible fashion
                services.AddScoped<HttpClient>(s =>
                {
                    // Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
                    var uriHelper = s.GetRequiredService<NavigationManager>();
                    return new HttpClient
                    {
                        BaseAddress = new Uri(uriHelper.BaseUri)
                    };
                });
            }

If not then ignore the answer!

It turns out that I was unable to access anything that was injected through my constructor so I did some research and according to this website: https://blazor-university.com/dependency-injection/injecting-dependencies-into-blazor-components/

Dependencies are injected after the Blazor component instance has been created and before the OnInitialized or OnInitializedAsync lifecycle events are executed. This means we cannot override our component’s constructor and use those dependencies from there, but we can use them in the OnInitialized* methods.

So basically I just can't use injected dependencies at all in my constructor. I've got to find another way to do this then! I'll update this when I find another way to do it if I don't just give up and move on.

Edit: I ended up using a (imo) not great work around where I created a method in Global.cs that set the username string to whatever was put into it. Then I used the fact that my shared layouts are used at all times and can access the username through the use of <AuthorizeView> so I just set the username using the method that I created in one of my layouts like this:

<AuthorizeView>
    <Authorized>
        @{
              Global.SetUserName(context.User.Identity.Name);
         }
    </Authorized>
</AuthorizeView>

So yeah, not ideal but it works and for now that's my goal.

Related