Extending ASP.NET Identity Roles: IdentityRole is not part of the model for the current context

24,758

Solution 1

After a few days of trying to get this to work in a clean manner, I've come to the conclusion that if you're using Database first and want to integrate ASP.NET Identity into your app, by far the easiest and cleanest solution is to create your own membership provider by overriding ASP.NET Identity. It's actually pretty easy, so far I've implemented UserStore and RoleStore to my liking. I've added columns/relations specific to my domain in my database, and whenever I create a user or a role, I take care of my database commits by adding the required relations. My UserStore implementation is quite similar to this. My RoleStore implementation is something like this:

public class ApplicationRoleStore : IRoleStore<ApplicationRoleDTO>
{
    private PayrollDBEntities _context;
    public ApplicationRoleStore() { }

    public ApplicationRoleStore(PayrollDBEntities database)
    {
        _context = database;
    }

    public Task CreateAsync(ApplicationRoleDTO role)
    {
        if (role == null)
        {
            throw new ArgumentNullException("RoleIsRequired");
        }
        var roleEntity = ConvertApplicationRoleDTOToAspNetRole(role);
        _context.AspNetRoles.Add(roleEntity);
        return _context.SaveChangesAsync();

    }

    public Task DeleteAsync(ApplicationRoleDTO role)
    {
        var roleEntity = _context.AspNetRoles.FirstOrDefault(x => x.Id == role.Id);
        if (roleEntity == null) throw new InvalidOperationException("No such role exists!");
        _context.AspNetRoles.Remove(roleEntity);
        return _context.SaveChangesAsync();
    }

    public Task<ApplicationRoleDTO> FindByIdAsync(string roleId)
    {
        var role = _context.AspNetRoles.FirstOrDefault(x => x.Id == roleId);

        var result = role == null
            ? null
            : ConvertAspNetRoleToApplicationRoleDTO(role);

        return Task.FromResult(result);
    }

    public Task<ApplicationRoleDTO> FindByNameAsync(string roleName)
    {

        var role = _context.AspNetRoles.FirstOrDefault(x => x.Name == roleName);

        var result = role == null
            ? null
            : ConvertAspNetRoleToApplicationRoleDTO(role);

        return Task.FromResult(result);
    }

    public Task UpdateAsync(ApplicationRoleDTO role)
    {

        return _context.SaveChangesAsync();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
    private ApplicationRoleDTO ConvertAspNetRoleToApplicationRoleDTO(AspNetRole aspRole)
    {
        return new ApplicationRoleDTO{
            Id = aspRole.Id,
            EnterpriseId = aspRole.EnterpriseId,
            Name = aspRole.Name
        };
    }

    private AspNetRole ConvertApplicationRoleDTOToAspNetRole(ApplicationRoleDTO appRole)
    {
        return new AspNetRole{
            Id = appRole.Id,
            EnterpriseId = appRole.EnterpriseId,
            Name = appRole.Name,
        };
    }
}

And my ApplicationRoleDTO:

public class ApplicationRoleDTO : IRole
{
    public ApplicationRoleDTO()
    {
        Id = Guid.NewGuid().ToString();
    }

    public ApplicationRoleDTO(string roleName)
        : this()
    {
        Name = roleName;
    }
    public string Id { get; set; }
    public string Name { get; set; }
    public Guid EnterpriseId { get; set; }
}

I also found these 2 articles pretty helpful:

Overview of Custom Storage Providers for ASP.NET Identity

Implementing a Custom MySQL ASP.NET Identity Storage Provider

Solution 2

You have to specify during the creation of User Store that AspNetRole is used instead of IdentityRole. You can achieve this by using the UserStore class with 6 type parameters:

new UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>(new PayrollDBEntities());

This indicates changes at User Manager creation as well. Here is a simplified example about the creation of needed instances:

public class AspNetUser : IdentityUser { /*customization*/ }

public class AspNetRole : IdentityRole { /*customization*/ }

public class PayrollDBEntities : IdentityDbContext //or : IdentityDbContext <AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> 
{
}

public class Factory 
{
    public IdentityDbContext DbContext 
    { 
        get 
        {
            return new PayrollDBEntities();
        } 
    }

    public UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> UserStore
    {
        get 
        {                
            return new UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>(DbContext);
        }
    }

    public UserManager<AspNetUser, string> UserManager
    { 
        get 
        {
            return new UserManager<AspNetUser, string>(UserStore);
        } 
    }

    public RoleStore<AspNetRole> RoleStore 
    {
        get 
        {
            return new RoleStore<AspNetRole>(DbContext);
        }
    }

