Как внедрить userManager внутри конструктора DbContext?

Я использую IdentityDbContext, и у меня есть класс SeedData, который я использую внутри метода OnModelCreating для заполнения базы данных с использованием начальных миграций следующим образом:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    //
    //
    SeedData seedData = new(_userManager);
    seedData.SeedWebsiteAdmins(modelBuilder.Entity<WebsiteAdmin>());
    seedData.SeedCompanyAdmins(modelBuilder.Entity<CompanyAdmin>());
    //
    //
}

Этому классу SeedData требуется параметр типа UserManager внутри его конструктора, поскольку ему необходимо создавать новых пользователей следующим образом:

public async void SeedWebsiteAdmins(EntityTypeBuilder<WebsiteAdmin> entity)
{
    var superAdminLogin = new ApplicationUser()
    {
        Name = "Name",
        Email = "Email",
        UserType = UserType.WebsiteAdmin
    };

    var result = await _userManager.CreateAsync(superAdminLogin, "{password}");

    if (result.Succeeded)
    {
        await _userManager.AddToRoleAsync(superAdminLogin, RoleNames.WebsiteAdminRole);
    }

    var superAdmin = new WebsiteAdmin()
    {
        Id = SuperAdminId,
        WebsiteAdminLoginId = superAdminLogin.Id,
        MaxServiceDiscount = 100,

    };

    entity.HasData(superAdmin);
}

Я попытался внедрить UserManager в конструктор DbContext, но это вызвало следующую ошибку:

