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}"); } } } }
关键修改点
- 替换依赖注入:移除
IHttpContextAccessor注入,改为注入AuthenticationStateProvider - 安全获取用户信息:通过
GetAuthenticationStateAsync()异步获取认证状态,避免直接依赖HttpContext - 增加空值判断:在渲染用户链接前判断
username是否为空,同时在获取用户信息时检查认证状态 - 修复异步方法:将
changeThreme从async void改为async Task,符合Blazor异步规范,避免潜在的内存泄漏
内容的提问来源于stack exchange,提问作者Кристиан Спасов
相关产品推荐
相关产品推荐

