为什么在使用 ASP.NET Core 时,对于等效于工作同步控制器的异步控制器,HttpContext.User.Identity.Name 显示为空?
Why does HttpContext.User.Identity.Name appear empty for async controller equivalent of working sync controller when using ASP.NET Core?
TLDR;我有几乎相同的控制器,它们仅在 async
/await
(当然还有 Task
)的使用上存在重大差异。非异步版本 returns 当前用户的用户名,正如预期的那样,但 async
等效版本没有。
我想发现的是
- 这可能成立的情况
- 我可以使用哪些工具(除了 Fiddler)来调查这个问题
注意:我无法针对 ASP.NET 核心源代码进行调试,因为尚未授予 运行 Set-ExecutionPolicy
的权限(这似乎是构建解决方案)
有效:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Baffled.API.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class IdentityController : ControllerBase
{
private readonly string userName;
public IdentityController(IHttpContextAccessor httpContextAccessor)
{
userName = httpContextAccessor?.HttpContext?.User.Identity?.Name;
}
[HttpGet]
public IActionResultGetCurrentUser()
{
return Ok(new { userName });
}
}
}
这不有效:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Baffled.Services;
namespace Baffled.API.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class IssueReproductionController : ControllerBase
{
private readonly HeartbeatService heartbeatService;
private readonly ILogger<IssueReproductionController> logger;
private readonly string userName;
public IssueReproductionController(
HeartbeatService heartbeatService,
ILogger<IssueReproductionController> logger,
IHttpContextAccessor httpContextAccessor)
{
this.heartbeatService = heartbeatService;
this.logger = logger;
userName = httpContextAccessor?.HttpContext?.User?.Identity?.Name;
}
[HttpGet]
public async Task<IActionResult> ShowProblem()
{
var heartbeats = await heartbeatService.GetLatest();
logger.LogInformation("Issue reproduction", new { heartbeats });
var currentUser = userName ?? HttpContext?.User.Identity?.Name;
return Ok(new { currentUser, heartbeats.Data });
}
}
}
我认为配置是正确的,但为了完整起见,它包含在下面:
Program.cs
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Baffled.API
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((_, config) =>
{
config.AddJsonFile("appsettings.json", true, true);
config.AddEnvironmentVariables();
})
.UseWindowsService()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseUrls("http://*:5002");
webBuilder.UseStartup<Startup>().UseHttpSys(Options);
});
private static void Options(HttpSysOptions options)
{
options.Authentication.AllowAnonymous = true;
options.Authentication.Schemes =
AuthenticationSchemes.NTLM |
AuthenticationSchemes.Negotiate |
AuthenticationSchemes.None;
}
}
}
Startup.cs
using System;
using System.Net.Http;
using Baffled.API.Configuration;
using Baffled.API.Hubs;
using Baffled.API.Services;
using Baffled.EF;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Serialization;
using Serilog;
using Serilog.Events;
namespace Baffled.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<HeartbeatService, HeartbeatService>();
services.AddHttpContextAccessor();
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme).AddNegotiate();
services.AddHttpClient("Default").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { UseDefaultCredentials = true });
services.AddControllers();
var corsOrigins = Configuration.GetSection("CorsAllowedOrigins").Get<string[]>();
services.AddCors(_ => _.AddPolicy("AllOriginPolicy", builder =>
{
builder.WithOrigins(corsOrigins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
services.AddSwagger();
services.AddSignalR();
services.AddHostedService<Worker>();
services.AddResponseCompression();
// Logging correctly configured here
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseSwagger();
app.UseAuthentication();
app.UseAuthorization();
app.UseResponseCompression();
app.UseCors("AllOriginPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<UpdateHub>("/updatehub");
});
}
}
}
我已经坐了几个星期了,希望能找到解决方案。 None有。请帮忙。
编辑
我通过 windows 服务托管了这个。奇怪的行为似乎是不一致和不确定的。有时我看到我的用户名被返回,但我的同事在遇到有问题的端点时从未这样做过。
在 .NET Core 中,您无法直接从 HttpContext 检索用户身份。
您还需要使用依赖项注入来获取对 http 上下文实例的引用。
这对我一直有效:
public class IssueReproductionController : ControllerBase
{
private readonly HeartbeatService heartbeatService;
private readonly ILogger<IssueReproductionController> logger;
private readonly string userName;
private readonly HttpContext context;
public IssueReproductionController(
HeartbeatService heartbeatService,
ILogger<IssueReproductionController> logger,
IHttpContextAccessor httpContextAccessor)
{
this.heartbeatService = heartbeatService;
this.logger = logger;
this.context = httpContextAccessor.HttpContext;
}
[HttpGet]
public async Task<IActionResult> ShowProblem()
{
...
var currentUser = this.context.Request.HttpContext.User.Identity.Name;
...
}
...
您不应尝试在控制器构造函数中访问特定于 HttpContext 的数据。该框架不保证 何时构造构造函数,因此很有可能构造函数是在 IHttpContextAccessor
访问当前 HttpContext
之前在此处创建的。相反,您应该仅在控制器操作本身内访问特定于上下文的数据,因为保证 运行 作为请求的一部分。
使用 IHttpContextAcccessor
时,您应该 始终 只在需要查看上下文的那一刻访问它的 HttpContext
。否则,很可能您正在使用过时的状态,这会导致各种问题。
然而,使用控制器时,实际上根本不需要使用 IHttpContextAccessor
。相反,该框架将为您提供多个属性,以便您可以直接在控制器上访问 HttpContext 甚至用户主体(使用 Razor 页面和 Razor 视图,有类似的属性可供使用):
请注意,这些属性 仅 在控制器操作中可用,而不是构造函数。有关详细信息,请参阅 .
以您的示例为例,您的控制器操作应该像这样工作:
[HttpGet]
public async Task<IActionResult> ShowProblem()
{
var heartbeats = await heartbeatService.GetLatest();
logger.LogInformation("Issue reproduction", new { heartbeats });
var currentUser = User.Identity.Name;
return Ok(new { currentUser, heartbeats.Data });
}
TLDR;我有几乎相同的控制器,它们仅在 async
/await
(当然还有 Task
)的使用上存在重大差异。非异步版本 returns 当前用户的用户名,正如预期的那样,但 async
等效版本没有。
我想发现的是
- 这可能成立的情况
- 我可以使用哪些工具(除了 Fiddler)来调查这个问题
注意:我无法针对 ASP.NET 核心源代码进行调试,因为尚未授予 运行 Set-ExecutionPolicy
的权限(这似乎是构建解决方案)
有效:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Baffled.API.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class IdentityController : ControllerBase
{
private readonly string userName;
public IdentityController(IHttpContextAccessor httpContextAccessor)
{
userName = httpContextAccessor?.HttpContext?.User.Identity?.Name;
}
[HttpGet]
public IActionResultGetCurrentUser()
{
return Ok(new { userName });
}
}
}
这不有效:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Baffled.Services;
namespace Baffled.API.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class IssueReproductionController : ControllerBase
{
private readonly HeartbeatService heartbeatService;
private readonly ILogger<IssueReproductionController> logger;
private readonly string userName;
public IssueReproductionController(
HeartbeatService heartbeatService,
ILogger<IssueReproductionController> logger,
IHttpContextAccessor httpContextAccessor)
{
this.heartbeatService = heartbeatService;
this.logger = logger;
userName = httpContextAccessor?.HttpContext?.User?.Identity?.Name;
}
[HttpGet]
public async Task<IActionResult> ShowProblem()
{
var heartbeats = await heartbeatService.GetLatest();
logger.LogInformation("Issue reproduction", new { heartbeats });
var currentUser = userName ?? HttpContext?.User.Identity?.Name;
return Ok(new { currentUser, heartbeats.Data });
}
}
}
我认为配置是正确的,但为了完整起见,它包含在下面:
Program.cs
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Baffled.API
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((_, config) =>
{
config.AddJsonFile("appsettings.json", true, true);
config.AddEnvironmentVariables();
})
.UseWindowsService()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseUrls("http://*:5002");
webBuilder.UseStartup<Startup>().UseHttpSys(Options);
});
private static void Options(HttpSysOptions options)
{
options.Authentication.AllowAnonymous = true;
options.Authentication.Schemes =
AuthenticationSchemes.NTLM |
AuthenticationSchemes.Negotiate |
AuthenticationSchemes.None;
}
}
}
Startup.cs
using System;
using System.Net.Http;
using Baffled.API.Configuration;
using Baffled.API.Hubs;
using Baffled.API.Services;
using Baffled.EF;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Serialization;
using Serilog;
using Serilog.Events;
namespace Baffled.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<HeartbeatService, HeartbeatService>();
services.AddHttpContextAccessor();
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme).AddNegotiate();
services.AddHttpClient("Default").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { UseDefaultCredentials = true });
services.AddControllers();
var corsOrigins = Configuration.GetSection("CorsAllowedOrigins").Get<string[]>();
services.AddCors(_ => _.AddPolicy("AllOriginPolicy", builder =>
{
builder.WithOrigins(corsOrigins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
services.AddSwagger();
services.AddSignalR();
services.AddHostedService<Worker>();
services.AddResponseCompression();
// Logging correctly configured here
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseSwagger();
app.UseAuthentication();
app.UseAuthorization();
app.UseResponseCompression();
app.UseCors("AllOriginPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<UpdateHub>("/updatehub");
});
}
}
}
我已经坐了几个星期了,希望能找到解决方案。 None有。请帮忙。
编辑
我通过 windows 服务托管了这个。奇怪的行为似乎是不一致和不确定的。有时我看到我的用户名被返回,但我的同事在遇到有问题的端点时从未这样做过。
在 .NET Core 中,您无法直接从 HttpContext 检索用户身份。 您还需要使用依赖项注入来获取对 http 上下文实例的引用。
这对我一直有效:
public class IssueReproductionController : ControllerBase
{
private readonly HeartbeatService heartbeatService;
private readonly ILogger<IssueReproductionController> logger;
private readonly string userName;
private readonly HttpContext context;
public IssueReproductionController(
HeartbeatService heartbeatService,
ILogger<IssueReproductionController> logger,
IHttpContextAccessor httpContextAccessor)
{
this.heartbeatService = heartbeatService;
this.logger = logger;
this.context = httpContextAccessor.HttpContext;
}
[HttpGet]
public async Task<IActionResult> ShowProblem()
{
...
var currentUser = this.context.Request.HttpContext.User.Identity.Name;
...
}
...
您不应尝试在控制器构造函数中访问特定于 HttpContext 的数据。该框架不保证 何时构造构造函数,因此很有可能构造函数是在 IHttpContextAccessor
访问当前 HttpContext
之前在此处创建的。相反,您应该仅在控制器操作本身内访问特定于上下文的数据,因为保证 运行 作为请求的一部分。
使用 IHttpContextAcccessor
时,您应该 始终 只在需要查看上下文的那一刻访问它的 HttpContext
。否则,很可能您正在使用过时的状态,这会导致各种问题。
然而,使用控制器时,实际上根本不需要使用 IHttpContextAccessor
。相反,该框架将为您提供多个属性,以便您可以直接在控制器上访问 HttpContext 甚至用户主体(使用 Razor 页面和 Razor 视图,有类似的属性可供使用):
请注意,这些属性 仅 在控制器操作中可用,而不是构造函数。有关详细信息,请参阅
以您的示例为例,您的控制器操作应该像这样工作:
[HttpGet]
public async Task<IActionResult> ShowProblem()
{
var heartbeats = await heartbeatService.GetLatest();
logger.LogInformation("Issue reproduction", new { heartbeats });
var currentUser = User.Identity.Name;
return Ok(new { currentUser, heartbeats.Data });
}