如何使用 Identity ASP.NET Core 通过代码优先迁移为用户和角色播种

Posted

技术标签:

【中文标题】如何使用 Identity ASP.NET Core 通过代码优先迁移为用户和角色播种【英文标题】:How to Seed Users and Roles with Code First Migration using Identity ASP.NET Core 【发布时间】:2016-03-24 10:52:43 【问题描述】:

我创建了一个新的干净的 asp.net 5 项目(rc1-final)。使用身份验证我只有 ApplicationDbContext.cs 和以下代码:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>

    protected override void OnModelCreating(ModelBuilder builder)
    
        // On event model creating
        base.OnModelCreating(builder);
    

请注意 ApplicationDbContext 使用 IdentityDbContext 而不是 DbContext。

有任何 IdentityConfig.cs。如果不存在,我需要将经典的受保护覆盖​​无效种子放在哪里来创建角色和用户?

【问题讨论】:

【参考方案1】:

这是not yet implemented。作为一种解决方法,只需编写自己的类来检查数据库中是否存在实体,如果它们不存在则添加它们,然后从 Startup.cs 调用此类。

【讨论】:

根据微软,The seeding code should not be part of the normal app execution as this can cause concurrency issues when multiple instances are running and would also require the app having permission to modify the database schema.docs.microsoft.com/en-us/ef/core/modeling/… @HamzaKhanzada 我首先检查 env 是否是开发环境,然后是种子,然后我创建一个单独的程序,将其种子用于生产,应该手动运行一次。【参考方案2】:

我这样做的方法是在模型命名空间中创建一个类。

public class SampleData

    public static void Initialize(IServiceProvider serviceProvider)
    
        var context = serviceProvider.GetService<ApplicationDbContext>();

        string[] roles = new string[]  "Owner", "Administrator", "Manager", "Editor", "Buyer", "Business", "Seller", "Subscriber" ;

        foreach (string role in roles)
        
            var roleStore = new RoleStore<IdentityRole>(context);

            if (!context.Roles.Any(r => r.Name == role))
            
                roleStore.CreateAsync(new IdentityRole(role));
            
        


        var user = new ApplicationUser
        
            FirstName = "XXXX",
            LastName = "XXXX",
            Email = "xxxx@example.com",
            NormalizedEmail = "XXXX@EXAMPLE.COM",
            UserName = "Owner",
            NormalizedUserName = "OWNER",
            PhoneNumber = "+111111111111",
            EmailConfirmed = true,
            PhoneNumberConfirmed = true,
            SecurityStamp = Guid.NewGuid().ToString("D")
        ;


        if (!context.Users.Any(u => u.UserName == user.UserName))
        
            var password = new PasswordHasher<ApplicationUser>();
            var hashed = password.HashPassword(user,"secret");
            user.PasswordHash = hashed;

            var userStore = new UserStore<ApplicationUser>(context);
            var result = userStore.CreateAsync(user);

        

        AssignRoles(serviceProvider, user.Email, roles);

        context.SaveChangesAsync();
    

    public static async Task<IdentityResult> AssignRoles(IServiceProvider services, string email, string[] roles)
    
        UserManager<ApplicationUser> _userManager = services.GetService<UserManager<ApplicationUser>>();
        ApplicationUser user = await _userManager.FindByEmailAsync(email);
        var result = await _userManager.AddToRolesAsync(user, roles);

        return result;
    


在启动时运行此代码。在配置方法末尾的 Startup.cs 中,在路由配置之后添加以下代码,如 Stafford Williams 之前所说。

SampleData.Initialize(app.ApplicationServices);

【讨论】:

