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

使用Azure Signal R Service后.NET 6 Blazor Server中HttpContext为空问题

问题:Blazor Server改用Azure Signal R Service后HttpContext相关代码失效

我有一个.NET 6 Blazor Server应用,用于和.NET 6 Web Api通信。之前通过DelegatingHandler获取授权令牌并记录userId的逻辑运行正常,但改用Azure Signal R Service后,所有引用HttpContext的代码都出问题了。我已经在Startup中注册了IHttpContextAccessor并注入服务,且确保它的注册顺序在Signal R之前。

问题代码示例

AuthenticationDelegatingHandler实现:

public class AuthenticationDelegatingHandler : DelegatingHandler
{
    private readonly ITokenAcquisition _tokenAcquisition;
    private readonly IConfiguration _configuration;
    private readonly ILogger<AuthenticationDelegatingHandler> _logger;
    private readonly IHttpContextAccessor _httpContext;

    public AuthenticationDelegatingHandler(ITokenAcquisition tokenAcquisition, IConfiguration configuration, ILogger<AuthenticationDelegatingHandler> logger, IHttpContextAccessor httpContext)
    {          
        _tokenAcquisition = tokenAcquisition;
        _configuration = configuration;
        _logger = logger;
        _httpContext = httpContext;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var scopes = new[] { _configuration["ApiScope"] };
        string accessToken = string.Empty;
        var userObject = _httpContext?.HttpContext?.User?.Claims.SingleOrDefault(_ => _.Type == ClaimTypes.NameIdentifier)?.Value;

        try
        {
            accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes!);
            _logger.LogInformation("Successfully retrieved access token from Azure for communication with API. User ObjectId - {UserObject}", userObject);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error while fetching access token for communication with API. Details: {Message}. {InnerException}", ex.Message, ex.InnerException);
            throw;
        }                        

        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        var response = await base.SendAsync(request, cancellationToken);            
        return response;
    }
}                

Startup中的注册代码:

builder.Services.AddRazorPages();
builder.Services.AddHttpContextAccessor();
builder.Services.AddServerSideBlazor()
        .AddMicrosoftIdentityConsentHandler();

builder.Services.AddScoped<NotificationService>();
builder.Services.AddTransient<AuthenticationDelegatingHandler>();

builder.Services.AddHttpClient("WebAPI", client => client.BaseAddress = new Uri(builder.Configuration["ApiBaseAddress"] ?? string.Empty))
    .AddHttpMessageHandler<AuthenticationDelegatingHandler>();

原因分析及解决方案

核心原因

  1. Signal R连接的上下文特性:Blazor Server改用Azure Signal R后,应用从短HTTP请求模型切换为长连接的Signal R会话。HttpContext仅在初始建立Signal R连接时存在,后续组件交互都在Signal R管道内进行,此时IHttpContextAccessor无法获取到有效的当前用户上下文——它依赖的是传统HTTP请求的上下文,而Signal R的后续消息不属于这个范畴。
  2. 生命周期不匹配:你的AuthenticationDelegatingHandler是Transient(瞬时)生命周期,而HttpClient默认的创建逻辑可能脱离了Blazor组件的Scoped(作用域)上下文,导致它无法关联到当前Signal R连接对应的用户会话。

解决方案

方案1:替换HttpContextAccessor,改用AuthenticationStateProvider获取用户信息

在Blazor Server中,官方推荐用AuthenticationStateProvider来获取用户信息,而非HttpContextAccessor。你可以:

  • 创建一个Scoped的用户信息服务,在组件初始化时把用户信息存入该服务,让DelegatingHandler从这个Scoped服务中读取userId。

示例:创建Scoped服务存储用户信息

public class CurrentUserService
{
    public string? UserObjectId { get; set; }
}

// 在Startup中注册为Scoped
builder.Services.AddScoped<CurrentUserService>();

在Blazor组件中初始化:

@inject AuthenticationStateProvider AuthStateProvider
@inject CurrentUserService CurrentUser

@code {
    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;
        CurrentUser.UserObjectId = user.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
    }
}

修改AuthenticationDelegatingHandler,注入CurrentUserService替代IHttpContextAccessor:

public class AuthenticationDelegatingHandler : DelegatingHandler
{
    private readonly ITokenAcquisition _tokenAcquisition;
    private readonly IConfiguration _configuration;
    private readonly ILogger<AuthenticationDelegatingHandler> _logger;
    private readonly CurrentUserService _currentUser;

    public AuthenticationDelegatingHandler(ITokenAcquisition tokenAcquisition, IConfiguration configuration, ILogger<AuthenticationDelegatingHandler> logger, CurrentUserService currentUser)
    {          
        _tokenAcquisition = tokenAcquisition;
        _configuration = configuration;
        _logger = logger;
        _currentUser = currentUser;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var scopes = new[] { _configuration["ApiScope"] };
        string accessToken = string.Empty;
        var userObject = _currentUser.UserObjectId;

        try
        {
            accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes!);
            _logger.LogInformation("Successfully retrieved access token from Azure for communication with API. User ObjectId - {UserObject}", userObject);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error while fetching access token for communication with API. Details: {Message}. {InnerException}", ex.Message, ex.InnerException);
            throw;
        }                        

        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        var response = await base.SendAsync(request, cancellationToken);            
        return response;
    }
}                

方案2:直接从ITokenAcquisition关联的用户上下文获取信息

ITokenAcquisition的GetAccessTokenForUserAsync方法本身依赖当前用户上下文,你可以尝试从它关联的用户Claims中直接获取userId,无需通过HttpContext:

// 在SendAsync方法中替换userObject的获取逻辑
var user = _tokenAcquisition.GetCurrentUser();
var userObject = user?.Claims.SingleOrDefault(_ => _.Type == ClaimTypes.NameIdentifier)?.Value;

方案3:确保HttpClient在组件Scoped上下文内使用

修改HttpClient的使用方式,在Blazor组件中通过IHttpClientFactory创建客户端,确保它运行在当前组件的Scoped上下文内,这样DelegatingHandler能关联到正确的用户会话:

@inject IHttpClientFactory HttpClientFactory

@code {
    private async Task CallApi()
    {
        var client = HttpClientFactory.CreateClient("WebAPI");
        // 调用API逻辑
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 06:25:55