Table of Contents

Why is IHttpContextAccessor -or- HttpContext sometimes null on Blazor Server?

The IHttpContextAccessor on Blazor Server is only available on the first Http request when loading a Blazor Server application. After that, all connections to the server is handled via SignalR. Your HttpContext will most likely be null after this.  This may be unfamiliar to you if you’re coming from a MVC/Razor Pages background. 

You should persist the HttpContext information via a cascading parameter throughout the application life cycle.

In this article we’ll show you how to do that.

Specifics From Microsoft

Don't use IHttpContextAccessor/HttpContext directly or indirectly in the Razor components of Blazor Server apps. Blazor apps run outside of the ASP.NET Core pipeline context. The HttpContext isn't guaranteed to be available within the IHttpContextAccessor, and HttpContext isn't guaranteed to hold the context that started the Blazor app.

A critical aspect of Blazor Server security is that the user attached to a given circuit might become updated at some point after the Blazor circuit is established but the IHttpContextAccessor isn't updated.

Shared state Blazor server apps live in server memory, and multiple app sessions are hosted within the same process. For each app session, Blazor starts a circuit with its own dependency injection container scope, thus scoped services are unique per Blazor session. Warning, we don't recommend apps on the same server share state using singleton services unless extreme care is taken, as this can introduce security vulnerabilities, such as leaking user state across circuits.

Okay, so how do you fix this?

The solution is fairly straight forward but has some nuance, see below:

Create a User model

UserModel.cs

				
					public class UserModel
{
    public string UserName { get; set; }
    //Add whatever additional properties you may need here
}
				
			

Create a User Repository

IUserRepository.cs & UserRepository.cs

				
					public interface IUserRepository
{
    public UserModel GetLoggedInUser();
}

public class UserRepository : IUserRepository
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    
    public UserRepository(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor
    }
    
    public UserModel GetLoggedInUser()
    {
        UserModel user = new();
        user.UserName = _httpContextAccessor.HttpContext.User.Identity.Name;
        
        return user;
    }
}
				
			

Wire Up Dependencies

Program.cs

				
					public class Program
{
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddHttpContextAccessor();
    builder.Services.AddScoped<IUserRepository, UserRepository>();
    
    var app = builder.Build()
    .
    .
    .
    app.Run();
}
				
			

The Scoped dependency ensures the IUserRepository is not shared among users. Read more here

Wire up cascading parameter

_Host.cshtml

				
					@inject IUserRepository User

@{
    UserModel UserObject = await User.GetLoggedInUser();
    if(UserObject != null)
    {
    <!DOCTYPE html>
    <html lang="en">
    <head>
                .
                .
    </head>
    <body>
                //param-UserObject = "@UserObject" is key for persisting the user object as a cascading parameter
                <component type="typeof(App)" render-mode="ServerPrerendered" param-UserObject = "@UserObject"/>
                .
                .
                <script src="_framework/blazor.server.js"></script>      
    </body>
    </html>
    }
    else
    {
        <b>Invalid user</b>
    }
}
				
			

App.razor

				
					
<CascadingValue Value="@UserObject"> 
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
        <NotFound>
            <PageTitle>Not found</PageTitle>
            <LayoutView Layout="@typeof(MainLayout)">
                <p role="alert">Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingValue>       


@code
{
    [Parameter]
    public UserModel UserObject { get; set; }
}
				
			

Accessing your UserObject in a custom component

MyCustomComponent.razor

				
					@if(User is not null)
{
    <h3>Hi! @User.UserName</h3>
}


@code
{
    [CascadingParameter]
    public UserModel User { get; set; }
}
				
			

You can see the UserObject is now exposed via cascading parameter. You can access this object anywhere throughout the Blazor Server application.

I hope this tutorial was helpful for you!

Want to learn more?