You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor热重载时HttpContextAccessor引发空引用异常问题及解决

解决Blazor热重载时HttpContextAccessor导致的NullReferenceException

我的Blazor应用启动后运行正常,但修改任意组件并保存后,热重载功能失效,抛出如下异常:

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

Microsoft.AspNetCore.Http.IHttpContextAccessor.HttpContext.get returned null.

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=WEBv3
  StackTrace:
   at WEBv3.Components.Layout.NavMenu.BuildRenderTree(RenderTreeBuilder __builder) in E:\projects\e-Schedule\WEBv3\Components\Layout\NavMenu.razor:line 84

问题原因

HttpContextAccessor仅在请求响应周期内有效,热重载时组件重新渲染,此时脱离了原请求周期,导致HttpContext返回null,进而引发空引用异常。尝试将相关值存储为局部变量后问题依旧,开发环境为Visual Studio 2022。

解决方法

通过在Razor组件中使用AuthenticationStateProvider替代HttpContextAccessor,从Blazor的认证系统中获取用户信息,彻底摆脱对HttpContext的依赖。

原组件代码

@inject IHttpContextAccessor accessor
@inject ILocalStorageService LocalStorage
@inject IJSRuntime JS
@inject NavigationManager UriHelper

@using System.Net.Http;
@using System.Diagnostics;
@using System.Net;
@using Blazored.LocalStorage

@rendermode InteractiveServer

<nav class="navbar navbar-expand-lg fixed-top shadow-sm">
    <div class="container-fluid">
        <a class="navbar-brand" asp-controller="Home" asp-action="Index">
            <img src="img/favicon.png" alt="Logo" height="32" class="d-inline-block align-text-top">
            e-Schedule
        </a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <i class="fa-solid fa-bars"></i>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav me-auto">
                <li class="nav-item">
                    <NavLink class="nav-link" href="/">
                        <span class="bi bi-list-nested-nav-menu" aria-hidden="true">Начало</span>
                    </NavLink>
                </li>
                <li class="nav-item">
                    <NavLink class="nav-link" href="SubGroup">
                        <span class="bi bi-list-nested-nav-menu" aria-hidden="true">Подгрупи</span>
                    </NavLink>
                </li>
                <li class="nav-item">
                    <a class="nav-link" asp-controller="Prepaire" asp-action="Flows">Потоци</a>
                </li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">Меню</a>
                    <ul class="dropdown-menu">
                        <li><a class="dropdown-item" asp-controller="Home" asp-action="Index">Подменю 1</a></li>
                        <li><a class="dropdown-item" asp-controller="Home" asp-action="Index">Подменю 2</a></li>
                    </ul>
                </li>
            </ul>
            <div class="row m-0 me-3 p-0 rounded-pill border border-1 border-secondary-subtle">
                <div class="col m-0 ms-1 p-1">
                    <i class="fa-solid fa-sun text-secondary" style="padding-top: 3px;"></i>
                </div>
                <div class="col m-0 p-0">
                    <div class="form-check form-switch ms-2 mt-1">
                        <input class="form-check-input" type="checkbox" name="theme" @oninput="changeThreme" @bind="isChecked"  role="switch">
                    </div>
                </div>
                <div class="col m-0 me-1 p-1">
                    <i class="fa-solid fa-moon text-secondary" style="padding-top: 3px;"></i>
                </div>
            </div>
            <div class="d-flex">
                <a href="https://e-organisation.uni-ruse.bg/People/Person?username=@accessor.HttpContext.User.Identity.Name.Substring(9)" target="_blank" class="person-card card text-decoration-none">
                    <div class="card-body row m-0 p-0">
                        <div class="col-2 m-0 p-0">
                            <div class="person-avatar m-1" style="
                                    background: lightgray center center no-repeat;
                                    background-size: cover;
                                    display: block;
                                    position: relative;
                                    width: 35px;
                                    height: 35px;
                                    border-radius: 50%;
                                    background-image: url('@bg');
                                ">
                            </div>
                        </div>
                        <div class="col-10 m-0 p-0">
                            <p class="person-names m-1 ms-1 pt-1">@names</p>
                        </div>
                    </div>
                </a>
            </div>
        </div>
    </div>
</nav>

