Blazor项目调用GetAuthenticationStateAsync时出现SetAuthenticationState未调用错误
解决Blazor中
GetAuthenticationStateAsync was called before SetAuthenticationState错误 问题根源分析
你碰到的这个错误,核心问题出在同步方法里强制阻塞调用异步方法,直接打乱了Blazor的AuthenticationStateProvider初始化流程:
- 在
DataContext.OnConfiguring这个同步方法中,你用serviceClass.getUserConnectionString().Result来获取连接字符串,这种同步阻塞的方式会强制提前触发异步的GetAuthenticationStateAsync调用,而此时Blazor还没完成SetAuthenticationState的初始化操作,直接抛出了错误。 - 另外,
ServiceClass.GetIdentity方法里如果找不到指定的Claim,会触发NullReferenceException,虽然你捕获了异常,但这块逻辑可以更健壮。
分步解决方案
1. 重构DbContext,避免同步阻塞异步方法
DbContext的OnConfiguring是同步方法,不能在这里直接调用异步方法的Result,我们改成通过构造函数注入提前解析好的连接字符串:
首先优化ServiceClass,添加异步获取连接字符串的方法并缓存用户信息:
public class ServiceClass : IServiceClass { private readonly AuthenticationStateProvider _authenticationStateProvider; private string _cachedEmail; public ServiceClass(AuthenticationStateProvider authenticationStateProvider) { _authenticationStateProvider = authenticationStateProvider; } public async Task<string> GetIdentity() { if (!string.IsNullOrEmpty(_cachedEmail)) return _cachedEmail; try { var authState = await _authenticationStateProvider.GetAuthenticationStateAsync(); var user = authState.User; // 用空值运算符避免NullReferenceException _cachedEmail = user.Claims.FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")?.Value ?? string.Empty; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToSafeString()); _cachedEmail = string.Empty; } return _cachedEmail; } // 新增异步获取连接字符串的方法,全程异步无阻塞 public async Task<string> GetUserConnectionStringAsync() { var email = await GetIdentity(); // 替换成你实际根据邮箱获取连接字符串的逻辑 return await FetchConnectionStringFromDatabase(email); } private async Task<string> FetchConnectionStringFromDatabase(string email) { // 示例:从数据库查询动态连接字符串 return "YourDynamicSqlServerConnectionString"; } }
然后修改DataContext,通过构造函数注入连接字符串:
public class DataContext : DbContext { private readonly string _connectionString; public DataContext(string connectionString) { _connectionString = connectionString; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(_connectionString); } public DbSet<Report> Reports { get; set; } }
2. 调整CustomReportSourceResolver,异步兼容处理报表解析
因为IReportSourceResolver.Resolve是同步方法,我们用Task.Run包装异步操作,避免阻塞主线程:
class CustomReportSourceResolver : IReportSourceResolver { private readonly IServiceClass _userClass; private readonly IReportConnectionStringManager _conStrMan; public CustomReportSourceResolver(IServiceClass sc, IReportConnectionStringManager csm) { _userClass = sc; _conStrMan = csm; } public Telerik.Reporting.ReportSource Resolve(string reportName, OperationOrigin operationOrigin, IDictionary<string, object> currentParameterValues) { if (string.IsNullOrEmpty(reportName)) { throw new Exception("Unable to load a report with the specified ID: " + reportName); } // 用Task.Run包装异步逻辑,避免同步阻塞 var reportDefinition = Task.Run(async () => { var connectionString = await _userClass.GetUserConnectionStringAsync(); using (var context = new DataContext(connectionString)) { return await context.Reports.Where(t => t.Name == reportName) .Select(t => t.Definition) .FirstOrDefaultAsync(); } }).GetAwaiter().GetResult(); if (string.IsNullOrEmpty(reportDefinition)) { throw new Exception("Unable to load a report with the specified ID: " + reportName); } var retreivedReport = new XmlReportSource { Xml = reportDefinition }; return _conStrMan.UpdateReportSource(retreivedReport); } }
3. 优化服务注册顺序与组件生命周期
确保身份验证相关服务优先注册,并在组件初始化阶段提前加载身份状态:
Startup.cs中的服务注册:
// 确保AuthenticationStateProvider优先注册(Blazor默认已注册,可明确声明) services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>(); services.AddScoped<IServiceClass, ServiceClass>(); services.AddScoped<IReportConnectionStringManager, ReportConnectionStringManager>(); services.AddScoped<CustomReportSourceResolver>(); services.AddScoped<IReportServiceConfiguration>(sp => new ReportServiceConfiguration { ReportingEngineConfiguration = sp.GetService<IConfiguration>(), HostAppId = "Html5DemoAppCore", Storage = new FileStorage(), ReportSourceResolver = sp.GetService<CustomReportSourceResolver>(), });
使用ReportViewer的组件:
@page "/report" @inject AuthenticationStateProvider AuthStateProvider @inject IServiceClass ServiceClass <ReportViewer ViewerId="rv1" ServiceUrl="/api/reports" ReportSource="@_reportSource" Parameters="@(new ParametersOptions { Editors = new EditorsOptions { MultiSelect = EditorType.ComboBox, SingleSelect = EditorType.ComboBox } })" ScaleMode="@(ScaleMode.Specific)" Scale="1.0" /> @code { private ReportSourceOptions _reportSource; protected override async Task OnInitializedAsync() { // 提前加载身份状态,确保后续调用时状态已就绪 await AuthStateProvider.GetAuthenticationStateAsync(); await ServiceClass.GetIdentity(); // 缓存用户信息 _reportSource = new ReportSourceOptions { Report = "reportAccountingGeneralLedger", Parameters= new Dictionary<string,object> {} }; } }
关键注意事项
- 绝对避免在同步方法中调用异步方法的
Result或Wait():这会导致死锁和上下文混乱,尤其是在Blazor这种依赖异步生命周期的框架中。 - 缓存用户身份信息:减少重复调用
GetAuthenticationStateAsync,提升性能同时避免状态未就绪的问题。 - 空值安全处理:获取Claim时使用
?.空值运算符,避免不必要的异常。
内容的提问来源于stack exchange,提问作者Tanveer Khan
相关产品推荐
相关产品推荐

