Separar ASP.NET IdentityUser de mis otras entidades

11

Tengo una ProjectName.Corebiblioteca que contiene toda mi lógica de negocios y mis entidades y su comportamiento. Actualmente no existe ninguna relación con Entity Framework o cualquier otro DAL porque me gusta mantener esas cosas separadas. Las configuraciones de Entity Framework (usando la API Fluent) residen en un ProjectName.Infrastructureproyecto, de modo que se encarga de empujar mis entidades a EF. Básicamente voy en la dirección de una arquitectura similar a la cebolla.

Sin embargo, al agregar el marco de identidad ASP.NET en la mezcla, tengo que hacer que mi ApplicationUserentidad herede de la IdentityUserclase, pero mi ApplicationUserclase tiene relaciones con otras entidades. Al heredar IdentityUser, estoy introduciendo una referencia a Entity Framework en mi proyecto de entidades, el único lugar donde no quisiera hacerlo. Sacar la ApplicationUserclase del proyecto de entidades y Infrastructureponerla en el proyecto (ya que está utilizando un sistema de identidad basado en Entity Framework) dará como resultado referencias circulares, por lo que tampoco es el camino a seguir.

¿Hay alguna forma de evitar esto para que pueda mantener la separación limpia entre las dos capas además de no usar ASP.NET Identity?

Steven Thewissen
fuente
1
¿No puede crear una interfaz IApplicationUser en su proyecto Core y mantener la implementación en Infraestructura? En mi opinión, a menos que esté creando una API o necesite intercambiar implementaciones de interfaz en tiempo de ejecución, solo mantenga todo su código que no sea de UI en un proyecto. Tener un montón de proyectos diferentes solo se agrega a su sobrecarga de gestión mental y de código sin mucho beneficio.
mortalapeman

Respuestas:

12

Puede crear una clase de usuario que no tenga nada que ver con la identidad ASP.NET en su 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; }
}

Si está utilizando Entity Framework, cree una clase de configuración para sus 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);
    }
}

También debería crear clases para Role, UserClaim y UserLogin . Puede nombrarlos como quiera si no le gustan los nombres anteriores.

En la capa web, cree una clase llamada AppUser (u otro nombre si lo desea). Esta clase debe implementar la interfaz ASP.NET Identity IUser <TKey> , donde TKey es el tipo de datos para la clave primaria ( Guid en el ejemplo anterior).

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; }
}

Cambie todas las referencias a UserManager en el proyecto web a UserManager <AppUser, Guid> .

Finalmente, cree su propio UserStore . Esencialmente, el UserStore personalizado tomará el objeto AppUser , lo convertirá en un objeto de entidad de Usuario y luego lo mantendrá. A continuación se muestra un ejemplo de uno de estos métodos:

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 obtener una descripción completa de una posible implementación, haga clic aquí .

Al final, es tu elección. Mida la cantidad de esfuerzo que le tomaría mantener esta implementación en lugar de solo hacer referencia al marco de Identity en su biblioteca Core. Personalmente, pensé en hacerlo de la manera que describí anteriormente, sin embargo, no lo hice porque potencialmente tendría que cambiar mi código cada vez que se actualiza el marco de identidad de ASP.NET.

¡Espero que esto ayude y responda tu pregunta!

fbhdev
fuente