Creating user inheritance from asp.net base user

I have a problem where I would like to create N, two in the example, custom objects (like client and provider) that are all inherent in the asp.net IdentityUser object. These objects have very different additional data besides the data from the IdentityUser. I would like to use the IdentityUser as it gives me a flexible way of authentication and authorization.

This example was very limited, but should provide sufficient information that you cannot create a specific user (eg Customer). I seem to need to use the UserManager object as it also takes care of generating, for example, a password hash and additional security information.

I got the following error:

{"Binding an object of type" Provider "failed because another object of the same type already has the same primary key value. This can happen when using the Attach method or setting the state of the object to Unchanged, or Modified if some objects on the chart have conflicting key values. This may be because some of the objects are new and have not yet received database key values. In this case, use the Add or Added method to track the chart, and then to set the state of non-new objects to "No Changes" or "Modified" as needed. " }

Classes inherent in 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 that throws an 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("customer@customer.com");
        if (user == null)
        {
            user = new User()
            {
                UserName = "customer@customer.com",
                Email = "customer@customer.com"
            };

            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("supplier@supplier.com");
        if (user == null)
        {
            user = new User()
            {
                UserName = "supplier@supplier.com",
                Email = "supplier@supplier.com"
            };

            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 problems:

  • I would always like to have one type of user (like Vendor Client) associated with the IdentityUser. I though about using the interface, but it doesn't work.
  • If I also add a virtual reference to IdentityUser by user type, I get "Unable to determine the main end of the association between types" ApplicaitonUser "and" Provider. "The main end of this association must be explicitly configured using loose API or data annotations.

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; }*
 }

      

** IdentityUser class (which works) **

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

      

** IdentityUser class (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("customer@customer.com");
    if (user == null)
    {
        user = new ApplicationUser()
        {
            UserName = "customer@customer.com",
            Email = "customer@customer.com"
            Customer = new Customer()
            {
                CustomerProperty = "Additional Info"
            }
        };

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

    user = userManager.FindByEmail("supplier@supplier.com");
    if (user == null)
    {
        user = new ApplicationUser()
        {
            UserName = "supplier@supplier.com",
            Email = "supplier@supplier.com",
            Supplier = new Supplier()
            {
                IBAN = "212323424342234",
                Relationship = "OK"
            }
        };

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

      

}

+3


source to share


2 answers


Like others have suggested, I think this is a design issue. There are several alternative approaches such as:

  • use roles to define "custom type" (user can be supplier and customer).
  • make objects Supplier

    and Customer

    relation not a user extension

eg:.

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("customer@customer.com");
        if (user == null)
        {
            user = new ApplicationUser()
            {
                UserName = "customer@customer.com",
                Email = "customer@customer.com"
                Customer = new Customer()
                {
                    CustomerProperty = "Additional Info"
                }
            };

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

        user = userManager.FindByEmail("supplier@supplier.com");
        if (user == null)
        {
            user = new ApplicationUser()
            {
                UserName = "supplier@supplier.com",
                Email = "supplier@supplier.com",
                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 and paste" example and should just give you an idea of ​​a different approach.

+4


source


I just solved a similar problem. I created a navigation property of an abstract type DomainUser in my AppUser (which 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 on all specific 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 the DbContext, in the OnModelCreating method, I configured the Entity Framework to store all the objects inherited from DomainUser in separate tables (it was called a Table for a specific type ). And set up a 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 the "DomainUser_Id" column to the AspNetUsers table and now I can access the IdentityUser navigation properties for each domain user and the DomainUser navigation properties in the AppUser.

0


source







All Articles