4

I am attempting to extend the AspNet IdentityRole class provided in the Web API 2 (With Individual Accounts) template in the latest version of Visual Studio 2013. When I hit /api/roles it returns an empty array

Identity Models

namespace API.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser<string, IdentityUserLogin, ApplicationUserRole, IdentityUserClaim>, IUser, IUser<string>
{
    [NotMapped]
    public virtual UserProfile Profile { get; set; }

    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }

    //public virtual List<ApplicationUserRole> ApplicationRoles { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager,
        string authenticationType)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        ClaimsIdentity userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
        // Add custom user claims here
        return userIdentity;
    }
}

public class ApplicationRole : IdentityRole<string, ApplicationUserRole>
{
    public ApplicationRole() : base() { }

}

public class ApplicationUserRole : IdentityUserRole<string>
{
    public ApplicationUserRole()
        : base()
    { }

    public virtual ApplicationRole Role { get; set; }
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser,ApplicationRole,string,IdentityUserLogin,ApplicationUserRole,IdentityUserClaim> 

{

     public ApplicationDbContext()
        : base("DefaultConnection")
    {
        base.Configuration.ProxyCreationEnabled = false;
    }

     public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

}

Identity Config

namespace API
{
// Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.

public class ApplicationUserManager : UserManager<ApplicationUser, string>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store)
        : base(store)
    {
    }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options,
        IOwinContext context)
    {
        var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));

        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };
        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };
        IDataProtectionProvider dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider =
                new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }
}



public class ApplicationRoleManager : RoleManager<ApplicationRole, string>
{
    public ApplicationRoleManager(IRoleStore<ApplicationRole, string> store)
        : base(store)
    {
    }

    public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options,
        IOwinContext context)
    {
        var manager = new ApplicationRoleManager(new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
        return manager;
    }

}

}

Dushan Perera
  • 69
  • 1
  • 8
  • Out of curiosity, why do you want to extend the role class? I find that most times, you really don't need to.. you need only add items to the claim. Most people assume you have to extend things to get more functionality, and that's not always true. – Erik Funkenbusch Sep 04 '14 at 21:33
  • I want to extend the roles so that I can add a Permissions object to them, and I want to extend userRoles so that I can add a virtual role object. Right now when I include user.Roles when getting a user, it returns UserRole objects but I can't for instance include UserRole.Role, so that I have all my data. – Dushan Perera Sep 04 '14 at 21:40
  • Why do you need to add a Permissions object to Roles? Roles is just, essentially, a tag.. If you want to build a complex permission system, I would instead create a UserRole mapping object that maps Roles to permissions. I have no idea what you mean by "virtual role object" either. I think you're going about this backwards. – Erik Funkenbusch Sep 04 '14 at 21:53
  • If I had a mapping object between roles and permissions, my understanding is that would be a many-to-many relationship, but I only want a one-to-one, that's why I wanted to add a permissions object as a property of Role. – Dushan Perera Sep 04 '14 at 21:59
  • By virtual role object, here is what I mean. Let's say I get a user using db.User.Where(t=>t.Id == id). If I want to include the roles in the object, I add .Include("Roles") before the where clause. This fills out the Roles[] property on IdentityUser with the connected IdentityUserRoles. However, if I want the Role name, I cannot go one step further and say .Include("Roles.Role") for example, because IdentityUserRoles doesn't have a navigation property for the Role represented by the RoleId property. – Dushan Perera Sep 04 '14 at 21:59
  • You seem to be confusing the RoleManager (and UserManager) with doing queries against your database. They're two different things. If you want the Role for a user, use the RoleManager. – Erik Funkenbusch Sep 04 '14 at 22:09
  • I understand that, but I want it all in a single, nested object that I can pass back to my client-side app. Even if I used RoleManager, the Roles property on IdentityUser is of type IdentityUserRole. How would you approach this differently? – Dushan Perera Sep 04 '14 at 22:11

1 Answers1

8

your mistake is in here

ApplicationDbContext : IdentityDbContext<ApplicationUser>

your db context is extent from IdentityDbContext<ApplicationUser> which is base from

IdentityDbContext<TUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>

ApplicationDbContext will generate IdentityRole when actualy you want ApplicationRole as your role.

