Armure
Armure

Reputation: 35

HttpContextAccessor is null

Firstly, I'm sorry if this topic has already been discussed. I couldn't find what I wanted online so that's why I'm doing this.

The title explains it well, HttpContextAccessor is null when I try to access it. I registered the service in Program.cs like this :

builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddScoped<CustomStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomStateProvider>());

I'm using it to save user data inside cookies on authentification.

I injected IHttpContextAccessor inside the CustomStateProvider class :

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

However when I launch the app I get System.NullReferenceException on this line :

var cookie = HttpContextAccessor.HttpContext.Request.Cookies["CurrentUser"];

Here's the full CustomStateProvider class :

public class CustomStateProvider : AuthenticationStateProvider
    {
        [Inject]
        public IHttpContextAccessor HttpContextAccessor { get; set; }

        private readonly IAuthService _authService;

        private CurrentUser _currentUser;

        public CustomStateProvider(IAuthService authService)
        {
            this._authService = authService;
        }

        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var identity = new ClaimsIdentity();
            try
            {
                var userInfo = GetCurrentUser();
                if (userInfo.IsAuthenticated)
                {
                    var claims = new[] { new Claim(ClaimTypes.Name, _currentUser.UserName) }.Concat(_currentUser.Claims.Select(c => new Claim(c.Key, c.Value)));
                    identity = new ClaimsIdentity(claims, "Server authentication");
                }
            }
            catch (HttpRequestException ex)
            {
                Console.WriteLine("Request failed:" + ex);
            }

            return new AuthenticationState(new ClaimsPrincipal(identity));
        }

        public async Task Login(ConnexionModel loginParameters)
        {
            _authService.Login(loginParameters);

            // No error - Login the user
            var user = _authService.GetUser(loginParameters.UserName);
            _currentUser = user;
            var cookieOptions = new CookieOptions
            {
                Expires = DateTime.Now.AddDays(7),
                HttpOnly = true
            };
            HttpContextAccessor.HttpContext.Response.Cookies.Append("CurrentUser", JsonConvert.SerializeObject(_currentUser), cookieOptions);
            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
        }

        public async Task Logout()
        {
            _currentUser = null;
            HttpContextAccessor.HttpContext.Response.Cookies.Delete("CurrentUser");
            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
        }

        public async Task Register(InscriptionModel registerParameters)
        {
            _authService.Register(registerParameters);

            // No error - Login the user
            var user = _authService.GetUser(registerParameters.UserName);
            _currentUser = user;
            var cookieOptions = new CookieOptions
            {
                Expires = DateTime.Now.AddDays(7),
                HttpOnly = true
            };
            HttpContextAccessor.HttpContext.Response.Cookies.Append("CurrentUser", JsonConvert.SerializeObject(_currentUser), cookieOptions);
            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
        }

        public CurrentUser GetCurrentUser()
        {
            if (_currentUser != null && _currentUser.IsAuthenticated)
            {
                return _currentUser;
            }
            else
            {
                var cookie = HttpContextAccessor.HttpContext.Request.Cookies["CurrentUser"];
                if (cookie != null)
                {
                    return JsonConvert.DeserializeObject<CurrentUser>(cookie);
                }
            }

            return new CurrentUser();
        }
    }
}

I think I didn't register the service properly, I tried messing with Program.cs but couldn't solve the problem. I'm pretty new to Blazor so I don't really know much about this kind of stuff.

Thank you for your help.

Upvotes: 1

Views: 848

Answers (1)

jon.r
jon.r

Reputation: 1058

IHttpContext 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. Depending on what version of Blazor Server you're running you need to find the first file that is rendered in your application tree.

You could persist the user details into a cookie however, since Blazor Server is stateful, I would suggest you persist it via a cascading parameter throughout the application life cycle.

In my version of Blazor Server that I'm developing an app in I have two files:

  • _Host.cshtml
  • App.razor

_Host.cshtml is where I can access the IHttpContext where it is not null, I store all those values into a User object and persist it through the application via a cascading parameter. In my instance I'm using Windows Authentication, but you'd be able to use this methodology for any type of authentication.

_Host.cshtml

@page "/"
@namespace oms.net.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Core.Identity.User
@using Microsoft.AspNetCore.Components.Web
@inject IUserRepository User
@{
    Layout = null;
}

@{
    var userObject = await User.GetUserDetailsAsync(User.GetCurrentUserName());
    if(userObject != null)
    {
    <!DOCTYPE html>
    <html lang="en">
    <head>
                <meta charset="utf-8" />
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                <title></title>
                <base href="~/" />
                <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
                <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">      
    </head>
    <body>
//this is key for persisting the user object as a cascading parameter
                <component type="typeof(App)" render-mode="ServerPrerendered" param-UserObject = "@userObject"/>
             
                <div id="blazor-error-ui">
                    <environment include="Staging,Production">
                        An error has occurred. This application may no longer respond until reloaded.
                    </environment>
                    <environment include="Development">
                        An unhandled exception has occurred. See browser dev tools for details.
                    </environment>
                    <a href="" class="reload">Reload</a>
                    <a class="dismiss">🗙</a>
                </div>
                <script src="_framework/blazor.server.js"></script>      
    </body>
    </html>
    }
    else
    {
    <b>Invalid user, contact support. The user may need to be added to []</b>
    <i>Your Active Directory username may not match your produce pro credentials. If this is the case, please inform IT.</i>
    }
}

App.razor

@using Core.Identity.User
@using Core.Identity.Models

<CascadingAuthenticationState>
    <CascadingValue Value="userObject">
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p></p>
            </LayoutView>
        </NotFound>
    </Router>
    </CascadingValue>
</CascadingAuthenticationState>

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

Example for accessing cascading parameter in a Razor component:

[CascadingParameter]
private UserModel User { get; set; }

Here is a more detailed explanation I created: https://www.raynorsystems.com/null-ihttpcontextaccessor-blazor-server/

Upvotes: 4

Related Questions