@code {
    private bool isChecked { get; set; }
    const string e_organisation_url = "";
    const string e_organisation_api_url = "";
    string username = "";
    private string bg { get; set; }
    string names = "";

    private async void changeThreme()
    {
        if (isChecked == false){
            isChecked = true;
            await JS.InvokeVoidAsync("eval", $"document.documentElement.setAttribute('data-bs-theme', 'dark')");
            await LocalStorage.SetItemAsStringAsync("theme", "dark");
        }
        else if(isChecked ==  true){
            isChecked = false;
            await JS.InvokeVoidAsync("eval", $"document.documentElement.setAttribute('data-bs-theme', 'light')");
            await LocalStorage.SetItemAsStringAsync("theme", "light");
        }
    }

    protected override async Task OnInitializedAsync()
    {
        username = accessor.HttpContext.User.Identity.Name.Substring(9);
        try
        {
            using HttpClientHandler handler = new()
                {
                    UseDefaultCredentials = true,
                    PreAuthenticate = true,
                    Credentials = CredentialCache.DefaultNetworkCredentials
                };

            using HttpClient httpClient = new(handler);

            HttpResponseMessage httpResponse = await httpClient.GetAsync($"{e_organisation_api_url}Person/{username}/Data");
            httpResponse.EnsureSuccessStatusCode();
            
            var json = await httpResponse.Content.ReadAsStringAsync();
            dynamic personData = Newtonsoft.Json.Linq.JObject.Parse(json);

            bg = $"{e_organisation_url}uploads/avatars/{personData.avatar}";
            names = personData.personNames;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }
}

修改后的组件代码

@inject AuthenticationStateProvider AuthStateProvider
@inject ILocalStorageService LocalStorage
@inject IJSRuntime JS
@inject NavigationManager UriHelper

@using System.Net.Http;
@using System.Diagnostics;
@using System.Net;
@using Blazored.LocalStorage
@using Microsoft.AspNetCore.Components.Authorization

@rendermode InteractiveServer

<nav class="navbar navbar-expand-lg fixed-top shadow-sm">
    <div class="container-fluid">
        <a class="navbar-brand" asp-controller="Home" asp-action="Index">
            <img src="img/favicon.png" alt="Logo" height="32" class="d-inline-block align-text-top">
            e-Schedule
        </a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <i class="fa-solid fa-bars"></i>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav me-auto">
                <li class="nav-item">
                    <NavLink class="nav-link" href="/">
                        <span class="bi bi-list-nested-nav-menu" aria-hidden="true">Начало</span>
                    </NavLink>
                </li>
                <li class="nav-item">
                    <NavLink class="nav-link" href="SubGroup">
                        <span class="bi bi-list-nested-nav-menu" aria-hidden="true">Подгрупи</span>
                    </NavLink>
                </li>
                <li class="nav-item">
                    <a class="nav-link" asp-controller="Prepaire" asp-action="Flows">Потоци</a>
                </li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">Меню</a>
                    <ul class="dropdown-menu">
                        <li><a class="dropdown-item" asp-controller="Home" asp-action="Index">Подменю 1</a></li>
                        <li><a class="dropdown-item" asp-controller="Home" asp-action="Index">Подменю 2</a></li>
                    </ul>
                </li>
            </ul>
            <div class="row m-0 me-3 p-0 rounded-pill border border-1 border-secondary-subtle">
                <div class="col m-0 ms-1 p-1">
                    <i class="fa-solid fa-sun text-secondary" style="padding-top: 3px;"></i>
                </div>
                <div class="col m-0 p-0">
                    <div class="form-check form-switch ms-2 mt-1">
                        <input class="form-check-input" type="checkbox" name="theme" @oninput="changeThreme" @bind="isChecked"  role="switch">
                    </div>
                </div>
                <div class="col m-0 me-1 p-1">
                    <i class="fa-solid fa-moon text-secondary" style="padding-top: 3px;"></i>
                </div>
            </div>
            <div class="d-flex">
                @if (!string.IsNullOrEmpty(username))
                {
                    <a href="https://e-organisation.uni-ruse.bg/People/Person?username=@username" target="_blank" class="person-card card text-decoration-none">
                        <div class="card-body row m-0 p-0">
                            <div class="col-2 m-0 p-0">
                                <div class="person-avatar m-1" style="
                                        background: lightgray center center no-repeat;
                                        background-size: cover;
                                        display: block;
                                        position: relative;
                                        width: 35px;
                                        height: 35px;
                                        border-radius: 50%;
                                        background-image: url('@bg');
                                    ">
                                </div>
                            </div>
                            <div class="col-10 m-0 p-0">
                                <p class="person-names m-1 ms-1 pt-1">@names</p>
                            </div>
                        </div>
                    </a>
                }
            </div>
        </div>
    </div>
</nav>

@code {
    private bool isChecked { get; set; }
    const string e_organisation_url = "";
    const string e_organisation_api_url = "";
    string username = "";
    private string bg { get; set; }
    string names = "";

    private async Task changeThreme()
    {
        if (!isChecked)
        {
            isChecked = true;
            await JS.InvokeVoidAsync("eval", $"document.documentElement.setAttribute('data-bs-theme', 'dark')");
            await LocalStorage.SetItemAsStringAsync("theme", "dark");
        }
        else
        {
            isChecked = false;
            await JS.InvokeVoidAsync("eval", $"document.documentElement.setAttribute('data-bs-theme', 'light')");
            await LocalStorage.SetItemAsStringAsync("theme", "light");
        }
    }

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;
        
        if (user.Identity?.IsAuthenticated == true)
        {
            username = user.Identity.Name.Substring(9);
            try
            {
                using HttpClientHandler handler = new()
                {
                    UseDefaultCredentials = true,
                    PreAuthenticate = true,
                    Credentials = CredentialCache.DefaultNetworkCredentials
                };

                using HttpClient httpClient = new(handler);

                HttpResponseMessage httpResponse = await httpClient.GetAsync($"{e_organisation_api_url}Person/{username}/Data");
                httpResponse.EnsureSuccessStatusCode();
                
                var json = await httpResponse.Content.ReadAsStringAsync();
                dynamic personData = Newtonsoft.Json.Linq.JObject.Parse(json);

                bg = $"{e_organisation_url}uploads/avatars/{personData.avatar}";
                names = personData.personNames;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
}

关键修改点

  1. 替换依赖注入:移除IHttpContextAccessor注入,改为注入AuthenticationStateProvider
  2. 安全获取用户信息:通过GetAuthenticationStateAsync()异步获取认证状态,避免直接依赖HttpContext
  3. 增加空值判断:在渲染用户链接前判断username是否为空,同时在获取用户信息时检查认证状态
  4. 修复异步方法:将changeThreme从async void改为async Task,符合Blazor异步规范,避免潜在的内存泄漏

内容的提问来源于stack exchange,提问作者Кристиан Спасов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 06:55:57