我正在寻找一种方法让用户拥有固定的 ID。当您移交一些 ApplicationUser 实例进行创建时,UserManager 会分配一个新的 Id。您的方法允许我将 Id 设置为某个固定值 - 所以感谢您的努力:) 这段代码中很少有异步问题(即 userStore.CreateAsync 不能保证在调用 .AddToRolesAsync 之前完成)但概念是***的 这给了我一些关于为 ApplicationDbContext 或其他东西使用范围服务的错误...... 出现错误提示非泛型方法“IServiceProvider.GetService(Type)”不能与类型参数一起使用 @HamzaKhanzada GetService 方法有两种,使用来自 Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions 的public static T GetService&lt;T&gt;【参考方案3】:

以下行在 AspNetRoles 表中创建条目,但不填充 NormalizedName 列。

替换为以下内容以填充此列:

RoleManager<IdentityRole> roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
roleManager.CreateAsync(new IdentityRole(role));

【讨论】:

【参考方案4】:

在撰写本文时,还没有用于为数据库播种的插件,但您可以创建一个类并将其添加到您的容器中,以便在应用启动时执行相同的操作,这就是我的做法它,首先创建一个类:

public class YourDbContextSeedData

    private YourDbContext _context;

    public YourDbContextSeedData(YourDbContext context)
    
        _context = context;
    

    public async void SeedAdminUser()
    
        var user = new ApplicationUser
        
            UserName = "Email@email.com",
            NormalizedUserName = "email@email.com",
            Email = "Email@email.com",
            NormalizedEmail = "email@email.com",
            EmailConfirmed = true,
            LockoutEnabled = false,
            SecurityStamp = Guid.NewGuid().ToString()
        ;

        var roleStore = new RoleStore<IdentityRole>(_context);

        if (!_context.Roles.Any(r => r.Name == "admin"))
        
            await roleStore.CreateAsync(new IdentityRole  Name = "admin", NormalizedName = "admin" );
        

        if (!_context.Users.Any(u => u.UserName == user.UserName))
        
            var password = new PasswordHasher<ApplicationUser>();
            var hashed = password.HashPassword(user, "password");
            user.PasswordHash = hashed;
            var userStore = new UserStore<ApplicationUser>(_context);
            await userStore.CreateAsync(user);
            await userStore.AddToRoleAsync(user, "admin");
        

        await _context.SaveChangesAsync();
    

Startup.cs 类的ConfigureServices 方法中注册类型:

services.AddTransient<YourDbContextSeedData>();

接下来将YourDbContextSeedData 类传递给Startup.cs 类的Configure 方法并使用它:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, YourDbContextSeedData seeder)

  seeder.SeedAdminUser();

【讨论】:

在我的核心 2 应用程序中尝试创建管理员级别帐户浪费了很多时间之后,您的帖子的稍微修改版本终于为我完成了,而当我调用 AddToRoleAsync 方法时,所有其他方法都失败了。非常感谢! 这很有帮助,谢谢@Hamid。我会向其他人建议您实际上是从 Program.Main() 调用初始化程序 - 请参阅 this answer 以获得解释。在我将初始化移出启动之前,我的应用程序不会持续加载。根据this post,您可能需要考虑使用UserManagerRoleManager 进行验证。 请注意,调用:await userStore.AddToRoleAsync(user, "admin");“admin”是角色的标准化值,因此如果您的不同,请确保它反映了标准化角色中指定的值。 让它在 EF6 中工作。我的问题是规范化的用户名和电子邮件必须大写!【参考方案5】:

在 Models 命名空间中添加以下类。它适用于添加多个用户和角色,并且还将为现有用户添加角色(例如 facbook 登录)。从 startup.cs 中这样称呼它app.SeedUsersAndRoles();

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;

namespace MyApplication.Models

    public static class DataSeeder
    
        public static async void SeedUsersAndRoles(this IApplicationBuilder app)
        
            var context = app.ApplicationServices.GetService<ApplicationDbContext>();
            UserWithRoles[] usersWithRoles = 
                new UserWithRoles("Admin", new string[]  "Administrator" , "Distributor" ,"somepassword"),//user and optional roles and password you want to seed 
                new UserWithRoles("PlainUser"),
                new UserWithRoles("Jojo",new string[]"Distributor" ) //seed roles to existing users (e.g. facebook login).
            ;

            foreach (var userWithRoles in usersWithRoles)
            
                foreach (string role in userWithRoles.Roles)
                    if (!context.Roles.Any(r => r.Name == role))
                    
                        var roleStore = new RoleStore<IdentityRole>(context);
                        await roleStore.CreateAsync(new IdentityRole(role));
                    
                var ExistingUser = context.Users.FirstOrDefault(p => p.NormalizedUserName == userWithRoles.User.NormalizedUserName);
                if (ExistingUser == null) //the following syntax: !context.Users.FirstOrDefault(p => p.NormalizedUserName == userWithRoles.User.NormalizedUserName)) 
                                            //provokes execption:(ExecuteReader requires an open and available Connection.) 
                    await new UserStore<ApplicationUser>(context).CreateAsync(userWithRoles.User);
                await app.AssignRoles(userWithRoles); //assign also to existing users.
            

            context.SaveChangesAsync();
        

        public static async Task<IdentityResult> AssignRoles(this IApplicationBuilder app, UserWithRoles uWR)
        
            UserManager<ApplicationUser> _userManager = app.ApplicationServices.GetService<UserManager<ApplicationUser>>();
            ApplicationUser user = await _userManager.FindByNameAsync(uWR.User.NormalizedUserName);
            var result = await _userManager.AddToRolesAsync(user, uWR.Roles);
            return result;
        
    
    public class UserWithRoles
    
        private ApplicationUser user;
        public ApplicationUser User  get  return user;  
        public string[] Roles  get; set; 
        public UserWithRoles(string name, string[] roles = null, string password = "secret")
        
            if (roles != null)
                Roles = roles;
            else
                Roles = new string[]  ;
            user = new ApplicationUser
            
                Email = name + "@gmail.com", NormalizedEmail = name.ToUpper() + "@GMAIL.COM",
                UserName = name, NormalizedUserName = name.ToUpper(),
                PhoneNumber = "+1312341234",
                EmailConfirmed = true,
                PhoneNumberConfirmed = true,
                SecurityStamp = Guid.NewGuid().ToString("D"),
            ;
            user.PasswordHash = new PasswordHasher<ApplicationUser>().HashPassword(user, password);
        
    

【讨论】:

【参考方案6】:

所以这是基于 Muhammad Abdullah 回答的解决方案。包括一些代码改进,提高了代码的可读性并使其与 .net core 2 一起使用。

 public class Seed
    
        public static async Task Initialize(IServiceProvider serviceProvider, IConfiguration configuration)
        
            var usrName = configuration.GetSection("Admin").GetSection("UserName").Value;
            var email = configuration.GetSection("Admin").GetSection("Email").Value;
            var pass = configuration.GetSection("Admin").GetSection("Pass").Value;
            var roles = new string[4]  OWNER, ADMIN, SENIOR, USER ;

            if(await CreateUser(serviceProvider, email, usrName, pass, roles))
            
                await AddToRoles(serviceProvider, email, roles);
            
        

        private static async Task<bool> CreateUser(IServiceProvider serviceProvider, string email, string usrName, string pass, string[] roles)
        
            var res = false;

            using (var scope = serviceProvider.CreateScope())
            
                var context = scope.ServiceProvider.GetService<BaseContext>();

                if (!context.ApplicationUsers.Any(u => u.NormalizedUserName == usrName.ToUpper()))
                
                    var roleStore = scope.ServiceProvider.GetService<RoleManager<IdentityRole>>();

                    foreach (string role in roles)
                    
                        if (!context.Roles.Any(r => r.Name == role))
                        
                            await roleStore.CreateAsync(new IdentityRole(role)).ConfigureAwait(false);
                        
                    

                    var user = new ApplicationUser
                    
                        UserName = usrName,
                        Email = email,
                        EmailConfirmed = true,
                        NormalizedEmail = email.ToUpper(),
                        NormalizedUserName = usrName.ToUpper(),
                        PhoneNumber = null,
                        PhoneNumberConfirmed = true,
                        SecurityStamp = Guid.NewGuid().ToString()
                    ;

                    var password = new PasswordHasher<ApplicationUser>();
                    user.PasswordHash = password.HashPassword(user, pass); ;

                    var userStore = new UserStore<ApplicationUser>(context);
                    res = (await userStore.CreateAsync(user).ConfigureAwait(false)).Succeeded;
                

                return res;
            
        

        private static async Task AddToRoles(IServiceProvider serviceProvider, string email, string[] roles)
        
            using (var scope = serviceProvider.CreateScope())
            
                var userManager = scope.ServiceProvider.GetService<UserManager<ApplicationUser>>();
                var usr = await userManager.FindByEmailAsync(email).ConfigureAwait(false);
                await userManager.AddToRolesAsync(usr, roles).ConfigureAwait(false);
                       
        
    

