Separando o ASP.NET IdentityUser das minhas outras entidades

11

Eu tenho uma ProjectName.Corebiblioteca contendo toda a minha lógica de negócios, minhas entidades e seu comportamento. Atualmente, não há nenhuma relação com o Entity Framework ou qualquer outro DAL, porque eu gosto de manter essas coisas separadas. As configurações do Entity Framework (usando a API Fluent) residem em um ProjectName.Infrastructureprojeto, de modo a cuidar de colocar minhas entidades no EF. Basicamente, eu estou indo na direção de uma arquitetura semelhante à cebola.

No entanto, ao adicionar a estrutura de identidade do ASP.NET ao mix, preciso ApplicationUserherdar minha entidade da IdentityUserclasse, mas minha ApplicationUserclasse tem relações com outras entidades. Ao herdar IdentityUser, estou introduzindo uma referência ao Entity Framework em meu projeto de entidades, o único lugar em que eu não gostaria de fazê-lo. Puxar a ApplicationUserclasse para fora do projeto de entidades e entrar no Infrastructureprojeto (já que ele está usando um sistema de identidade baseado em Entity Framework) resultará em referências circulares, de modo que também não é o caminho a seguir.

Existe alguma maneira de contornar isso para que eu possa manter a separação limpa entre as duas camadas, além de não usar a identidade do ASP.NET?

Steven Thewissen
fonte
1
Você não pode criar uma interface IApplicationUser em seu projeto Core e manter a implementação em Infraestrutura? Na minha opinião, a menos que você esteja criando uma API ou precise trocar implementações de interface em tempo de execução, mantenha todo o código que não seja da interface do usuário em um projeto. Ter vários projetos diferentes apenas aumenta sua sobrecarga de gerenciamento mental e de código sem muito benefício.
mortalapeman

Respostas:

12

Você pode criar uma classe de usuário que não tem nada a ver com a identidade do ASP.NET na sua biblioteca principal.

public class User {
    public Guid UserId { get; set; }
    public string UserName { get; set; }
    public string EmailAddress { get; set; }
    public string EmailAddressConfirmed { get; set; }
    public string PhoneNumber { get; set; }
    public string PhoneNumberConfirmed { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }

    ...

    public virtual ICollection<Role> Roles { get; set; }
    public virtual ICollection<UserClaim> UserClaims { get; set; }
    public virtual ICollection<UserLogin> UserLogins { get; set; }
}

Se você estiver usando o Entity Framework, crie uma classe de configuração para suas entidades (opcional).

internal class UserConfiguration : EntityTypeConfiguration<User>
{
    internal UserConfiguration()
    {
        ToTable("User");

        HasKey(x => x.UserId)
            .Property(x => x.UserId)
            .HasColumnName("UserId")
            .HasColumnType("uniqueidentifier")
            .IsRequired();

        Property(x => x.PasswordHash)
            .HasColumnName("PasswordHash")
            .HasColumnType("nvarchar")
            .IsMaxLength()
            .IsOptional();

        Property(x => x.SecurityStamp)
            .HasColumnName("SecurityStamp")
            .HasColumnType("nvarchar")
            .IsMaxLength()
            .IsOptional();

        Property(x => x.UserName)
            .HasColumnName("UserName")
            .HasColumnType("nvarchar")
            .HasMaxLength(256)
            .IsRequired();

        // EmailAddress, PhoneNumber, ...

        HasMany(x => x.Roles)
            .WithMany(x => x.Users)
            .Map(x =>
            {
                x.ToTable("UserRole");
                x.MapLeftKey("UserId");
                x.MapRightKey("RoleId");
            });

        HasMany(x => x.UserClaims)
            .WithRequired(x => x.User)
            .HasForeignKey(x => x.UserId);

        HasMany(x => x.UserLogins)
            .WithRequired(x => x.User)
            .HasForeignKey(x => x.UserId);
    }
}

Você também deve criar classes para Role, UserClaim e UserLogin . Você pode nomeá-los como quiser, se não gostar dos nomes acima.

Na camada da web, crie uma classe chamada AppUser (ou outro nome, se você escolher). Essa classe deve implementar a interface ASP.NET Identity IUser <TKey> , em que TKey é o tipo de dados da chave primária ( Guid no exemplo acima).

public class AppUser : IUser<Guid>
{
    public AppUser()
    {
        this.Id = Guid.NewGuid();
    }

    public AppUser(string userName)
        : this()
    {
        this.UserName = userName;
    }

    public Guid Id { get; set; }
    public string UserName { get; set; }
    public string EmailAddress { get; set; }
    public string EmailAddressConfirmed { get; set; }
    public string PhoneNumber { get; set; }
    public string PhoneNumberConfirmed { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }
}

Altere todas as referências ao UserManager no projeto da web para UserManager <AppUser, Guid> .

Por fim, crie sua própria UserStore . Essencialmente, o UserStore personalizado recebe o objeto AppUser , converte-o em um objeto de entidade Usuário e depois o mantém . Um exemplo de um desses métodos é mostrado abaixo:

public class UserStore : 
    IUserLoginStore<AppUser, Guid>, 
    IUserClaimStore<AppUser, Guid>, 
    IUserRoleStore<AppUser, Guid>, 
    IUserPasswordStore<AppUser, Guid>, 
    IUserSecurityStampStore<AppUser, Guid>, 
    IUserStore<AppUser, Guid>, 
    IDisposable
{
    private User MapFromAppUser(AppUser appUser)
    {
        if (appUser == null)
            return null;

        var userEntity = new User();

        PopulateUser(userEntity, appUser);

        return userEntity;
    }

    private void PopulateUser(User user, AppUser appUser)
    {
        user.UserId = appUser.Id;
        user.UserName = appUser.UserName;
        user.EmailAddress = appUser.EmailAddress;
        user.EmailAddressConfirmed = appUser.EmailAddressConfirmed;
        user.PhoneNumber = appUser.PhoneNumber;
        user.PhoneNumberConfirmed = appUser.PhoneNumberConfirmed;
        user.PasswordHash = appUser.PasswordHash;
        user.SecurityStamp = appUser.SecurityStamp;

        // First name, last name, ... 
    }

    #region IUserStore<AppUser, Guid> Members

    public Task CreateAsync(AppUser appUser)
    {
        if (appUser == null)
            throw new ArgumentNullException("appUser");

        var userEntity = MapFromAppUser(appUser);

        // Persist the user entity to database using a data repository.
        // I'll leave this to you.
    }

    ...

    #endregion
}

Para obter uma descrição completa de uma possível implementação, clique aqui .

No final, a escolha é sua. Avalie a quantidade de esforço necessário para manter essa implementação, em vez de apenas referenciar a estrutura de Identidade na sua biblioteca Core. Pessoalmente, pensei em fazê-lo da maneira que descrevi acima, porém não o fiz, porque provavelmente teria que alterar meu código toda vez que a estrutura de identidade do ASP.NET for atualizada.

Espero que isso ajude e responda à sua pergunta!

fbhdev
fonte