Asp.Net 从 ApplicationUser 派生的核心不同用户模型

Asp.Net Core Different User Models to derive from ApplicationUser

我们刚刚开始学习如何编码,我们正在研究一个 Asp.Net Core 3.1 项目,我知道这是一个简单的问题,但我到处搜索但没有找到解决方案。

我们有 Admin、Client、Mentor User Models,除了来自 ApplicationUser 模型的标准 Email、Pass、First name 和 Last name 之外,它们还具有我们需要的所有额外的用户属性。

所以我们有这样的东西(你可以在模型下面找到):

AdminUserDetails   ---\
ClientUserDetails  ------ : ApplicationUser : IdentityUser
MentorUserDetails  ---/

此刻,身份验证和授权工作正常,但所有用户都是在 EF 中的 AspNetUser Table 中创建的,但我们希望在客户端中创建每个角色 (eg.Client) table 继承了 ApplicationUser 和 IdentityUser 的所有属性。

首先,我发现 this link 从 ApplicationUser 继承模型。这是正确的做法吗?

此外,当用户注册了在表单中选择的角色时,我们希望将该用户分配给相应的 ...UserDetails 模型并在 Guid 中获取身份 ID。

以下是一些示例模型:

public class ApplicationUser : IdentityUser
    {
        [Required]
        [MaxLength(50)]
        public string FirstName { get; set; }

        [Required]
        [MaxLength(50)]
        public string LastName { get; set; }

        [NotMapped]
        public string Name
        {
            get
            {
                return FirstName + " " + LastName;
            }
        }

        [Required]
        [Display(Name = "Phone Number")]
        [DataType(DataType.PhoneNumber)]
        public int? PhoneNumber { get; set; }

        [Display(Name = "Profile Picture")]
        [DataType(DataType.Upload)]
        public string? ProfilePicture { get; set; }
   }
public class AdministratorDetails : ApplicationUser
    {
        [Key]
        [ForeignKey("ApplicationUser")]
        public Guid Id { get; set; }

        //Προσθέτω Foreign key se ola ta details APP USER

        public virtual ApplicationUser ApplicationUser { get; set; }

    }

启动:

public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors();
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));


            services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddRoles<IdentityRole>() //Πρόσθεσα Identity role
                .AddRoleManager<RoleManager<IdentityRole>>()
                .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

            services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
                    options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
                    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
                })
                .AddIdentityServerJwt();


            services.AddControllersWithViews();
            services.AddRazorPages();

            // In production, the React files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/build";
            });

            //SendGrid Confirmation Email Sender
            services.AddTransient<IEmailSender, EmailSender>();
            services.Configure<AuthMessageSenderOptions>(Configuration);

            services.AddTransient<IProfileService, IdentityClaimsProfileService>();
            services.AddScoped<IJobCategoriesRepository, JobCategoryRepository>();
            services.AddScoped<IMentorRepository, MentorRepository>();
            services.AddScoped<IHrDetailsRepository, HrDetailsRepository>();
            services.AddScoped<IClientRepository, ClientRepository>();
            services.AddScoped<IJobListingsRepository, JobListingsRepository>();
            services.AddScoped<IMentorOfferRepository, MentorOfferRepository>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }


            app.UseCors(
                options => options.WithOrigins("https://localhost:5001/")
                                  .AllowAnyHeader()
                                  .AllowAnyMethod()
            );

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSpaStaticFiles();

            app.UseRouting();

            app.UseIdentityServer();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });


            app.UseStaticFiles();

            app.UseIdentityServer();

            CreateRoles(serviceProvider).Wait();
        }


        private async Task CreateRoles(IServiceProvider serviceProvider)
        {
            //adding custom roles
            var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
            var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
            string[] roleNames = { "Admin", "HR", "Mentor", "Client" };
            IdentityResult roleResult;

            foreach (var roleName in roleNames)
            {
                //creating the roles and seeding them to the database
                var roleExist = await RoleManager.RoleExistsAsync(roleName);
                if (!roleExist)
                {
                    roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
                }
            }


            //creating a super user who could maintain the web app

            var powerUser = new ApplicationUser
            {
                UserName = Configuration.GetSection("UserSettings")["UserEmail"],
                Email = Configuration.GetSection("UserSettings")["UserEmail"],

                FirstName = "PowerAdmin",
                LastName = "Admin",
                UserRole = "Admin",
                EmailConfirmed = true
            };


            string UserPassword = Configuration.GetSection("UserSettings")["UserPassword"];
            var _user = await UserManager.FindByEmailAsync(Configuration.GetSection("UserSettings")["UserEmail"]);

            if (_user == null)
            {
                var createPowerUser = await UserManager.CreateAsync(powerUser, UserPassword);
                if (createPowerUser.Succeeded)
                {
                    //here we tie the new user to the "Admin" role 
                    await UserManager.AddToRoleAsync(powerUser, "Admin");

                }

            }

        }

        public class IdentityClaimsProfileService : IProfileService
        {
            private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
            private readonly UserManager<ApplicationUser> _userManager;

            public IdentityClaimsProfileService(UserManager<ApplicationUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory)
            {
                _userManager = userManager;
                _claimsFactory = claimsFactory;
            }

            public async Task GetProfileDataAsync(ProfileDataRequestContext context)
            {
                var sub = context.Subject.GetSubjectId();
                var user = await _userManager.FindByIdAsync(sub);
                var principal = await _claimsFactory.CreateAsync(user);
                var roles = await _userManager.GetRolesAsync(user);
                var claims = principal.Claims.ToList();
                claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
                foreach(string role in roles)
                {
                    claims.Add(new Claim(JwtClaimTypes.Role, role));
                }

                context.IssuedClaims = claims;
            }

            public async Task IsActiveAsync(IsActiveContext context)
            {
                var sub = context.Subject.GetSubjectId();
                var user = await _userManager.FindByIdAsync(sub);
                context.IsActive = user != null;
            }
        }

感谢@Xin Zou 的评论,我终于意识到我必须从我的用户模型中删除 IdentityUser 继承,并且只为每个用户设置外键

 [ForeignKey("ApplicationUserId")]
 public ApplicationUser ApplicationUser { get; set; }
 public string ApplicationUserId { get; set; }

因此创建一个 AspNetUser table 但 一个用户 table (根据它的角色)与 AspNetUser 的外键。这里唯一的问题是用户 table 必须让他们的道具可以为空。