【讨论】:

【参考方案7】:

如果您有异步问题,请尝试以下代码:

    protected override void Seed(ApplicationDbContext context)
    
        //  This method will be called after migrating to the latest version.

        string[] roles = new string[]  "Admin", "User" ;
        foreach (string role in roles)
        
            if (!context.Roles.Any(r => r.Name == role))
            
                context.Roles.Add(new IdentityRole(role));
            
        

        //create user UserName:Owner Role:Admin
        if (!context.Users.Any(u => u.UserName == "Owner"))
        
            var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
            var user = new ApplicationUser
            
                FirstName = "XXXX",
                LastName = "XXXX",
                Email = "xxxx@example.com",
                UserName = "Owner",
                PhoneNumber = "+111111111111",
                EmailConfirmed = true,
                PhoneNumberConfirmed = true,
                SecurityStamp = Guid.NewGuid().ToString("D"),
                PasswordHash = userManager.PasswordHasher.HashPassword("secret"),
                LockoutEnabled = true,
            ;
            userManager.Create(user);
            userManager.AddToRole(user.Id, "Admin");
                    

        context.SaveChanges();
    

【讨论】:

【参考方案8】:

似乎这个线程已经很老了,但它仍然适用于想要在实体框架核心中播种他们的身份表数据的人。

你可以简单地试试下面的。

modelBuilder.Entity<IdentityUser>().HasData(
               new IdentityUser  Id= "-1", UserName="sagark",PasswordHash="sagark", Email="emailid goes here" 
               );

【讨论】:

不推荐这种方法。如果没有正确的哈希密码,您将如何登录?此外,如果没有预设 ID,您将永远无法将用户添加到任何角色或在播种时建立任何关系。【参考方案9】:

您可以在 IdentityDbContext.cs 文件中的 OnModelCreating() 方法中为用户和角色播种,如下所示。请注意,必须预定义密钥以避免在每次执行此方法时播种新用户和角色。

protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        base.OnModelCreating(modelBuilder);

        //Seeding a  'Administrator' role to AspNetRoles table
        modelBuilder.Entity<IdentityRole>().HasData(new IdentityRole Id = "2c5e174e-3b0e-446f-86af-483d56fd7210", Name = "Administrator", NormalizedName = "ADMINISTRATOR".ToUpper() );


        //a hasher to hash the password before seeding the user to the db
        var hasher = new PasswordHasher<IdentityUser>();


        //Seeding the User to AspNetUsers table
        modelBuilder.Entity<IdentityUser>().HasData(
            new IdentityUser
            
                Id = "8e445865-a24d-4543-a6c6-9443d048cdb9", // primary key
                UserName = "myuser",
                NormalizedUserName = "MYUSER",
                PasswordHash = hasher.HashPassword(null, "Pa$$w0rd")
            
        );


        //Seeding the relation between our user and role to AspNetUserRoles table
        modelBuilder.Entity<IdentityUserRole<string>>().HasData(
            new IdentityUserRole<string>
            
                RoleId = "2c5e174e-3b0e-446f-86af-483d56fd7210", 
                UserId = "8e445865-a24d-4543-a6c6-9443d048cdb9"
            
        );
        

    

