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

Blazor Server中DbContext获取当前登录用户的异常问题求助

Blazor Server中DbContextFactory结合审计获取当前用户的解决方案

你遇到的错误Cannot resolve scoped service 'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider' from root provider,本质是生命周期不匹配:DbContextFactory默认是Singleton服务,它创建DbContext时使用的是根服务容器,而AuthenticationStateProvider是Scoped服务(每个用户会话一个实例),根容器无法解析Scoped服务,否则会导致Scoped服务被Singleton持有,引发线程安全和数据混淆问题。

以下是三个保留DbContextFactory核心优势的可行解决方案:

方案1:调用DbContext时手动传入用户ID(推荐,简单直接)

不在DbContext中依赖AuthenticationStateProvider,而是在业务层先获取用户信息,再传递给DbContext用于审计逻辑。

步骤1:修改CustomDbContext,添加UserId属性

public partial class CustomDbContext : DbContext
{
    // 存储当前操作的用户ID
    public string? UserId { get; set; }

    // 保留无AuthenticationStateProvider依赖的构造函数
    public CustomDbContext(DbContextOptions<CustomDbContext> options) : base(options) { }

    // 重写SaveChanges处理审计逻辑
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        if (!string.IsNullOrEmpty(UserId))
        {
            // 示例:给审计实体设置创建/修改人信息
            foreach (var entry in ChangeTracker.Entries()
                     .Where(e => e.State is EntityState.Added or EntityState.Modified))
            {
                if (entry.Entity is IAuditableEntity auditable)
                {
                    if (entry.State == EntityState.Added)
                    {
                        auditable.CreatedBy = UserId;
                        auditable.CreatedAt = DateTime.UtcNow;
                    }
                    auditable.UpdatedBy = UserId;
                    auditable.UpdatedAt = DateTime.UtcNow;
                }
            }
        }

        return await base.SaveChangesAsync(cancellationToken);
    }
}

步骤2:在组件/服务中获取用户ID并传入DbContext

@inject IDbContextFactory<CustomDbContext> DbContextFactory
@inject AuthenticationStateProvider AuthStateProvider

async Task SaveEntity()
{
    // 获取当前用户认证状态
    var authState = await AuthStateProvider.GetAuthenticationStateAsync();
    var userId = authState.User.Claims.FirstOrDefault(c => c.Type == "userId")?.Value ?? string.Empty;

    // 创建DbContext并设置用户ID
    using var context = DbContextFactory.CreateDbContext();
    context.UserId = userId;

    // 执行数据操作
    context.YourEntities.Add(new YourEntity { /* 属性赋值 */ });
    await context.SaveChangesAsync();
}

方案2:封装Scoped服务统一获取带用户信息的DbContext

如果不想每次手动传UserId,可以封装一个Scoped服务,自动处理用户信息注入,同时保留DbContextFactory的Singleton特性。

步骤1:创建Scoped上下文提供者

public interface IScopedDbContextProvider
{
    Task<CustomDbContext> GetDbContextAsync();
}

public class ScopedDbContextProvider : IScopedDbContextProvider
{
    private readonly IDbContextFactory<CustomDbContext> _dbContextFactory;
    private readonly AuthenticationStateProvider _authStateProvider;

    public ScopedDbContextProvider(IDbContextFactory<CustomDbContext> dbContextFactory, 
                                   AuthenticationStateProvider authStateProvider)
    {
        _dbContextFactory = dbContextFactory;
        _authStateProvider = authStateProvider;
    }

    public async Task<CustomDbContext> GetDbContextAsync()
    {
        var context = _dbContextFactory.CreateDbContext();
        var authState = await _authStateProvider.GetAuthenticationStateAsync();
        context.UserId = authState.User.Claims.FirstOrDefault(c => c.Type == "userId")?.Value ?? string.Empty;
        return context;
    }
}

步骤2:注册Scoped服务

在Program.cs中添加:

builder.Services.AddScoped<IScopedDbContextProvider, ScopedDbContextProvider>();

步骤3:在组件/服务中使用

@inject IScopedDbContextProvider DbContextProvider

async Task SaveData()
{
    using var context = await DbContextProvider.GetDbContextAsync();
    // 直接使用上下文,已自动设置UserId
    context.YourEntities.Add(new YourEntity());
    await context.SaveChangesAsync();
}

方案3:使用EF Core拦截器实现审计(解耦审计逻辑)

将审计逻辑从DbContext中分离,用EF Core拦截器处理,通过拦截器传递用户ID,避免DbContext依赖认证服务。

步骤1:创建审计拦截器

public class AuditInterceptor : SaveChangesInterceptor
{
    private string? _userId;

    // 设置当前操作的用户ID
    public void SetUserId(string userId)
    {
        _userId = userId;
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, 
                                                                          InterceptionResult<int> result, 
                                                                          CancellationToken cancellationToken = default)
    {
        if (!string.IsNullOrEmpty(_userId) && eventData.Context is not null)
        {
            // 遍历实体处理审计逻辑
            foreach (var entry in eventData.Context.ChangeTracker.Entries()
                     .Where(e => e.State is EntityState.Added or EntityState.Modified))
            {
                if (entry.Entity is IAuditableEntity auditable)
                {
                    if (entry.State == EntityState.Added)
                    {
                        auditable.CreatedBy = _userId;
                        auditable.CreatedAt = DateTime.UtcNow;
                    }
                    auditable.UpdatedBy = _userId;
                    auditable.UpdatedAt = DateTime.UtcNow;
                }
            }
        }

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

步骤2:注册拦截器到DbContextFactory

builder.Services.AddDbContextFactory<CustomDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
    // 添加审计拦截器
    options.AddInterceptors(new AuditInterceptor());
});

步骤3:在调用时设置用户ID

@inject IDbContextFactory<CustomDbContext> DbContextFactory
@inject AuthenticationStateProvider AuthStateProvider

async Task SaveEntity()
{
    var authState = await AuthStateProvider.GetAuthenticationStateAsync();
    var userId = authState.User.Claims.FirstOrDefault(c => c.Type == "userId")?.Value ?? string.Empty;

    using var context = DbContextFactory.CreateDbContext();
    // 获取拦截器并设置用户ID
    var auditInterceptor = context.GetService<AuditInterceptor>();
    auditInterceptor?.SetUserId(userId);

    context.YourEntities.Add(new YourEntity());
    await context.SaveChangesAsync();
}

以上方案均保留了DbContextFactory的核心优势(控制上下文生命周期、支持并发场景),同时规避了Scoped服务注入Singleton的风险。其中方案1最直接简单,适合大多数场景;方案2适合需要复用上下文获取逻辑的场景;方案3适合审计逻辑复杂、需要与DbContext解耦的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 13:35:35