0

I'm working on an MVC project that required an authentication and authorization which will be created using Identity, also my project database includes main entities that should use the system, what is the best practice to deal with that situation.

  1. Should I customize the identity ApplicationUser to my requirements.
  2. Create my own tables (models) and integrate them with identity tables and I don't know how to do that. or any other solutions.
Zaker typoir
  • 99
  • 1
  • 2
  • 8

1 Answers1

1

You should of course customize the Identity entities, as that is the whole reason for the creation of Identity: to allow greater extensibility. To have different types of "users", you should inherit from ApplicationUser; importantly, not from IdentityUser directly. This will assure that the core Identity relationships (roles, claims, logins, etc.) are all tied to a single "user" table, while you can then either extend that table or create other tables to hold additional user data.

public class ApplicationUser : IdentityUser

public class Student : ApplicationUser

public class Instructor : ApplicationUser

By default, this inheritance will be implemented by TPH (Table Per Hierarchy), also known as STI (Single Table Inheritance). What that means is that all properties from all derived classes will be represented by columns on a single database table. A Discriminator column will also be added, which will hold the name of the actual class that was saved, i.e. "ApplicationUser", "Student", or "Instructor". EF will use this column when building object graphs from your queries to instantiate the right "user" type.

There's pros and cons with this approach. Since everything exists in a single table, queries are simple and quick. However, this approach necessitates that all properties on each derived class must be nullable at the database level. The obvious reason why is because if Instructor has a required column, you would not be able to save Student, since Student would not have the property to fulfill that requirement. You can still enforce that properties be required at the view level, using view models. The actual column in the database must be nullable, though.

An alternative approach is to use what's called TPT (Table Per Type). In this inheritance strategy, a table will be created for the base class (ApplicationUser) with all common properties. Then, a table will be created for each discreet derived class, with just the properties that exist on that class. A foreign key will be added to the table for the base class, which EF will then use to join the common data from that table to the specific data on your derived class' table. This approach allows you to enforce NOT NULL at the database level, but it of course requires a join to bring in all the data, which can slow down your queries.

To implement TPT, you need simply to add the [Table] annotation to your derived class:

[Table("Students")]
public class Student : ApplicationUser

[Table("Instructors")]
public class Instructor : ApplicationUser

One final thing of note is how you'll need to utilize UserManager. If you scaffolded your AccountController, you'll notice that it sets up a UserManager controller property, which is then utilized to create users, lookup users, change passwords, etc. This is actually an instance of UserManager<ApplicationUser>, as it's a generic type. If you need to specifically work with Student or Instructor, you'll need to instantiate UserManager<Student> and UserManager<Instructor>, respectively. You can't use an instance of UserManager<ApplicationUser> as it will upcast your derived type to ApplicationUser. For example:

var student = new Student { ... };
await UserManager.CreateAsync(student);

Would actually result in ApplicationUser being saved to the database. The student-specific data will be discarded and the Discriminator column's value would be "ApplicationUser".

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444