IdentityRole в мультитенантном приложении

Я строю мультитенантное решение 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 Identity где-то. Есть ли способ изменить это так, чтобы я мог искать уникальность по "TenantId + 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>()));
        }
    }

Однако теперь я получаю новую ошибку:

Невозможно вставить повторяющуюся строку ключа в объект "dbo.AspNetRoles" с уникальным индексом "RoleNameIndex". Значение дубликата ключа (Администраторы). Заявление было прекращено

Я мог бы просто изменить базу данных, чтобы изменить индексы, я полагаю, но мне нужно, чтобы она была правильной при установке, потому что решение, которое я создаю, - это CMS и будет использоваться для многих установок в будущем...

Моя первая мысль: мне нужно как-то изменить EntityTypeConfiguration<T> для ApplicationRole юридическое лицо. Но, конечно, у меня нет немедленного доступа к этому... он просто автоматически создается ApplicationDbContext потому что он наследует от IdentityDbContext<ApplicationUser>, Мне придется глубже вникать в разобранный код и посмотреть, что я могу найти...

ОБНОВЛЕНИЕ 2

Хорошо я использовал base.OnModelCreating(modelBuilder); чтобы получить конфигурации для таблиц членства идентичности. Я удалил эту строку и скопировал декомпилированный код в мой OnModelCreating метод, но удалил часть для создания индекса. Это (и удаление индекса в БД) решило ту ошибку, которая у меня была раньше... однако, у меня есть еще 1 ошибка, и я теперь совершенно тупик...

Я получаю сообщение об ошибке следующим образом:

Невозможно вставить значение NULL в столбец "Имя", таблица "dbo.AspNetRoles"; столбец не допускает нулевые значения. Вставить не удается. Заявление было прекращено.

Это не имеет смысла, потому что при отладке я ясно вижу, что передаю Имя и TenantId в роли, которую пытаюсь создать. Это мой код:

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

Эти значения не равны нулю, поэтому я не знаю, что здесь происходит. Любая помощь будет наиболее ценной.

ОБНОВЛЕНИЕ 3

Я создал свой собственный RoleStore, который наследуется от RoleStore<ApplicationRole> и я преодолел CreateAsync((ApplicationRole role) метод, чтобы я мог отладить эту часть и посмотреть, что происходит. Увидеть ниже:

введите описание изображения здесь

Продолжая запускать код, я все еще получаю следующую ошибку на желтом экране смерти:

введите описание изображения здесь

Кто-нибудь, кто-нибудь, пожалуйста, помогите пролить свет на то, что здесь происходит, и если это вообще возможно исправить.

ОБНОВЛЕНИЕ 4

Хорошо, теперь я ближе к ответу.. Я создал новую базу данных с нуля (позволяя EF создавать ее), и я заметил, что столбец Имя не создается... только Id и TenantId.. это означает, что предыдущий ошибка в том, что моя существующая БД уже имела столбец Имя и была установлена ​​на NOT NULL.. и EF по какой-то причине игнорирует столбец Имя для моей сущности роли, которая, как я полагаю, имеет какое-то отношение к ней, наследуемой от 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 распознавать оба свойства Name из базового класса, IdentityRole и свойство TenantId из производного класса ApplicationRole?

1 ответ

Решение

ОК, я решил это. Ответ заключается в том, чтобы в первую очередь следить за всеми обновлениями, которые я добавил в исходное сообщение, а затем последнее, что нужно сделать, это сделать мой ApplicationDbContext наследовать от IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> вместо просто IdentityDbContext<ApplicationUser>

Другие вопросы по тегам