ASP.NET Core MVC Identity实现手机号与SSN唯一校验的解决方案咨询
前置说明
ASP.NET Core Identity 默认脚手架生成的是 Razor Pages 代码而非 MVC 控制器,注册相关逻辑默认存放在Areas/Identity/Pages/Account/Register.cshtml.cs文件中,你可以直接修改该文件的逻辑,也可以自行编写MVC控制器承接注册请求。
方案1:Remote异步校验(前端实时提示)
就是你提到的remote-validator方案,依赖ASP.NET Core内置的Remote特性实现前端异步校验:
- 首先给模型属性新增
[Remote]注解:
[Required(ErrorMessage ="身份证号为必填项")] [DataType(DataType.Text)] [Display(Name = "身份证号")] [StringLength(10, MinimumLength =10, ErrorMessage ="身份证号必须为10位")] [RegularExpression("(^[0-9]*)(^[12].*)", ErrorMessage = "身份证号必须为数字")] // 新增Remote特性,指向校验接口 [Remote(action: "VerifyNationalIdUnique", controller: "Account", ErrorMessage = "该身份证号已被注册")] public string NationalId { get; set; } [Required(ErrorMessage ="手机号为必填项")] [DataType(DataType.PhoneNumber)] [Display(Name = "手机号")] [RegularExpression(@"^((?:[+?0?0?966]+)(?:\s?\d{2})(?:\s?\d{7}))$", ErrorMessage = "手机号格式不正确")] [Remote(action: "VerifyPhoneUnique", controller: "Account", ErrorMessage = "该手机号已被注册")] public string PhoneNumber { get; set; }
- 编写对应的校验接口:
public class AccountController : Controller { private readonly UserManager<你的自定义用户实体类> _userManager; public AccountController(UserManager<你的自定义用户实体类> userManager) { _userManager = userManager; } [AcceptVerbs("Get", "Post")] public async Task<IActionResult> VerifyNationalIdUnique(string nationalId) { var isExist = await _userManager.Users.AnyAsync(u => u.NationalId == nationalId); return !isExist ? Json(true) : Json("该身份证号已被注册"); } [AcceptVerbs("Get", "Post")] public async Task<IActionResult> VerifyPhoneUnique(string phoneNumber) { var isExist = await _userManager.Users.AnyAsync(u => u.PhoneNumber == phoneNumber); return !isExist ? Json(true) : Json("该手机号已被注册"); } }
- 确保注册页面引入了
jquery.validate.min.js和jquery.validate.unobtrusive.min.js,即可触发前端实时校验。
方案2:后端提交逻辑校验(兜底必备)
前端校验可被绕过,必须在后端提交逻辑中增加校验:
如果用默认的Identity Razor Pages,直接修改Register.cshtml.cs的OnPostAsync方法:
public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); // 新增唯一性校验逻辑 var nationalIdExist = await _userManager.Users.AnyAsync(u => u.NationalId == Input.NationalId); if (nationalIdExist) { ModelState.AddModelError("Input.NationalId", "该身份证号已被注册"); } var phoneExist = await _userManager.Users.AnyAsync(u => u.PhoneNumber == Input.PhoneNumber); if (phoneExist) { ModelState.AddModelError("Input.PhoneNumber", "该手机号已被注册"); } if (!ModelState.IsValid) { return Page(); } // 原有创建用户逻辑保持不变 // ...... }
更高效稳妥的优化方案(推荐必加)
前面两种校验都存在并发风险(两个用户同时提交相同信息,同时通过校验会重复入库),必须在数据库层面增加唯一索引从底层拦截重复数据:
修改你的DbContext配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 身份证号加唯一索引 modelBuilder.Entity<你的自定义用户实体类>() .HasIndex(u => u.NationalId) .IsUnique() .HasDatabaseName("IX_Users_NationalId"); // 手机号加唯一索引 modelBuilder.Entity<你的自定义用户实体类>() .HasIndex(u => u.PhoneNumber) .IsUnique() .HasDatabaseName("IX_Users_PhoneNumber"); }
配置完成后执行迁移更新数据库即可,就算上层校验没拦住,数据库也会抛出唯一约束异常,你可以在提交逻辑中捕获该异常返回友好提示。
可选优化:自定义全局验证属性
如果需要在多处复用唯一性校验逻辑,可以自定义验证属性,无需重复编写校验代码:
public class UniqueFieldAttribute : ValidationAttribute { private readonly string _fieldName; public UniqueFieldAttribute(string fieldName) { _fieldName = fieldName; } protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { if (value == null) return ValidationResult.Success; var userManager = validationContext.GetService<UserManager<你的自定义用户实体类>>(); var isExist = userManager.Users.Any(u => EF.Property<string>(u, _fieldName) == value.ToString()); return isExist ? new ValidationResult(ErrorMessage) : ValidationResult.Success; } }
直接在模型属性上使用即可:
[UniqueField(nameof(NationalId), ErrorMessage = "该身份证号已被注册")] public string NationalId { get; set; } [UniqueField(nameof(PhoneNumber), ErrorMessage = "该手机号已被注册")] public string PhoneNumber { get; set; }
内容的提问来源于stack exchange,提问作者Mohammed Abed
相关产品推荐
相关产品推荐

