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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 15:48:16