System.InvalidOperationException: невозможно разрешить службу для типа «Microsoft.EntityFrameworkCore.DbContext» при попытке активировать «Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore`3 [RepositoryProject.DomainAuth.ApplicationUser,Microsoft.EntityFrameworkCore.DbContext,System.Guid] '

Как я могу внедрить UserManager в конструктор DbContext, не вызывая ошибок?

Или есть способ создать идентификационного пользователя без использования UserManager, чтобы я мог удалить зависимость из класса SeedData и, следовательно, она мне не понадобится внутри DbContext?

На самом деле, размещение кода логики заполнения внутри OnModelCreating — это яма падения. В производственных средах, если приложение масштабируется на несколько экземпляров, может возникнуть проблема состояния гонки. Усилия по решению этой проблемы не малы. Вашей текущей проблемы и потенциальных проблем можно избежать, создав еще одно новое консольное приложение, взяв на себя ответственность только за отправку данных.

Nguyễn Kỳ Dương Trường 19.05.2024 11:03

Я постараюсь решить эти проблемы, когда захочу расширить свое приложение. Но на данный момент меня это не беспокоит. @NguyễnKỳDươngTrường

siba36 19.05.2024 11:13

Если вы хотите использовать миграцию для заполнения данных через модель, единственный способ, который я нашел, — это использовать HasData API с пользовательскими данными из базы данных (сначала я создал с помощью usermanager и просто скопировал и вставил). Кроме того, вам нужно настроить модель удостоверения, чтобы добавить UserRoles свойство навигации для добавления роли с помощью HasData... и множество проблем. Я рекомендую вам внимательно прочитать эту страницу из MS Learn.microsoft.com/en-us/ef/core/modeling/data-seeding Мое решение — создать собственную логику заполнения, и я предпочитаю переместить их в отдельное консольное приложение. .

Nguyễn Kỳ Dương Trường 19.05.2024 11:28

Я уже использую этот метод HasData внутри метода SeedData. Поэтому я попытался использовать старый традиционный способ, добавляя новые строки в таблицу IdentityUser, затем добавляя строки в таблицу IdentityRole, а затем связывая пользователей и роли, добавляя новые строки в таблицу IdentityUserRole. Но я боюсь, что мне не хватает некоторых дополнительных действий, которые выполняются при создании пользователя или при хешировании пароля @NguyễnKỳDươngTrường

siba36 19.05.2024 11:40

Более подробно: если вы включите AccountConfirmed, вам следует установить для EnmailConfirmed значение true. Кстати, UserManager и RoleManager обычно используют Normalized{Object} вместо {Object}. Пожалуйста, установите верхний регистр.

Nguyễn Kỳ Dương Trường 19.05.2024 12:10

Внедрение службы в конструктор создаст циклическую ссылку. Когда вы определяете свою модель, она не существует. ИМХО, просто добавьте тест запуска в свое приложение; применить миграцию, проверить, пуста ли таблица, заполнить некоторые значения по умолчанию.

Jeremy Lakeman 21.05.2024 05:25
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Попробуй это:

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

    var userManager = this.GetService<UserManager>();
    ...

Однако есть одна вещь: я бы сделал это в вашей реализации IdentityDbContext, используя HasData, как предложено в одном из комментариев выше, НЕ в DbContext OnModelCreating, т.е.

public MyIdentityDbContext : IdentityDbContext<ApplicationUser, IdentityRole, string> {
    
    public MyIdentityDbContext(DbContextOptions<MyIdentityDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
  
        SetupData(builder);
    }

    private void SetupData(ModelBuilder builder)
    {
        // Users
        var defaultUsers = new List<ApplicationUser>() { 
            new ApplicationUser()
            {
                Id = "USER_HASH"
                Name = "name",
                Email = "[email protected]",
                NormalizedEmail = "[email protected]",
                UserName = "username",
                NormalizedUserName  = "NormalizedUserName",
                UserType = UserType.WebsiteAdmin,
                // Use your known hash here (the hash below is for Password123!)
                PasswordHash = "PasswordHash",
                // Use your hash here
                SecurityStamp = "SecurityStamp",
                AccessFailedCount = 0,
                EmailConfirmed = true,
                PhoneNumberConfirmed = true,
                TwoFactorEnabled = false
            }
        };

        builder.Entity<ApplicationUser>().HasData(defaultUsers);

        // Roles
        var defaultRoles = new List<IdentityRole>(){ 
            new IdentityRole { Id = "ROLE_HASH", Name = 
            RoleNames.WebsiteAdminRole, NormalizedName = 
            RoleNames.WebsiteAdminRole.ToUpper() }
        };

        builder.Entity<IdentityRole>().HasData(defaultRoles);

        // User roles
        var defaultUserRoles = new List<IdentityUserRole<string>>(){ 
            new IdentityUserRole<string>(){
               Id = "USER_HASH",
               RoleId = "ROLE_HASH" 
            }
        };

        builder.Entity<IdentityUserRole<string>>().HasData(defaultUsersRoles);
    }

}

Примечание. Хэш, который у меня есть для приведенного выше пароля, соответствует API, настроенному на использование IdentityV3, т.е.

builder.Services.Configure<PasswordHasherOptions>(options => options.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV3);

Вы можете создать свой собственный хэш пароля, используя

var passwordHasher = this.GetService<IPasswordHasher<IdentityUser>>();
var passwordHash = passwordHasher.HashPassword(user, "YourPassword");

куда бы я положил эту инструкцию? внутри SeedData конструктора? Я пытался поместить его туда, но для класса GetService не определен метод SeedData?

siba36 19.05.2024 11:35

в OnModelCreating (или в методе вашей реализации DbContext). Я добавил пример использования метода HasData, который, по моему мнению, вам действительно следует делать.

matt sharp 19.05.2024 11:42

Проблема, с которой вы столкнулись, связана с смешиванием задач определения схемы базы данных и управления пользователями в OnModelCreating. OnModelCreating в первую очередь предназначен для настройки сущностей и отношений с использованием Fluent API и не идеально подходит для операционных задач, таких как создание пользователей. Как вы описали, попытка внедрить UserManager непосредственно в ваш DbContext может привести к проблемам с разрешением зависимостей из-за циклических зависимостей и способа управления областями внедрения зависимостей в приложениях ASP.NET Core. Вместо этого вы можете попытаться заполнить свои данные, используя отдельный механизм заполнения, который запускается после настройки контекста вашей базы данных.

Для этого сначала создайте начальный класс, который не зависит от конструктора DbContext:

public class SeedData
{
    private readonly UserManager<ApplicationUser> _userManager;

    public SeedData(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task SeedWebsiteAdminsAsync()
    {
        var superAdminLogin = new ApplicationUser()
        {
            UserName = "username",
            Email = "youremail",
            UserType = UserType.WebsiteAdmin
        };

        var result = await _userManager.CreateAsync(superAdminLogin, "test");

        if (result.Succeeded)
        {
            await _userManager.AddToRoleAsync(superAdminLogin, RoleNames.WebsiteAdminRole);
        }

        // Additional logic for saving related data to the database
    }

    public async Task SeedCompanyAdminsAsync()
    {
        // Your logic to seed company admins
    }
}

В своем Program.cs настройте свое приложение для запуска заполнения после запуска приложения и после выполнения миграции. Удалите исходный код из OnModelCreating.

Другие вопросы по теме