多租户应用程序中的IdentityRole

时间:2016-10-31 04:26:07

标签: c# asp.net-mvc asp.net-mvc-5 asp.net-identity

我正在构建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)方法,所以我可以调试这个部分,看看发生了什么。见下文:

enter image description here

继续运行代码之后,我仍然在死亡的黄色屏幕上收到以下错误:

enter image description here

有人,任何人,请帮助阐明这里发生的事情以及是否有可能解决这个问题。

更新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 属性}?

1 个答案:

答案 0 :(得分:3)

好的,我已经解决了这个问题。答案是首先遵循我在原始帖子中添加的所有更新,然后最后要做的是让ApplicationDbContext继承自IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>,而不仅仅是IdentityDbContext<ApplicationUser>