EF Core SaveChanges方法中System.Threading.Thread.CurrentPrincipal为空的问题求助
I've run into this exact issue before with Blazor and EF Core—thread-bound principal objects just don't play nice with async contexts like Blazor's component lifecycle. Let's break down why your current approach isn't working and how to fix it properly.
Why Your Current JWT Setup Fails
Thread.CurrentPrincipal is tied to the specific thread that runs the OnTokenValidated event. But in Blazor (especially Blazor Server), async operations frequently switch threads, and that thread-bound principal doesn't carry over. ClaimsPrincipal.Current has the same problem—it relies on outdated thread context logic that doesn't work in modern async ASP.NET Core apps.
Solution 1: Use IHttpContextAccessor (Blazor Server)
For Blazor Server, the most straightforward fix is to use IHttpContextAccessor, which uses AsyncLocal under the hood to track the current user's context across async threads.
Step 1: Register the Accessor
Add this to your Program.cs to make the accessor available for injection:
builder.Services.AddHttpContextAccessor();
Step 2: Inject It Into Your DbContext
Update your BlazorRestaurantDbContext constructor to accept the accessor:
private readonly IHttpContextAccessor _httpContextAccessor; public BlazorRestaurantDbContext(DbContextOptions<BlazorRestaurantDbContext> options, IHttpContextAccessor httpContextAccessor) : base(options) { _httpContextAccessor = httpContextAccessor; }
Step 3: Fetch the User in ValidateAndSetDefaults
Replace your existing user lookup code with this:
private void ValidateAndSetDefaults() { // ... keep your existing code for IPs and assembly name ... string rowCreationUser = "Unknown"; if (entities.Any(p => p is IOriginatorInfo)) { ipAddresses = String.Join(",", GetCurrentHostIPv4Addresses()); assemblyFullName = System.Reflection.Assembly.GetEntryAssembly().FullName; var httpContext = _httpContextAccessor.HttpContext; if (httpContext?.User?.Identity != null && httpContext.User.Identity.IsAuthenticated) { rowCreationUser = httpContext.User.Identity.Name; } } // ... rest of your validation logic ... }
Solution 2: Custom IUserContext Service (Cross-Platform Friendly)
If you might ever switch to Blazor WebAssembly or want a more decoupled approach, create a dedicated user context service:
Step 1: Define the Interface
public interface IUserContext { string GetCurrentUserName(); }
Step 2: Implement for Blazor Server
public class ServerUserContext : IUserContext { private readonly IHttpContextAccessor _httpContextAccessor; public ServerUserContext(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public string GetCurrentUserName() { var user = _httpContextAccessor.HttpContext?.User; return user?.Identity?.IsAuthenticated == true ? user.Identity.Name : "Unknown"; } }
Step 3: Register the Service
In Program.cs:
builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped<IUserContext, ServerUserContext>();
Step 4: Inject and Use in DbContext
private readonly IUserContext _userContext; public BlazorRestaurantDbContext(DbContextOptions<BlazorRestaurantDbContext> options, IUserContext userContext) : base(options) { _userContext = userContext; } // Then in ValidateAndSetDefaults: rowCreationUser = _userContext.GetCurrentUserName();
Key Notes
- For Blazor WebAssembly:
IHttpContextAccessordoesn't work client-side, so you'll need to pass user info from the client to your API endpoints, or fetch it directly from the JWT token in your backend API logic. - Double-check your middleware order in
Program.cs: Make sureUseAuthentication()andUseAuthorization()come afterUseBlazorFrameworkFiles()andUseRouting(), but beforeUseEndpoints().
内容的提问来源于stack exchange,提问作者Eduardo Fonseca

