ASP.NET Core中ProfileValidator类里User.Identity.Name返回null的原因?
ASP.NET Core 7中自定义ValidationAttribute无法获取User.Identity.Name的问题解决
问题根源
你在自定义验证特性DisplayNameForCreateAttribute里犯了两个关键错误:
- 手动实例化
AccessController(new AccessController())——这个实例脱离了ASP.NET Core的请求管道,框架不会为它绑定当前请求的上下文,因此控制器的User属性为空,GetUserName()自然返回null。 - 手动创建
ApplicationDbContext时传入空的DbContextOptions——这样的DbContext没有正确配置数据库连接,无法正常操作数据。
解决方案
方案一:在验证特性中正确获取请求上下文与DbContext
修改DisplayNameForCreateAttribute,通过ValidationContext获取服务提供者,从而拿到当前请求的HttpContext和配置好的DbContext:
using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Http; using Penfolio2.Data; using Penfolio2.Models; using System.ComponentModel.DataAnnotations; namespace Penfolio2.Validation { public class ProfileValidator { public class DisplayNameForCreateAttribute : ValidationAttribute { protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { var profile = (CreateProfileViewModel)validationContext.ObjectInstance; var errors = new List<IdentityError>(); // 获取服务提供者 var serviceProvider = validationContext.GetRequiredService<IServiceProvider>(); // 获取当前请求上下文 var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>(); var httpContext = httpContextAccessor.HttpContext; if (httpContext == null || httpContext.User.Identity?.Name == null) { errors.Add(new IdentityError() { Description = "仅注册用户可创建资料,请登录后重试。" }); } else { var username = httpContext.User.Identity.Name; // 获取配置好的DbContext实例 var db = serviceProvider.GetRequiredService<ApplicationDbContext>(); // 示例:检查DisplayName是否已被占用 bool displayNameExists = db.PenProfiles.Any(p => p.DisplayName == profile.DisplayName); if (displayNameExists) { errors.Add(new IdentityError() { Description = "该显示名称已被使用。" }); } } if (errors.Count > 0) { string errorString = string.Join(" ", errors.Select(e => e.Description)); return new ValidationResult(errorString); } return ValidationResult.Success; } } } }
同时需要在Program.cs中注册IHttpContextAccessor服务:
builder.Services.AddHttpContextAccessor();
方案二:将验证逻辑移至控制器(服务器端验证)
如果只需要服务器端验证,可移除ViewModel上的验证特性,在控制器POST方法中手动执行验证逻辑,这种方式更直接:
// POST: ProfileController/Create [Route("Profile/Create")] [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(CreateProfileViewModel model) { string userId = GetUserId(); var errors = new List<string>(); // 检查显示名称是否重复 bool displayNameExists = db.PenProfiles.Any(p => p.DisplayName == model.DisplayName); if (displayNameExists) { errors.Add("该显示名称已被使用。"); } // 其他自定义验证逻辑... // 将错误添加到ModelState foreach (var error in errors) { ModelState.AddModelError(nameof(model.DisplayName), error); } if (ModelState.IsValid) { // 执行资料创建逻辑 return RedirectToAction("Index"); } return View(model); }
额外优化:修正AccessController的DbContext注入
原代码中手动new DbContext的方式会导致连接泄漏等问题,改为构造函数注入:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Penfolio2.Data; namespace Penfolio2.Controllers { [Authorize] public class AccessController : Controller { private readonly ApplicationDbContext _db; public AccessController(ApplicationDbContext db) { _db = db; } public string? GetUserName() { return User?.Identity?.Name; } protected string GetUserId() { var user = _db.PenUsers.FirstOrDefault(i => i.NormalizedUserName == User.Identity.Name?.ToUpper()); if (user == null) { throw new InvalidOperationException("未找到当前用户。"); } return user.Id; } protected bool UserHasProfile() { return _db.PenProfiles.Any(i => i.UserId == GetUserId()); } } }
对应的ProfileController也要调整构造函数:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Penfolio2.Data; using Penfolio2.Models; namespace Penfolio2.Controllers { [Authorize] public class ProfileController : AccessController { private readonly ApplicationDbContext _db; private readonly IWebHostEnvironment _environment; public ProfileController(ApplicationDbContext db, IWebHostEnvironment environment) : base(db) { _db = db; _environment = environment; } // 其他方法保持不变 } }
内容的提问来源于stack exchange,提问作者Melissa
相关产品推荐
相关产品推荐

