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

Blazor Server中Scoped状态服务实例异常及数据为空问题咨询

Blazor Server 会话状态共享解决方案

一、先排查Scoped服务实例不一致的原因

Blazor Server中AddScoped注册的服务默认是电路级共享的,同一会话(同一个电路)下的所有组件应该拿到同一个实例。如果出现实例不同/为null,先排查这几点:

  • 确认组件不是静态渲染模式(静态渲染的组件不会绑定到电路,每次请求都会创建新实例),如果用了静态渲染,需要改为交互式渲染(比如@rendermode InteractiveServer)。
  • 检查Program.cs中服务注册的顺序,确保AddScoped<CurrentState>()在AddRazorComponents()之前或者正确的位置,没有被覆盖。
  • 确认组件中注入的类名和注册的完全一致(比如大小写、命名空间),避免因类名不匹配导致注入失败为null。

二、正确实现Scoped状态服务的初始化(针对CustomStateService的问题)

你在OIDC的OnTokenValidated事件中初始化CustomStateService时,要注意:Blazor Server的Scoped服务是和电路关联的,而OIDC的验证事件是在HTTP请求管道中执行的,此时对应的电路可能还没创建,或者无法直接获取到Scoped服务实例。正确的做法是通过AuthenticationStateProvider来获取用户信息,或者在组件初始化时加载状态:

方案1:通过AuthenticationStateProvider获取用户信息

  1. 自定义CustomStateService,依赖注入AuthenticationStateProvider:
public class CustomStateService
{
    private readonly AuthenticationStateProvider _authProvider;
    private UserItem? _userItem;

    public CustomStateService(AuthenticationStateProvider authProvider)
    {
        _authProvider = authProvider;
        // 监听身份状态变化
        _authProvider.AuthenticationStateChanged += OnAuthStateChanged;
    }

    public UserItem? UserItem => _userItem;

    private async void OnAuthStateChanged(Task<AuthenticationState> authStateTask)
    {
        var authState = await authStateTask;
        if (authState.User.Identity?.IsAuthenticated == true)
        {
            // 从Claims中提取用户信息,或者调用API获取
            _userItem = new UserItem
            {
                Id = authState.User.FindFirstValue(ClaimTypes.NameIdentifier),
                Name = authState.User.FindFirstValue(ClaimTypes.Name)
                // 其他字段
            };
        }
        else
        {
            _userItem = null;
        }
    }
}
  1. 在Program.cs中注册为Scoped:
builder.Services.AddScoped<CustomStateService>();
  1. 组件中注入后,可通过UserItem获取数据,也可以监听状态变化(比如用StateHasChanged()更新UI)。

方案2:在组件初始化时手动初始化状态

如果必须在OIDC验证后初始化,可将用户信息存储到HttpContext.Session,然后在组件的OnInitializedAsync中从Session读取并填充到CustomStateService:

  1. 先启用Session:
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30);
});
// 在管道中添加Session中间件
app.UseSession();
  1. OIDC的OnTokenValidated事件中存储用户信息到Session:
.AddOpenIdConnect(options =>
{
    // 其他配置
    options.Events = new OpenIdConnectEvents
    {
        OnTokenValidated = async context =>
        {
            var userItem = new UserItem
            {
                // 从context.Principal提取信息
            };
            // 序列化UserItem到Session
            context.HttpContext.Session.SetString("UserItem", JsonSerializer.Serialize(userItem));
        }
    };
});
  1. 在CustomStateService中注入IHttpContextAccessor(需要先注册builder.Services.AddHttpContextAccessor();),然后在组件初始化时读取:
public class CustomStateService
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    public UserItem? UserItem { get; set; }

    public CustomStateService(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public void LoadUserFromSession()
    {
        var session = _httpContextAccessor.HttpContext?.Session;
        if (session != null)
        {
            var userJson = session.GetString("UserItem");
            if (!string.IsNullOrEmpty(userJson))
            {
                UserItem = JsonSerializer.Deserialize<UserItem>(userJson);
            }
        }
    }
}
  1. 组件中调用:
@inject CustomStateService customStateService

@code {
    protected override async Task OnInitializedAsync()
    {
        customStateService.LoadUserFromSession();
        await base.OnInitializedAsync();
    }
}

三、级联参数方案(适合简单状态共享)

如果不想用依赖注入,可在Routes.razor中管理全局状态,通过级联参数传递给所有子组件:

  1. 修改Routes.razor,添加状态类并级联:
@inject AuthenticationStateProvider AuthProvider
<CascadingValue Name="CurrentUserState" Value="@_currentState" IsFixed="false">
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
        <NotFound>
            <PageTitle>Not found</PageTitle>
            <LayoutView Layout="@typeof(MainLayout)">
                <p role="alert">Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingValue>

@code {
    private CurrentState _currentState = new();

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthProvider.GetAuthenticationStateAsync();
        if (authState.User.Identity?.IsAuthenticated == true)
        {
            // 初始化_currentState的参数
            _currentState.UserItem = new UserItem
            {
                // 填充数据
            };
        }
    }
}
  1. 子组件中接收级联参数:
@code {
    [CascadingParameter(Name = "CurrentUserState")]
    public CurrentState? CurrentState { get; set; }

    protected override void OnParametersSet()
    {
        // 使用CurrentState中的数据
        if (CurrentState != null)
        {
            // 逻辑处理
        }
    }
}

这种方式的好处是所有子组件共享同一个CurrentState实例,状态更新时只要修改_currentState并调用StateHasChanged(),所有接收级联参数的组件都会更新。

四、总结

  • 不需要强制使用Session,Blazor Server的Scoped服务本身就支持同电路(会话)内的实例共享,先排查实例不一致的原因(渲染模式、注册顺序、类名匹配)。
  • 状态初始化要结合Blazor的生命周期,避免在HTTP管道事件中直接操作Scoped服务,优先通过AuthenticationStateProvider监听身份状态变化。
  • 级联参数适合简单的全局状态共享,实现成本低,无需依赖注入配置。

内容的提问来源于stack exchange,提问作者Tobias Gustafsson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 09:51:20