【发布时间】:2017-03-13 04:55:35
【问题描述】:
我正在构建一个 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”而不是仅通过名称来寻找唯一性?
更新
使用 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”中插入重复的键行。重复的键值为 (Administrators)。 声明已终止
我可以只修改数据库以更改我想的索引,但我需要它在安装时正确,因为我正在构建的解决方案是一个 CMS,并且将来会用于许多安装......
我的第一个想法是我需要以某种方式修改EntityTypeConfiguration<T> 的ApplicationRole 实体。但我当然不能立即访问它......它只是由ApplicationDbContext 自动创建,因为它继承自IdentityDbContext<ApplicationUser>。我将不得不深入研究反汇编代码,看看我能找到什么......
更新 2
好的,我使用base.OnModelCreating(modelBuilder); 来获取身份成员表的配置。我删除了该行并将反编译的代码复制到我的OnModelCreating 方法中,但删除了用于创建索引的部分。这(并删除了数据库中的索引)解决了我之前遇到的错误。但是,我还有 1 个错误,现在我完全被难住了...
我收到如下错误消息:
无法将值 NULL 插入到列“名称”、表“dbo.AspNetRoles”中;列不允许空值。插入失败。 声明已终止。
这没有任何意义,因为在调试时,我可以清楚地看到我在尝试创建的角色中传递了 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 中的 Name 属性和派生类 ApplicationRole 中的 TenantId 属性? p>
【问题讨论】:
标签: c# asp.net-mvc asp.net-mvc-5 asp.net-identity