【讨论】:

这对我有用,而顶部接受的却没有。谢谢 这也是 .NET 5.0 的正确答案。 Micro$oft 不推荐这个 @JonathanDaniel 你能发布一个链接来验证你所说的吗?我正在研究这个并发现了这个,我假设如果你满足某些因素,它会显示出最好的方法。 docs.microsoft.com/en-us/ef/core/modeling/data-seeding @HeribertoLugo 我从字面上引用了 microsoft 所写的内容:“如果您的方案包含以下任何内容,建议使用上一节中描述的自定义初始化逻辑:...需要调用外部的数据API,例如 ASP.NET Core Identity 角色和用户创建”【参考方案10】:

在aspnetcore 中有IHostedService 的概念。这使得运行异步后台Task成为可能。

@hamid-mosalla 的解决方案可以做成async 并从IHostedService 实现中调用。

种子类实现可能类似于

public class IdentityDataSeeder

    private readonly UserManager<ApplicationUser> _userManager;
    private readonly RoleManager<IdentityRole> _roleManager;

    public IdentityDataSeeder(
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager)
    
        _userManager = userManager;
        _roleManager = roleManager;
    

    public async Task SeedAsync()
    
        var superAdminRole = new IdentityRole
        
            Id = "cac43a6e-f7bb-4448-baaf-1add431ccbbf",
            Name = "SuperAdmin",
            NormalizedName = "SUPERADMIN"
        ;
        await CreateRoleAsync(superAdminRole);

        var superAdminUserPassword = "P@ssword1";
        var superAdminUser = new ApplicationUser
        
            Id = "b8633e2d-a33b-45e6-8329-1958b3252bbd",
            UserName = "admin@example.nl",
            NormalizedUserName = "ADMIN@EXAMPLE.NL",
            Email = "admin@example.nl",
            NormalizedEmail = "ADMIN@EXAMPLE.NL",
            EmailConfirmed = true,
        ;
        await CreateUserAsync(superAdminUser, superAdminUserPassword);

        var superAdminInRole = await _userManager.IsInRoleAsync(superAdminUser, superAdminRole.Name);
        if (!superAdminInRole)
            await _userManager.AddToRoleAsync(superAdminUser, superAdminRole.Name);
    

    private async Task CreateRoleAsync(IdentityRole role)
    
        var exits = await _roleManager.RoleExistsAsync(role.Name);
        if (!exits)
            await _roleManager.CreateAsync(role);
    

    private async Task CreateUserAsync(ApplicationUser user, string password)
    
        var exists = await _userManager.FindByEmailAsync(user.Email);
        if (exists == null)
            await _userManager.CreateAsync(user, password);
    

这可以从IHostedService 调用:

public class SetupIdentityDataSeeder : IHostedService

    private readonly IServiceProvider _serviceProvider;
    public SetupIdentityDataSeeder(IServiceProvider serviceProvider)
    
        _serviceProvider = serviceProvider;
    

    public async Task StartAsync(CancellationToken cancellationToken)
    
        using (var scope = _serviceProvider.CreateScope())
        
            var seeder = scope.ServiceProvider.GetRequiredService<IdentityDataSeeder>();

            await seeder.SeedAsync();
        
    

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;

Startup 看起来像:

public void ConfigureServices(IServiceCollection services)

    //...

    services.AddHostedService<SetupIdentityDataSeeder>();

【讨论】:

