简体   繁体   中英

How to conditionally map multiple models to one table (AspNetUsers) in ASP.NET Core Identity?

I'm creating a learning assessment web application in ASP.NET Core MVC. I plan to have two separate databases: one for the users using Identity Framework and another for my data using Dapper. However, I have models having relationships with my users( Instructor , Student ). Here is the Entity-Relationship diagram I drew showing the relations:

实体关系图 For instance, I have to include a Course navigation property for each Student instance. Likewise, TestInstance contains navigation properties for both Instructor and Student , making a clear distinction between the two user classes. I also need another user class called Administrator :

User Class Responsibilities
Student Attends tests
Instructor Creates and schedules tests
Administrator Manages instructor and student accounts and courses. Has all the privileges of an instructor

I know custom properties can be created in Identity by deriving from IdentityUser but I need to maintain three different user classes and Identity provides just one table called AspNetUsers . I don't want to directly use tables from Identity in my domain model. In fact, it would be great if Identity can have its own separate database. The administrator will be the one who would create and manage instructor and student accounts.

My question is:

Is there any way that I can create three models/entities Student , Instructor and Adminstrator as part of my domain model and then have them all use the IDs in the Identity's AspNetUsers table? Their role information can be stored as a claim or in the AspNetUserRoles table provided by Identity.

I can then use Identity to add policies to implement access control.

I hope it is not a design flaw. I can still reconsider the design if so as my project is in the initial stage.

You can use TPT With EF Core 5 for example, this project Integrates TPT with ASP.NET Core Identity.

https://github.com/ArminShoeibi/SchoolManagementSystem

 public class ApplicationUser : IdentityUser<Guid>
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string NationalCode { get; set; }
        public DateTime BirthDate { get; set; }
        public Gender Gender { get; set; }

    }

 public class Student : ApplicationUser
    {
        public string FatherName { get; set; }
        public string PlaceOfBirth { get; set; }
        public string Address { get; set; }
        public string HomePhoneNumber { get; set; }
    }

 public class Teacher : ApplicationUser
    {
        public string FieldOfStudy { get; set; }
        public AcademicDegree AcademicDegree{ get; set; }
        public int YearsOfExperience { get; set; }
    }


  public class SchoolMgmtContext : IdentityDbContext<ApplicationUser,ApplicationRole,Guid>
    {


        public SchoolMgmtContext(DbContextOptions<SchoolMgmtContext> dbContextOptions)
            :base(dbContextOptions)
        {

        }

        public DbSet<Teacher> Teachers { get; set; }
        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            builder.Entity<ApplicationUser>().ToTable("Users");
            builder.Entity<ApplicationRole>().ToTable("Roles");

            builder.Entity<Student>().ToTable("Students");
            builder.Entity<Teacher>().ToTable("Teachers");
         

        }
    }

and the result is: 在此处输入图像描述

Can an instructor also be an administrator? How could such multi-role authorizations be handled?

Perhaps this is not relevant in this case, but I like to think about such quality/architectural concerns before I get confronted with a change request from the users afterwards due to "new insights".

Personally, I would consider creating a kind of Role table, containing the three roles. Using a separate UserRole table, you could create a many-to-many relation between the users and their role(s). So instead of using inheritance/derivation, I would use a kind of composition here.

I think your current logic using the AspNetUsers will remain intact. Perhaps you can use Identity to add policies to implement access control based on these two additional role-related tables?

EDIT

You could also go a step further and create an additional Right table, containing the separate user rights, like attending tests, creating tests, scheduling tests, managing accounts, etc. You could create a separate RoleRight table to assign those rights to the desired roles.

This would make your roles more dymamic and perhaps even configurable by users. (In the latter case only if the user has rights to create and manage roles, of course.)

And in your code, you could then check if the user has specific rights assigned (by its roles). For example, in the code that is responsible for creating tests, you might actually want to check if the user has the right to create tests instead of explicitly checking if the user is an instructor.

Anyway, that's just my 50 cents to this issue. Hope it helps.

I think that you can solve this problem quite easily by creating a users table that is a manifest of all the system users that might access the system (students, instructors, admins) containing shared properties such as Ids, names, email adresses, etc. (be sure to salt any passwords you store!)

Then you have two options for moving forward: you can go with the multi-table approach you have here, and just have each 'role table' be a join to the users table via userId and store unique properties to that role: In you example, a student table would probably be just a course Id and a User Id. This lets you customize each role with additional properties as you discover them.

Alternately, you could combine the roles into a single table, but unless you do some work to abstract unique properties, you will probably end up with a really wide table that contains a lot of blank values for properties that don't apply to every role. Don't do this. Its easy at first, but as your app grows, this table will turn into a hot mess.

Just adding a 'users' table should allow you to forge forward with minimal changes to your existing schema.

One other note: You have a 1:1 relationship between students and courses. I could be wrong, but you probably want this to be a one to many, since most students will take multiple courses at a time. Your mileage may vary.

Because of the tight integration

I would like to suggest that the existence of a tight integration is the core issue. If your design does not create a tight integration, then the problem disappears. You determine how tightly integrated your design is.

Separation Of Concerns

There are good arguments and real-world use cases for maintaining a separate database for Identity and one for the application itself. I recently developed and deployed such an application and I am very happy with the results.

The first reason being "What happens when another application is added to the mix?". Out in the wild, identity services tend to be isolated: Azure AD, Google Cloud Identity, AWS Identity and Access Management, etc. Aside from the inherent security benefits, an Identity Service will support all of your applications, present and future, and should not be tightly coupled within one specific application.

Your application is web-based: what happens when users demand a mobile application? If Identity is only accessible through the web application, what now? The web app probably uses session-based authentication, so you will need to rewrite all of that in order to support mobile applications. It won't be pretty.

On the other hand, let's say you had separated the services. Furthermore, because your design called for the Identity service to be platform-agnostic, you implemented JWT (JSON Web Tokens) support for authentication. In this scenario, authentication for the mobile application will work exactly the same , with no changes

Let's take this scenario one step further: users of your application are tired of hitting "refresh" to see if their test scores are available (pull). They are demanding to be notified when test scores are ready (push).

The implementation of mobile push notifications has nothing to do with the core functionality of the application; it is a user/platform-centric feature. If you are not convinced this is true, consider 2FA (Two Factor Authentication) utilizing a mobile device as the second factor.

Identity is never a good place to experiment, and arguably the last place you want to make a mistake. I believe everything you add or tweak in an Identity solution can potentially increase the attack vector. This is the textbook example of KISS (Keep It Simple Stupid).

Connecting a User to Identity

User table refers to the Student and Teacher tables; Identity table refers to AspNetUsers .

As you stated, had the tables been in a single database they would be connected using the standard "out-of-the-box" foreign key reference that we all know and love.

Here's the fun part: we get to roll our own: Fundamentally a foreign key is very basic concept, here's an id. use it to reference something. That is the only requirement for this implementation of a foreign key reference.

Keep in mind: a User is not the same as an Identity . It is common for systems to contain data for User s who will never log into their systems. Also, a site administrator will most likely not be a Teacher or Student .

For the standard use-case, my suggestion is to create the User entity first, followed by the Identity . Then update the optional column in the User table with the id of the Identity you just created.

You want to add a role or UserType claim (Instructor, Student) for each Identity so you know which of the User tables you will find the User .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM