Blazor Server应用中登录用户信息缓存优化咨询:避免重复获取EmployeeId及存储复杂用户对象的实现方案
这确实是Blazor Server开发中很常见的性能优化痛点——每个页面初始化都去查数据库,不仅浪费资源,还会拖慢页面加载速度。结合Windows认证的场景,我给你几个实用的优化方案,按推荐度排序:
方案1:扩展AuthenticationStateProvider+内存缓存(最推荐)
Blazor Server本身依赖AuthenticationStateProvider管理用户身份,我们可以扩展这个类,在用户首次登录时一次性查询并缓存员工信息,后续所有组件直接从缓存或身份凭证中获取数据,完全避免重复查库。
实现步骤:
- 自定义AuthenticationStateProvider
继承ServerAuthenticationStateProvider(适配Windows认证场景),重写身份获取逻辑,同时加入缓存和员工信息的Claims注入:
public class EmployeeAuthStateProvider : ServerAuthenticationStateProvider { private readonly IEmployeeService _employeeService; private readonly IMemoryCache _cache; public EmployeeAuthStateProvider(IEmployeeService employeeService, IMemoryCache cache) { _employeeService = employeeService; _cache = cache; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var baseAuthState = await base.GetAuthenticationStateAsync(); var user = baseAuthState.User; if (user.Identity.IsAuthenticated) { var cacheKey = $"Employee_{user.Identity.Name}"; // 优先从缓存取员工信息 if (!_cache.TryGetValue(cacheKey, out Employee employee)) { employee = await _employeeService.GetByShortName(user.Identity.Name); if (employee == null) { // 返回未授权状态,后续组件可处理跳转 return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } // 将员工信息转为Claims,方便组件直接从User对象获取 var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, employee.EmployeeId), new Claim("FullName", employee.FullName) }; // 批量添加角色Claims employee.Roles.ForEach(role => claims.Add(new Claim(ClaimTypes.Role, role.Name))); // 更新身份凭证 var newIdentity = new ClaimsIdentity(user.Identity.AuthenticationType); newIdentity.AddClaims(claims); var updatedUser = new ClaimsPrincipal(newIdentity); // 缓存8小时(匹配会话有效期) _cache.Set(cacheKey, employee, TimeSpan.FromHours(8)); return new AuthenticationState(updatedUser); } else { // 缓存已存在,直接构造带Claims的身份对象 var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, employee.EmployeeId), new Claim("FullName", employee.FullName) }; employee.Roles.ForEach(role => claims.Add(new Claim(ClaimTypes.Role, role.Name))); var newIdentity = new ClaimsIdentity(user.Identity.AuthenticationType); newIdentity.AddClaims(claims); return new AuthenticationState(new ClaimsPrincipal(newIdentity)); } } return baseAuthState; } // 提供便捷方法,直接获取完整员工对象 public async Task<Employee> GetCurrentEmployeeAsync() { var authState = await GetAuthenticationStateAsync(); var userName = authState.User.Identity.Name; return await _cache.GetOrCreateAsync($"Employee_{userName}", async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(8); return await _employeeService.GetByShortName(userName); }); } }
- 注册服务
在Program.cs中替换默认的身份提供者,并添加内存缓存:
builder.Services.AddScoped<AuthenticationStateProvider, EmployeeAuthStateProvider>(); builder.Services.AddMemoryCache();
- 组件中使用
任何组件只需注入AuthenticationStateProvider,即可直接获取员工信息:
@inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavigationManager protected string EmployeeId { get; set; } protected Employee CurrentEmployee { get; set; } protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); // 从Claims直接获取ID EmployeeId = authState.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; // 或者获取完整员工对象 CurrentEmployee = await ((EmployeeAuthStateProvider)AuthStateProvider).GetCurrentEmployeeAsync(); if (CurrentEmployee == null) { NavigationManager.NavigateTo("/accessdenied"); } }
优势:
- 完全贴合Blazor身份系统设计,无需额外依赖
- 仅首次加载时查一次数据库,后续全程用缓存
- 员工信息可通过
User对象的Claims全局访问,代码更简洁
方案2:Scoped全局用户上下文服务
创建一个Scoped生命周期的UserContext服务,在用户会话初始化时加载员工信息,所有组件直接注入该服务使用。
实现步骤:
- 定义UserContext类
public class UserContext { public Employee CurrentEmployee { get; set; } public string EmployeeId => CurrentEmployee?.EmployeeId; public List<Role> Roles => CurrentEmployee?.Roles ?? new List<Role>(); }
- 注册服务
builder.Services.AddScoped<UserContext>();
- 初始化上下文
在根组件(如App.razor)的初始化方法中加载员工信息:
@inject UserContext UserContext @inject IEmployeeService EmployeeService @inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavigationManager protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var userName = authState.User.Identity.Name; UserContext.CurrentEmployee = await EmployeeService.GetByShortName(userName); if (UserContext.CurrentEmployee == null) { NavigationManager.NavigateTo("/accessdenied"); } }
- 组件中使用
@inject UserContext UserContext protected override void OnInitialized() { if (UserContext.CurrentEmployee == null) { NavigationManager.NavigateTo("/accessdenied"); return; } EmployeeId = UserContext.EmployeeId; }
优势:
- 逻辑直观,适合新手理解
- Scoped生命周期确保每个用户会话有独立的上下文实例
方案3:Session中间件(备选)
利用ASP.NET Core Session存储员工信息,在用户首次请求时查库并存入Session,后续组件从Session读取。
注意事项:
Blazor Server基于SignalR长连接,HttpContext仅在初始请求时有效,后续SignalR消息中无法直接访问Session,因此该方案仅适合初始加载场景,若用户重新连接需重新初始化。
内容的提问来源于stack exchange,提问作者SandroRiz
相关产品推荐
相关产品推荐