据我了解,IHostedService 在后台持续运行,直到应用程序进程停止。如果是这种情况,那么它不适合执行一次性任务(例如为数据库播种)。 @pnavk 文档状态:The hosted service is activated once at app startup and gracefully shut down at app shutdown. 见MS docs。 是的,这正是我的观点。托管服务用于需要在整个应用程序运行期间重复运行的东西。如果您想在数据库中植入某些内容(只需执行一次的任务),您可以在应用程序启动期间执行一次(支持异步),如下所示:***.com/a/52709902/2754727 @pnavk 它需要一直重复运行是不正确的。 IHostedService 用于后台任务和计划作业。在这里,它用于将代码作为后台任务运行。要重复运行它,您需要使用Timer。由于这里没有这样做,这将在应用程序启动时运行一次。我只想将任务卸载到后台,并且为此使用了内置机制。【参考方案11】:

我的方式:

    在模型文件夹中创建类

    public static class ModelBuilderExtensions
      
    
         public static void Seed(this ModelBuilder builder)
         
    
        // Seed Roles
    
        List<IdentityRole> roles = new List<IdentityRole>()
        
            new IdentityRole  Name = "Admin", NormalizedName = "ADMIN" ,
            new IdentityRole  Name = "User", NormalizedName = "USER" 
        ;
    
        builder.Entity<IdentityRole>().HasData(roles);
    
        // -----------------------------------------------------------------------------
    
        // Seed Users
    
        var passwordHasher = new PasswordHasher<ApplicationUser>();
    
        List<ApplicationUser> users = new List<ApplicationUser>()
        
             // imporant: don't forget NormalizedUserName, NormalizedEmail 
                     new ApplicationUser 
                        UserName = "user2@hotmail.com",
                        NormalizedUserName = "USER2@HOTMAIL.COM",
                        Email = "user2@hotmail.com",
                        NormalizedEmail = "USER2@HOTMAIL.COM",
                    ,
    
                    new ApplicationUser 
                        UserName = "user3@hotmail.com",
                        NormalizedUserName = "USER3@HOTMAIL.COM",
                        Email = "user3@hotmail.com",
                        NormalizedEmail = "USER3@HOTMAIL.COM",
                    ,
        ;
    
    
        builder.Entity<ApplicationUser>().HasData(users);
    
        ///----------------------------------------------------
    
        // Seed UserRoles
    
    
        List<IdentityUserRole<string>> userRoles = new List<IdentityUserRole<string>>();
    
          // Add Password For All Users
    
            users[0].PasswordHash = passwordHasher.HashPassword(users[0], "User.123");
            users[1].PasswordHash = passwordHasher.HashPassword(users[1], "User.155");
    
             userRoles.Add(new IdentityUserRole<string>  UserId = users[0].Id, RoleId = 
             roles.First(q => q.Name == "User").Id );
    
             userRoles.Add(new IdentityUserRole<string>  UserId = users[1].Id, RoleId = 
             roles.First(q => q.Name == "Admin").Id );
    
    
        builder.Entity<IdentityUserRole<string>>().HasData(userRoles);
    
    
    

    在 DBContext 中

    public class AppDbContext : IdentityDbContext<ApplicationUser>
     
    
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    
    
    
    protected override void OnModelCreating(ModelBuilder builder)
    
        // Use seed method here
        builder.Seed();
        base.OnModelCreating(builder);
    
    

【讨论】:

此解决方案将重新创建角色,因为未提供 ID。然后它将更新并发标记,除非也提供。几乎和@hamza 的回答一样

以上是关于如何使用 Identity ASP.NET Core 通过代码优先迁移为用户和角色播种的主要内容,如果未能解决你的问题,请参考以下文章

如何更改 asp.net identity 3 (vnext) 使用的表名?

ASP.NET 如何在应用程序请求模块中访问 User.Identity.IsAuthenticated?

如何在没有实体框架的情况下使用 ASP.NET Identity 3.0 [关闭]

ASP.NET Identity 重置密码

如何更新 ASP.NET Identity 中的声明?

如何使用具有最小 API .Net 6 的 Asp.net core Identity 运行迁移(第一次迁移)