    public RoleManager<AspNetRole> RoleManager 
    {
        get 
        {
            return new RoleManager<AspNetRole>(RoleStore);
        }
    }
}

Solution 3

I know this is an old question, but just in case someone else is having a hard time adding roles/users when they modified asp identity to use numeric primary keys (int/long) instead of the default string for the Identity Roles, so if you have changed the IdentityUserRole in IdentityModels.cs to something like this:

public class Role : IdentityRole<long, UserRole>
{
    public Role() { }
    public Role(string name) { Name = name; }
}

You have to use the class Role instead of the default IdentityRole when constructing the RoleManager, so your code should be like this:

public static void RegisterUserRoles()
{
     ApplicationDbContext context = new ApplicationDbContext();

     var RoleManager = new RoleManager<Role, long>(new RoleStore(context));

     if (!RoleManager.RoleExists("Administrador"))
     {
         var adminRole = new Role {
              Name = "Administrador",
         };
         RoleManager.Create(adminRole);
     }
}

So this should populate your database properly, I think all experienced ASP programmers already know this, but for others this could take some time to figure out.

Solution 4

I'll explain here with the code exampels :).

The trick is, they are already in the IdentityDbContext (AspNetRoles, AspNetUserClaims, AspNetUsers, ....)

In the IdentityModel you will see ApplicationUser is empty at the top. If you want to customize these users or roles, just add properties here and then update your database via the console

Example of my context

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<Request> Requests { get; set; }
    public DbSet<Reservation> Reservations { get; set; }
    public DbSet<PriceType> PriceTypes { get; set; }
    public DbSet<Product> Products { get; set; }
    public DbSet<Price> Prices { get; set; }
    public DbSet<GuestbookPost> Posts { get; set; }
    public DbSet<Count> Counts { get; set; }
    public DbSet<Invoice> Invoices { get; set; }
    public DbSet<InvoiceLine> InvoiceLines { get; set; }

    ...

}

So no application user is defined here, but I did add more properties to it, example:

public class ApplicationUser : IdentityUser
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string GroupName { get; set; }
    public string Email { get; set; }
    [StringLength(15)]
    public string Phone { get; set; }
    public string Remark { get; set; }
    public DateTime? BirthDate { get; set; }
    public DateTime ValidFrom { get; set; }
    public DateTime ValidUntil { get; set; }

    public string Street { get; set; }
    public string ZipCode { get; set; }
    public string City { get; set; }

    public virtual ICollection<Request> Requests { get; set; } 
}
Share:
24,758
Mohammad Sepahvand
Author by

Mohammad Sepahvand

Updated on July 09, 2022

Comments

  • Mohammad Sepahvand
    Mohammad Sepahvand almost 2 years

    I'm trying to use the new ASP.NET Identity in my MVC5 application, specifically I'm trying to integrate ASP.NET Identity into an existing database. I've already read the questions/answers on SO pertaining to DB First and ASP.NET Identity, and having followed all the recommendations I still can't add roles to my database, although I have no problems adding users. Here's my code:

    var context = new PayrollDBEntities();
    var roleManager = new RoleManager<AspNetRole>(new RoleStore<AspNetRole>(context));
    
    bool roleExists = roleManager.RoleExists(roleDto.Name);
    if (roleExists){
        return false;
    }
    
    var role = new AspNetRole(roleDto.Name){
        Name = roleDto.Name,
    };
    
    IdentityResult result = roleManager.Create(role);//Getting exception here
    

    At the last line of code I get an exception of type 'System.InvalidOperationException': The entity type IdentityRole is not part of the model for the current context.

    Here is my context:

    public partial class PayrollDBEntities : IdentityDbContext
    {
            public PayrollDBEntities()
                : base("name=PayrollDBEntities")
            {
            }
    
            public virtual DbSet<AspNetRole> AspNetRoles { get; set; }
            public virtual DbSet<AspNetUserClaim> AspNetUserClaims { get; set; }
            public virtual DbSet<AspNetUserLogin> AspNetUserLogins { get; set; }
            public virtual DbSet<AspNetUser> AspNetUsers { get; set; }
    ......
    }
    

    My AspNetUser and AspNetRole classes derive from IdentityUser and IdentityRole respectively, but I'm still getting that exception. Here is my database diagram:

    enter image description here

    Any help would be greatly appreciated.

  • Mohammad Sepahvand
    Mohammad Sepahvand about 10 years
    Thanks Jelle, I did as you suggested, however the same problem. Btw, Isn't update-database used for code first? I'm using database first.
  • Jelle Oosterbosch
    Jelle Oosterbosch about 10 years
    Seing you're reply, I did had the same problem once. Try to inherit the IdentityDbContext with the ApplicationUser? PayrollDBEntities : IdentityDbContext<ApplicationUser>
  • Mohammad Sepahvand
    Mohammad Sepahvand about 10 years
    same problem still, The entity type ApplicationUser is not part of the model for the current context.
  • Jelle Oosterbosch
    Jelle Oosterbosch about 10 years
    public PayrollDBEntities() : base("name=PayrollDBEntities") May I ask why U use name=?
  • Mohammad Sepahvand
    Mohammad Sepahvand about 10 years
    that's the default data context definition created by visual studio, that name= is inconsequential, removing it didn't help either.
  • Jelle Oosterbosch
    Jelle Oosterbosch about 10 years
    Sorry it took so long :)
  • mejdev
    mejdev about 8 years
    Future viewers: Pay attention to ALL of the changes to generic parameters! I think the sample templates come with an ApplicationUserManager that overrides UserManager<ApplicationUser> which I missed -- it needed to change to UserManager<ApplicationUser, string>
  • Click Ok
    Click Ok over 7 years
    @mjohnsonengr I came from the future to say: Thanks!
  • Giox
    Giox over 6 years
    I'm facing the same issue, as I want to "simply" add a new field for the role, but I can't find a solution. Do you think I have to implement from scratch the RoleStore? Could you please have a look at this to get me some direction on how to solve it? stackoverflow.com/questions/47351526/…