Creating inheritance users from base asp.net identity user

13,434

Solution 1

As others do too I think this is a design problem. There are some alternative approaches like:

  1. use roles to define the "user-type" (a user can be supplier AND customer)
  2. make the Supplier and Customer entities a relation not extension of the user

e.g.:

public class ApplicationUser : IdentityUser
{
    public virtual Customer Customer { get; set; }
    public virtual Supplier Supplier { get; set; }
}

public class Customer
{
    [Key]
    public int Id { get; set; }

    public virtual ApplicationUser User { get; set; }
    public string CustomerProperty { get; set; }
}

public class Supplier
{
    [Key]
    public int Id { get; set; }

    public virtual ApplicationUser User { get; set; }
    public string SupplierProperty { get; set; }
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Supplier> Suppliers { get; set; }
}

public class ApplicationDbInitializer
             : DropCreateDatabaseAlways<ApplicationDbContext>
{
    protected override void Seed(ApplicationDbContext context)
    {
        var userStore = new UserStore(context);
        var userManager = new UserManager(userStore);
        var roleManager = new RoleManager(roleStore);

        var user = userManager.FindByEmail("[email protected]");
        if (user == null)
        {
            user = new ApplicationUser()
            {
                UserName = "[email protected]",
                Email = "[email protected]"
                Customer = new Customer()
                {
                    CustomerProperty = "Additional Info"
                }
            };

            userManager.Create(user, userPassword);
            roleManager.AddUserToRole("Customer");
        }

        user = userManager.FindByEmail("[email protected]");
        if (user == null)
        {
            user = new ApplicationUser()
            {
                UserName = "[email protected]",
                Email = "[email protected]",
                Supplier = new Supplier()
                {
                    IBAN = "212323424342234",
                    Relationship = "OK"
                }
            };

            userManager.Create(user, userPassword);
            roleManager.AddUserToRole("Supplier");
        }
    }
}

and in your logic you can do something like:

if (User.IsInRole("Customer"))
{
    // do something
}

DISCLAIMER: This is not a "copy&paste" example and should just give you an idea of a different approach.

Solution 2

I just resolved a similar problem. I created a navigation property of abstract type DomainUser in my AppUser (that inherits from Identity User)

public class AppUser : IdentityUser
{
    public DomainUser DomainUser { get; set; }
}

DomainUser looks like this:

public abstract class DomainUser : IAggregateRoot
{
    public Guid Id { get; set; }
    public AppUser IdentityUser { get; set; }
}

I inherit from DomainUser in all concrete domain user types:

public class AdministrationUser : DomainUser
{
    public string SomeAdministrationProperty { get; set; }
}

public class SupplierUser : DomainUser
{
    public string SomeSupplierProperty { get; set; }
}

public class Customer : DomainUser
{
    public string SomeCustomerProperty { get; set; }
}

And in DbContext in OnModelCreating method I configured Entity Framework to store all entities inherited from DomainUser in separate tables (it's called Table per Concrete Type). And configured one to one relationship between IdentityUser and DomainUser:

modelBuilder.Entity<DomainUser>()
            .Map<AdministrationUser>(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("AdministrationUsers");
            })
            .Map<SupplierUser>(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("SupplierUsers");
            })
            .Map<Customer>(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("Customers");
            });

modelBuilder.Entity<DomainUser>()
            .HasRequired(domainUser => domainUser.IdentityUser)
            .WithRequiredPrincipal(groomUser => groomUser.DomainUser);

This code added column "DomainUser_Id" to table AspNetUsers and now I'm able to access IdentityUser navigation property in each domain user and DomainUser navigation property in AppUser.

Share:
13,434
Frank
Author by

Frank

Updated on June 26, 2022