Change your ApplicationDbContext to

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserLogin, ApplicationUserRole, IdentityUserClaim>
{

 public ApplicationDbContext()
    : base("DefaultConnection", false)
 {
    base.Configuration.ProxyCreationEnabled = false;
 }

 public static ApplicationDbContext Create()
 {
    return new ApplicationDbContext();
 }
}

you don't have to overide OnModelCreating and add property Roles in this point.

since you have navigation property in ApplicationUserRole you should add configurationmapping

protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<ApplicationUserRole>().HasRequired(ur => ur.Role).WithMany().HasForeignKey(ur => ur.RoleId);
}

IdentityModel.cs should be like this :

public class ApplicationUserRole : IdentityUserRole<string>
{
    public ApplicationUserRole()
        : base()
    { }

    public virtual ApplicationRole Role { get; set; }
}

public class ApplicationRole : IdentityRole<string, ApplicationUserRole>
{
    public ApplicationRole() : base() { }

    public ApplicationRole(string name, string description)
        : base()
    {
        this.Name = name;
        this.Description = description;
    }

    public string Description { get; set; }
}

public class ApplicationUser : IdentityUser<string, IdentityUserLogin, ApplicationUserRole, IdentityUserClaim>
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserLogin, ApplicationUserRole, IdentityUserClaim>
{

    public ApplicationDbContext()
        : base("DefaultConnection")
    {
        base.Configuration.ProxyCreationEnabled = false;
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
    protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<ApplicationUserRole>().HasRequired(ur => ur.Role).WithMany().HasForeignKey(ur => ur.RoleId);
    }
}

identityconfig.cs should be like this:

public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, string, IdentityUserLogin, ApplicationUserRole, IdentityUserClaim>, IUserStore<ApplicationUser>
{
    public ApplicationUserStore(ApplicationDbContext context)
        : base(context)
    {
    }   
}

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store)
        : base(store)
    {
    }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
    {
        var manager = new ApplicationUserManager(new ApplicationUserStore(context.Get<ApplicationDbContext>()));
        ....
    }
}
public class ApplicationRoleStore : RoleStore<ApplicationRole, string, ApplicationUserRole>
{
    public ApplicationRoleStore(ApplicationDbContext context)
        : base(context)
    {

    }
}
public class ApplicationRoleManager : RoleManager<ApplicationRole, string>
{
    public ApplicationRoleManager(IRoleStore<ApplicationRole, string> store)
        : base(store)
    {
    }

    public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options,
        IOwinContext context)
    {
        var manager = new ApplicationRoleManager(new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
        return manager;
    }
}
Aiska Hendra
  • 111
  • 4
  • When I try to do this I get the following errors (For both user and role): "The type 'API.Models.ApplicationRole' cannot be used as type parameter 'TRole' in the generic type or method...There is no implicit reference conversion from 'API.Models.ApplicationRole' to 'Microsoft.AspNet.Identity.EntityFramework.IdentityRole'. – Dushan Perera Sep 04 '14 at 19:58
  • Ah sorry i forgot, u have to change ApplicationRole : IdentityRole – Aiska Hendra Sep 04 '14 at 20:47
  • That fixed my models page, but now my RoleManager and UserManager are blowing up, with a similar error (cannot convert ApplicationRole to IdentityRole): var manager = new ApplicationRoleManager(new RoleStore(context.Get())); – Dushan Perera Sep 04 '14 at 21:02
  • Hmm, Update 3 & 4 didn't seem to fix the error. I will update my post to show my code as it is now. -- Posted – Dushan Perera Sep 04 '14 at 21:33
  • new RoleStore(context.Get()) <--This is where the error is occurring, when creating a new RoleStore. There is no implicit reference conversion from API.Models.ApplicationRole to IdentityRole – Dushan Perera Sep 04 '14 at 22:09
  • Works great, except when trying to set custom validator at the `ApplicationUserManager Create()` method. this line `manager.UserValidator = new CustomUserValidator(manager);` doesn't compile. adding IUser inheritance to ApplicationUser sort it out. `public class ApplicationUser : IdentityUser, IUser` – Issac Jun 28 '15 at 15:18