I'm trying to pull out all my Identity users and their associated roles for a user management admin page. I thought this would be reasonably easy but apparently not. I've tried following the following solution: https://stackoverflow.com/a/43562544/5392786 but it hasn't worked out so far.
Here is what I have so far:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public List<IdentityUserRole<string>> Roles { get; set; }
}
DBContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Startup Identity code
services.AddIdentity<ApplicationUser, IdentityRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Razor Page where I want to display the list:
public class IndexModel : PageModel
{
private readonly UserManager<ApplicationUser> userManager;
public IndexModel(UserManager<ApplicationUser> userManager)
{
this.userManager = userManager;
}
public IEnumerable<ApplicationUser> Users { get; set; }
public void OnGetAsync()
{
this.Users = userManager.Users.Include(u => u.Roles).ToList();
}
}
I get the following error when calling userManager.Users.Include(u => u.Roles).ToList();
:
MySql.Data.MySqlClient.MySqlException: 'Unknown column 'u.Roles.ApplicationUserId' in 'field list''
I have now implemented the following solution.
As CodeNotFound pointed out in the comments, IdentityUser used to have a Roles
property. This is no longer the case in .NET Core. This comment/issue on GitHub seems to be the current solution for .Net Core. I have attempted to implemented it with the following code:
ApplicationUser
public class ApplicationUser : IdentityUser
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
ApplicationUserRole
public class ApplicationUserRole : IdentityUserRole<string>
{
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
}
ApplicationRole
public class ApplicationRole : IdentityRole
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
DBContext
public class ApplicationDbContext
: IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ApplicationUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
}
Startup
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Finally, make sure when you're using it that you eagerly load the User's UserRoles, and then the UserRole's Role like so:
this.Users = userManager.Users.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).ToList();
I had an issue where the Role
property of each UserRole
was null and this was resolved by adding in the .ThenInclude(ur => ur.Role)
part.
Microsoft doc on multi-level eager loading: https://docs.microsoft.com/en-us/ef/core/querying/related-data#including-multiple-levels
Reference comment
First is the code to get data
public async Task<IEnumerable<AccountViewModel>> GetUserList()
{
var userList = await (from user in _context.Users
select new
{
UserId = user.Id,
Username = user.UserName,
user.Email,
user.EmailConfirmed,
RoleNames = (from userRole in user.Roles //[AspNetUserRoles]
join role in _context.Roles //[AspNetRoles]//
on userRole.RoleId
equals role.Id
select role.Name).ToList()
}).ToListAsync();
var userListVm = userList.Select(p => new AccountViewModel
{
UserId = p.UserId,
UserName = p.Username,
Email = p.Email,
Roles = string.Join(",", p.RoleNames),
EmailConfirmed = p.EmailConfirmed.ToString()
});
return userListVm;
}
In ASP.Net core 2.1 we to setup ApplicationRole like this in order to get Roles of users. You need to defined data you want explicit expose for user to use
public class ApplicationRole : IdentityRole
{
public virtual ICollection<IdentityUserRole<string>> Users { get; set; }
public virtual ICollection<IdentityRoleClaim<string>> Claims { get; set; }
}
Finally
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
modelBuilder.Entity<User>().HasMany(u => u.Claims).WithOne().HasForeignKey(c => c.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<User>().HasMany(u => u.Roles).WithOne().HasForeignKey(r => r.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ApplicationRole>().HasMany(r => r.Claims).WithOne().HasForeignKey(c => c.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ApplicationRole>().HasMany(r => r.Users).WithOne().HasForeignKey(r => r.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.EnableAutoHistory(null);
}
The result will be the user name and user roles. If user have more than 1 roles the data will display like this
Admin, Editor, etc...
Full code can be found here here here and here
Hope this help.
I solved this by creating a view with all the columns I needed (including roles) and adding it to the context.
loops through user list and get user roles by calling _userManager.GetRolesAsync(user) function and loops through roles of user and split roles with "," in one string variable
[HttpPost]
public async Task<IActionResult> OnPostGetPagination()
{
var users = await _userManager.Users.ToListAsync();
InputModel inputModel = new InputModel();
foreach (var v in users)
{
inputModel = new InputModel();
var roles = await _userManager.GetRolesAsync(v);
inputModel.Email = v.UserName;
inputModel.role = "";
foreach (var r in roles)
{
if (!inputModel.role.Contains(","))
{
inputModel.role = r;
}
else
{
inputModel.role = "," + r;
}
}
Input2.Add(inputModel);
}
}
good luck