我正在构建ASP.NET MVC 5多租户解决方案,并且在角色方面存在一些问题。我创建了一个自定义角色实体,如下所示:
public class ApplicationRole : IdentityRole, ITenantEntity
{
public ApplicationRole()
: base()
{
}
public ApplicationRole(string roleName)
: base(roleName)
{
}
public int? TenantId { get; set; }
}
并完成了所有其他所需的工作......除了一件事之外,一切都很顺利......当租户管理员尝试添加新角色,并且该角色的名称已由另一个租户创建的角色使用时,他将收到以下错误:
名称管理员已被占用。
显然,某些基础检查角色名称在ASP.NET身份中是唯一的。有没有办法改变这一点,以便我可以通过“TenantId + Name”来寻找唯一性,而不仅仅是Name?
更新
使用dotPeek反编译DLL,我发现我需要创建自己的IIdentityValidator实现,当然还要修改我的RoleManager。所以,这是我的角色验证器:
public class TenantRoleValidator : IIdentityValidator<ApplicationRole>
{
private RoleManager<ApplicationRole, string> Manager { get; set; }
/// <summary>Constructor</summary>
/// <param name="manager"></param>
public TenantRoleValidator(RoleManager<ApplicationRole, string> manager)
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
this.Manager = manager;
}
/// <summary>Validates a role before saving</summary>
/// <param name="item"></param>
/// <returns></returns>
public virtual async Task<IdentityResult> ValidateAsync(ApplicationRole item)
{
if ((object)item == null)
{
throw new ArgumentNullException("item");
}
var errors = new List<string>();
await this.ValidateRoleName(item, errors);
return errors.Count <= 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
}
private async Task ValidateRoleName(ApplicationRole role, List<string> errors)
{
if (string.IsNullOrWhiteSpace(role.Name))
{
errors.Add("Name cannot be null or empty.");
}
else
{
var existingRole = await this.Manager.Roles.FirstOrDefaultAsync(x => x.TenantId == role.TenantId && x.Name == role.Name);
if (existingRole == null)
{
return;
}
errors.Add(string.Format("{0} is already taken.", role.Name));
}
}
}
我的角色经理:
public class ApplicationRoleManager : RoleManager<ApplicationRole>
{
public ApplicationRoleManager(IRoleStore<ApplicationRole, string> store)
: base(store)
{
this.RoleValidator = new TenantRoleValidator(this);
}
public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
{
return new ApplicationRoleManager(
new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
}
}
但是,我现在收到一个新错误:
无法在具有唯一索引“RoleNameIndex”的对象“dbo.AspNetRoles”中插入重复的键行。重复键值为(管理员)。 该声明已被终止
我可以修改数据库以更改我想要的索引,但我需要它在安装时是正确的,因为我正在构建的解决方案是CMS,将来会用于许多安装......
我的第一个想法是我需要修改EntityTypeConfiguration<T>
实体的ApplicationRole
。但是我当然没有立即访问它......它只是由ApplicationDbContext
自动创建,因为它继承自IdentityDbContext<ApplicationUser>
。我将不得不深入研究反汇编的代码,看看我能找到什么......
更新2
好的,我使用base.OnModelCreating(modelBuilder);
来获取身份成员资格表的配置。我删除了该行并将反编译的代码复制到我的OnModelCreating
方法,但删除了用于创建索引的部分。这个(以及删除数据库中的索引)解决了我之前遇到的错误..但是,我还有一个错误,我现在完全被困......
我收到如下错误消息:
无法将值NULL插入列'Name',表'dbo.AspNetRoles';列不允许空值。 INSERT失败。 声明已经终止。
这没有任何意义,因为在调试时,我可以清楚地看到我正在尝试创建的角色中传递Name和TenantId。这是我的代码:
var result = await roleManager.CreateAsync(new ApplicationRole
{
TenantId = tenantId,
Name = role.Name
});
这些值不为空,所以我不知道这里发生了什么。任何帮助都将非常感激。
更新3
我创建了自己的RoleStore,它继承自RoleStore<ApplicationRole>
并且我覆盖了CreateAsync((ApplicationRole role)
方法,所以我可以调试这个部分,看看发生了什么。见下文:
继续运行代码之后,我仍然在死亡的黄色屏幕上收到以下错误:
有人,任何人,请帮助阐明这里发生的事情以及是否有可能解决这个问题。
更新4
好的,我现在更接近答案..我从头创建了一个新的数据库(允许EF创建它),我注意到Name列没有被创建...只有Id和TenantId ..这个表示之前的错误是因为我的现有数据库已经具有Name列并且设置为NOT NULL ..并且由于某种原因,EF忽略了我的角色实体的Name列,我认为这与继承自IdentityRole的事件有关。
这是我的模型配置:
var rolesTable = modelBuilder.Entity<ApplicationRole>().ToTable("AspNetRoles");
rolesTable.Property(x => x.TenantId)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("RoleNameIndex") { IsUnique = true, Order = 1 }));
rolesTable.Property(x => x.Name)
.IsRequired()
.HasMaxLength(256)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("RoleNameIndex") { IsUnique = true, Order = 2 }));
rolesTable.HasMany(x => x.Users).WithRequired().HasForeignKey(x => x.RoleId);
我认为这可能与索引配置有关,所以我只删除了这些(TenantId和Name)并将其替换为:
rolesTable.Property(x => x.Name)
.IsRequired()
.HasMaxLength(256);
但是,仍未创建“名称”列。现在和之前的唯一区别是,我使用的是modelBuilder.Entity<ApplicationRole>()
,而我默认的默认值是modelBuilder.Entity<IdentityRole>()
...
如何让EF识别基类IdentityRole
中的名称属性以及派生类{{1}中的 TenantId 属性}?
答案 0 :(得分:3)
好的,我已经解决了这个问题。答案是首先遵循我在原始帖子中添加的所有更新,然后最后要做的是让ApplicationDbContext
继承自IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
,而不仅仅是IdentityDbContext<ApplicationUser>