多租户应用中的 IdentityRole [英] IdentityRole in multi-tenant application

查看:21
本文介绍了多租户应用中的 IdentityRole的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个 ASP.NET MVC 5 多租户解决方案,但在角色方面有一个小问题.我创建了一个自定义角色实体,如下所示:

I am building an ASP.NET MVC 5 multi-tenant solution and have a slight problem when it comes to roles. I have created a custom role entity as follows:

public class ApplicationRole : IdentityRole, ITenantEntity
    {
        public ApplicationRole()
            : base()
        {
        }

        public ApplicationRole(string roleName)
            : base(roleName)
        {
        }

        public int? TenantId { get; set; }
    }

并完成了其他所有需要的工作......一切都很好,除了一件事......;当租户管理员尝试添加新角色并且该角色的名称已被另一个租户创建的角色使用时,他将收到以下错误:

And done everything else needed.. it's all working nicely, except for one thing...; when a tenant admin tries to add a new role and if that role's name is already being used by a role created by another tenant, he will get the following error:

名称管理员已被占用.

很明显,在 ASP.NET Identity 的某处,有一些基本的检查角色名称是唯一的.有什么方法可以改变这一点,以便我可以通过租户 ID + 名称"而不是仅名称来查找唯一性?

Obviously there is some underlying check for role names to be unique in ASP.NET Identity somewhere. Is there some way to change this so that I can make it look for uniqueness by "TenantId + Name", instead of Name only?

更新

使用 dotPeek 反编译 DLL,我发现我需要创建自己的 IIdentityValidator 实现,当然还要修改我的 RoleManager.所以,这是我的角色验证器:

Using dotPeek to decompile the DLLs, I have found that I need to create my own implementation of IIdentityValidator and of course modify my RoleManager. So, here's my role validator:

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>()));
        }
    }

但是,我现在收到一个新错误:

However, I am now getting a new error:

无法在具有唯一索引RoleNameIndex"的对象dbo.AspNetRoles"中插入重复的键行.重复的键值为 (Administrators).声明已终止

我可以修改数据库来更改我想的索引,但我需要它在安装时正确,因为我正在构建的解决方案是一个 CMS,将来会用于许多安装......

I could just modify the db to change the indexes I suppose, but I need it to be correct on installation because the solution I am building is a CMS and will be used for many installations in future...

我的第一个想法是我需要为 ApplicationRole 实体修改 EntityTypeConfiguration.但当然我没有立即访问它......它只是由 ApplicationDbContext 自动创建,因为它继承自 IdentityDbContext.我将不得不深入研究反汇编的代码,看看我能找到什么......

My first thought is I somehow need to modify the EntityTypeConfiguration<T> for the ApplicationRole entity. But of course I don't have immediate access to that... it just gets auto created by the ApplicationDbContext because it inherits from IdentityDbContext<ApplicationUser>. I will have to delve deeper into the disassembled code and see what I can find...

更新 2

好的,我使用 base.OnModelCreating(modelBuilder); 来获取身份成员资格表的配置.我删除了该行并将反编译的代码复制到我的 OnModelCreating 方法中,但删除了用于创建索引的部分.这(并删除数据库中的索引)解决了我之前遇到的错误..但是,我还有 1 个错误,现在我完全被难住了...

OK, I was using base.OnModelCreating(modelBuilder); to get the configurations for the identity membership tables. I removed that line and copied the decompiled code to my OnModelCreating method, but removed the part for creating the index. This (and removing the index in the db) solved that error I had before.. however, I have 1 more error and I am totally stumped now...

我收到如下错误消息:

无法将值 NULL 插入到列 'Name'、表 'dbo.AspNetRoles' 中;列不允许空值.插入失败.声明已终止.

这没有任何意义,因为在调试时,我可以清楚地看到我正在尝试创建的角色中传递 Name 和 TenantId.这是我的代码:

This makes no sense, because when debugging, I can clearly see I am passing the Name and the TenantId in the role I am trying to create. This is my code:

var result = await roleManager.CreateAsync(new ApplicationRole
            {
                TenantId = tenantId,
                Name = role.Name
            });

那些值不为空,所以我不知道这里发生了什么.任何帮助将不胜感激.

Those values are not null, so I don't know what's going on here anymore. Any help would be most appreciated.

更新 3

我创建了我自己的 RoleStore,它继承自 RoleStore 并且我覆盖了 CreateAsync((ApplicationRole role) 方法,所以我可以调试这部分,看看有什么正在发生.见下文:

I created my own RoleStore, which inherits from RoleStore<ApplicationRole> and I overrode the CreateAsync((ApplicationRole role) method so I can debug this part and see what's happening. See below:

继续运行代码后,还是出现如​​下黄屏死机错误:

After continuing to run the code, I still get the following error on the yellow screen of death:

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

Someone, anyone, please help shed some light on what's happening here and if it's at all possible to fix this.

更新 4

好的,我现在更接近答案了..我从头开始创建了一个新数据库(允许 EF 创建它),我注意到 Name 列没有被创建......只有 Id 和 TenantId.. 这个意味着之前的错误是因为我现有的数据库已经有 Name 列并且设置为 NOT NULL ..并且 EF 由于某种原因忽略了我的角色实体的 Name 列,我认为这与它从 IdentityRole 继承有关.

OK, I'm closer to the answer now.. I created a new db from scratch (allowing EF to create it) and I noticed that the Name column does not get created... only Id and TenantId.. this means the previous error is because my existing DB had the Name column already and was set to NOT NULL.. and EF is ignoring the Name column for my role entity for some reason, which I assume has something to do with it inheriting from 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)并将其替换为:

I thought it was maybe something to do with the index config, so I just removed both of those (TenantId and Name) and replaced it with this:

rolesTable.Property(x => x.Name)
                .IsRequired()
                .HasMaxLength(256);

但是,名称列仍未创建.现在和以前的唯一区别是,我使用的是 modelBuilder.Entity() 而默认值是 modelBuilder.Entity() 我假设...

However, the Name column was still not created. The only difference between now and before, is that I am using modelBuilder.Entity<ApplicationRole>() whereas the default would have been modelBuilder.Entity<IdentityRole>() I suppose...

如何让 EF 识别来自基类的 Name 属性、IdentityRole 和来自派生类的 TenantId 属性<代码>ApplicationRole?

How can I get EF to recognize the both Name property from the base class, IdentityRole and the TenantId property from the derived class ApplicationRole?

推荐答案

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

OK I've solved this. The answer is to firsrtly follow all the updates I added in my original post and then the final thing to do was make my ApplicationDbContext inherit from IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> instead of just IdentityDbContext<ApplicationUser>

这篇关于多租户应用中的 IdentityRole的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