Comments

  • Frank
    Frank almost 2 years

    I have problem in which i would like to create N, two in the example, user objects (e.g. Customer & Supplier) which all inherent from the asp.net IdentityUser object. These object have very different additional data besides the the data from the IdentityUser. I would like to use the IdentityUser user as this gives me a flexible way of taking care of authentication and authorization.

    This example has been very stripped down but should supply sufficient information concerning the not being able to create a concrete user (e.g. Customer of Supplier). It seems i need to use the UserManager object as this also takes care of creating for example the password hash and additional security information.

    I get presented the following error:

    {"Attaching an entity of type 'Supplier' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate."}

    Classes which inherent from IdentityUser

     public class Customer : IdentityUser
     {
        public string CustomerProperty { get; set; }
     }
    
     public class Supplier : IdentityUser
     {
        public string SupplierProperty { get; set; }
     }
    

    Database context class

     public class ApplicationDbContext : IdentityDbContext {
    
          public ApplicationDbContext() : base("ApplicationDbContext")
          {
             Database.SetInitializer(new ApplicationDbInitializer());
          }
    
          public DbSet<Customer> CustomerCollection { get; set; }
          public DbSet<Supplier> SupplierCollection { get; set; }
     }
    

    Seeding class which throws the exception

     public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
     {
        protected override void Seed(ApplicationDbContext context)
        {
            var userStore = new UserStore(context);
            var userManager = new UserManager(userStore);
    
    
            // Seed customer user which inherents from asp.net IdentityUser 
            var user = userManager.FindByEmail("[email protected]");
            if (user == null)
            {
                user = new User()
                {
                    UserName = "[email protected]",
                    Email = "[email protected]"
                };
    
                userManager.Create(user, userPassword);
    
                var customerUser = new Customer()
                {
                    Id = user.Id,
                    CustomerProperty = "Additional Info"
                };
    
                context.Entry(customerUser).State = EntityState.Modified;
                context.SaveChanges();
            }
    
            // Seed supplier user which inherents from asp.net IdentityUser 
            var user = userManager.FindByEmail("[email protected]");
            if (user == null)
            {
                user = new User()
                {
                    UserName = "[email protected]",
                    Email = "[email protected]"
                };
    
                userManager.Create(user, userPassword);
    
                var supplierUser = new Supplier()
                {
                    Id = user.Id,
                    IBAN = "212323424342234",
                    Relationship = "OK"
                };
    
                context.Entry(supplierUser).State = EntityState.Modified;
                context.SaveChanges();
            }
        }
    }
    

    **** UPDATE ****

    The solution below works but i am still struggling with two issues:

    1. I would always like to have one user type (e.g. Customer of Supplier) associated with the IdentityUser. I though about using an interface but this doesn't work.
    2. If i also add the virtual reference towards the IdentityUser on the user types i get an 'Unable to determine the principal end of an association between the types 'ApplicaitonUser' and 'Supplier'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.' exception.

    Classes

     public class Customer 
     {
        [Key]
        public int CustomerId { get;set; }
        public string CustomerProperty { get; set; }
    
        *public virtual User User { get; set; }*
    
     }
    
     public class Supplier 
     {
        [Key]
        public int SupplierId { get;set; }
        public string SupplierProperty { get; set; }
    
        *public virtual User User { get; set; }*
     }
    

    **Class IdentityUser (which works) **

    public class User : IdentityUser
    {
        public virtual Supplier Supplier { get; set; }
        public virtual Customer Customer { get; set; }
    }
    

    **Class IdentityUser (what i would like) **

    public class User : IdentityUser
    {
        public virtual IConcreteUser ConcreteUser{ get; set; }
    }
    

    Database context class

     public class ApplicationDbContext : IdentityDbContext {
    
          public ApplicationDbContext() : base("ApplicationDbContext")
          {
             Database.SetInitializer(new ApplicationDbInitializer());
          }
    
          public DbSet<Customer> CustomerCollection { get; set; }
          public DbSet<Supplier> SupplierCollection { get; set; }
     }
    

    **Seeding class **

     public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
     {
    protected override void Seed(ApplicationDbContext context)
    {
        var userStore = new UserStore(context);
        var userManager = new UserManager(userStore);
        var roleManager = new RoleManager(roleStore);
    
        var user = userManager.FindByEmail("[email protected]");
        if (user == null)
        {
            user = new ApplicationUser()
            {
                UserName = "[email protected]",
                Email = "[email protected]"
                Customer = new Customer()
                {
                    CustomerProperty = "Additional Info"
                }
            };
    
            userManager.Create(user, userPassword);
            roleManager.AddUserToRole("Customer");
        }
    
        user = userManager.FindByEmail("[email protected]");
        if (user == null)
        {
            user = new ApplicationUser()
            {
                UserName = "[email protected]",
                Email = "[email protected]",
                Supplier = new Supplier()
                {
                    IBAN = "212323424342234",
                    Relationship = "OK"
                }
            };
    
            userManager.Create(user, userPassword);
            roleManager.AddUserToRole("Supplier");
        }
    }
    

    }