我想使用 Identity Framework 1.0 在 MVC 中开发一个应用程序,该应用程序允许用户使用其他用户使用的相同用户名进行注册。
删除用户时,我想将其
IsDeleted
自定义属性设置为 true,而不是从数据库中删除用户。在这种情况下,另一个用户可以使用 UserName
设置为 true 的用户的 IsDeleted
。
但是默认的
UserManager.CreateAsync(user, password);
方法阻止这样做。
我已经像这样覆盖了
ValidateEntity
的 IdentityDbContext
方法
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
if ((entityEntry != null) && (entityEntry.State == EntityState.Added))
{
ApplicationUser user = entityEntry.Entity as ApplicationUser;
if ((user != null) && this.Users.Any<ApplicationUser>(u => string.Equals(u.UserName, user.UserName) && u.IsDeleted==false))
{
return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()) {
ValidationErrors = {
new DbValidationError("User", string.Format(CultureInfo.CurrentCulture,
"", new object[] { user.UserName }))
}
};
}
IdentityRole role = entityEntry.Entity as IdentityRole;
if ((role != null) && this.Roles.Any<IdentityRole>(r => string.Equals(r.Name, role.Name)))
{
return new DbEntityValidationResult(entityEntry, new List<DbValidationError>()) {
ValidationErrors = {
new DbValidationError("Role", string.Format(CultureInfo.CurrentCulture,
"", new object[] { role.Name })) } };
}
}
return base.ValidateEntity(entityEntry, items);
}
这是我创建用户的注册方法
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName, Email = model.Email.ToLower(), CreatedBy = model.UserName, CreatedDate = DateTime.UtcNow, };
user.ConfirmedEmail = false;
var result = await _accountService.CreateAsync(user, model.Password);
if (result.Succeeded)
{
TempData["MessageConfirm"] = user.Email;
return RedirectToAction("Confirm", "Account");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
ValidateEntity
方法应该在await _accountService.CreateAsync(user, model.Password);
执行时执行。但它是在 register
方法完成执行后执行的。所以结果会抛出错误。
有什么建议我可以实现这一目标吗?
我最讨厌的是当人们决定提供建议而不是回答所提出的问题时。 用户名不必是唯一的。 db key 是 userid 而不是用户名。 如果您的应用程序为多家公司提供服务,则不同公司的员工有可能拥有相同的用户名。 为了做到这一点,你必须扩展 aspnet 身份。
https://www.scottbrady91.com/ASPNET-Identity/Quick-and-Easy-ASPNET-Identity-Multitenancy
身份用户 首先,我们需要通过扩展 IdentityUser 类来添加 TenantId 声明(您可以根据您的业务需求对其进行重命名)。虽然这在概念上是一个声明,但我们将利用 AspNetUser 表并将 TenantId 添加为属性,因为我们将通过此属性进行相当多的查询。为简单起见,我将 TenantId 添加为 int,但非迭代替代方案是使用字符串。
public class ApplicationUser : IdentityUser
{
public int TenantId { get; set; }
}
用户商店 接下来,我们将为了解新属性的新用户实现 UserStore。在这里,我们使用 UserStore 类中的属性来设置 TenantId,从而允许我们使用多租户实现覆盖基本实现。
public class ApplicationUserStore<TUser> : UserStore<TUser>
where TUser : ApplicationUser
{
public ApplicationUserStore(DbContext context)
: base(context) {
}
public int TenantId { get; set; }
}
创建用户异步
public override Task CreateAsync(TUser user)
{
if (user == null) {
throw new ArgumentNullException("user");
}
user.TenantId = this.TenantId;
return base.CreateAsync(user);
}
通过电子邮件查找异步
public override Task<TUser> FindByEmailAsync(string email)
{
return this.GetUserAggregateAsync(u =>
u.Email.ToUpper() == email.ToUpper() &&
u.TenantId == this.TenantId);
}
按名称查找异步
public override Task<TUser> FindByNameAsync(string userName)
{
return this.GetUserAggregateAsync(u =>
u.UserName.ToUpper() == userName.ToUpper() &&
u.TenantId == this.TenantId);
}
用户验证器 虽然默认的 UserValidator 对重复的用户名进行硬编码检查,但我们对 UserStore 方法 FindByNameAsync 和 FindByEmailAsync 的新实现将允许正确的多租户行为(假设您已在 UserStore 中设置 TenantId)。这意味着我们可以充分利用默认的 UserValidator 并在必要时扩展它。
IdentityDbContext 现在有一个尴尬的地方。 ASP.NET Identity 团队再次对 IdentityDbContext 类中的重复用户名检查进行硬编码,但这一次它既在 ValidateEntity 方法中又在 EF 数据库架构本身中使用索引。
索引可以通过扩展OnModelCreating方法来解决,根据用户名更改唯一索引,同时查找我们的TenantId(复合索引)。这可以避免我们丢失这个有用的索引,并优化我们的数据库以实现多租户。您可以使用以下覆盖方法来执行此操作:
public class ApplicationUserDbContext<TUser> : IdentityDbContext<TUser>
where TUser : ApplicationUser
{
public ApplicationUserDbContext(string nameOrConnectionString)
: base(nameOrConnectionString) {
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var user = modelBuilder.Entity<TUser>();
user.Property(u => u.UserName)
.IsRequired()
.HasMaxLength(256)
.HasColumnAnnotation("Index", new IndexAnnotation(
new IndexAttribute("UserNameIndex") { IsUnique = true, Order = 1}));
user.Property(u => u.TenantId)
.IsRequired()
.HasColumnAnnotation("Index", new IndexAnnotation(
new IndexAttribute("UserNameIndex") { IsUnique = true, Order = 2 }));
}
}
但是 ValidateEntity 方法有点棘手,因为我们必须重新实现整个方法才能删除硬编码的用户名检查:
protected override DbEntityValidationResult ValidateEntity(
DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
if (entityEntry != null && entityEntry.State == EntityState.Added) {
var errors = new List<DbValidationError>();
var user = entityEntry.Entity as TUser;
if (user != null) {
if (this.Users.Any(u => string.Equals(u.UserName, user.UserName)
&& u.TenantId == user.TenantId)) {
errors.Add(new DbValidationError("User",
string.Format("Username {0} is already taken for AppId {1}",
user.UserName, user.TenantId)));
}
if (this.RequireUniqueEmail
&& this.Users.Any(u => string.Equals(u.Email, user.Email)
&& u.TenantId == user.TenantId)) {
errors.Add(new DbValidationError("User",
string.Format("Email Address {0} is already taken for AppId {1}",
user.UserName, user.TenantId)));
}
}
else {
var role = entityEntry.Entity as IdentityRole;
if (role != null && this.Roles.Any(r => string.Equals(r.Name, role.Name)))
{
errors.Add(new DbValidationError("Role",
string.Format("Role {0} already exists", role.Name)));
}
}
if (errors.Any()) {
return new DbEntityValidationResult(entityEntry, errors);
}
}
return new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
}
客户 现在剩下的就是初始化类。不要忘记每次新建上下文时都需要提供 TenantId。请参阅下面的示例(请注意“示例”的使用,这些类都是一次性的......)。
var context = new ApplicationUserDbContext<ApplicationUser>("DefaultConnection");
var userStore = new ApplicationUserStore<ApplicationUser>(context) { TenantId = 1 };
var userManager = new UserManager<ApplicationUser, string>(userStore);
src:https://www.scottbrady91.com/aspnet-identity/quick-and-easy-aspnet-identity-multitenancy