【问题标题】:Clean Architecture Issue when using .Net Core Identity (.NET 5)使用 .Net Core Identity (.NET 5) 时出现清理架构问题
【发布时间】:2021-10-05 10:55:18
【问题描述】:

我最近在使用 .NET 5 时开始搞乱清洁架构概念。我遇到的问题是,虽然身份在基础设施层中被很好地隔离,但如果我想在不混合的情况下使用用户的其他属性AspNetUsers 表中的数据,问题就开始了。

我必须在域层中创建一个新实体,例如 UserProfile,并想办法将该实体与 ApplicationUser 实体相关联,而无需

  1. 在我的新实体中包含 ApplicationUser 引用
  2. 将 ApplicationUser 实体移动到域层并在其中引用身份命名空间。

在所有 Clean Architecture 项目中,这种情况要么没有得到解决(他们只是将新属性添加到 ApplicationUser 中),要么给出的解决方案是在新实体中包含对 Identity User Id 的引用。

我不喜欢第一个解决方案,因为它迫使我将自定义字段添加到身份表中。第二种解决方案带来了新的和复杂的问题,因为它会迫使我在创建或删除数据的情况下手动同步 2 个表,还会强制使用事务以避免在同步失败的情况下出现孤儿数据。它还带来了一个抽象问题,因为删除仅使用 Identity 的用户不会自动处理可能依赖或以某种方式与 AspNetUsers 表相关的未知实体(来自未来的插件)。

让我更具体一些。这是我的简化项目结构(使用 .NET core 5):

应用程序

  • 接口
    • IApplicationDbContext.cs

核心

    • UserProfile.cs(我的自定义用户类,带有附加属性)

基础设施

  • 数据
    • ApplicationDbContext.cs(接口实现)
  • 身份
    • ApplicationRole.cs
    • ApplicationUser.cs DependencyInjection.cs

网络

我的网络应用程序

这是我的代码:

IApplicationDbContext.cs

namespace Application.Common.Interfaces
{
    public interface IApplicationDbContext
    {        
        Task<int> SaveChangesAsync(CancellationToken cancellationToken);
    }
}

UserProfile.cs

namespace Core.Domain
{
    public class UserProfile : AuditEntity
    {
        // Don't want to reference ApplicationUser in domain.
        // [ForeignKey("Id")]
        //public virtual ApplicationUser User { get; set; }

        [StringLength(50)]
        public string FirstName { get; set; }

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

        [StringLength(50)]
        public string JobPosition { get; set; }

        [StringLength(50)]
        public string Photo { get; set; }

        [DefaultValue(true)]
        public bool IsListed { get; set; }

        [DefaultValue(false)]
        public bool IsDisabled { get; set; }

        [DefaultValue(false)]
        public bool IsLocked { get; set; }

        public DateTime? LastLoginDate { get; set; }

        public DateTime? LastActivityDate { get; set; }

        [StringLength(100)]
        public string LastSessionId { get; set; }

        [StringLength(256)]
        public string PrivateFolder { get; set; }

        public bool IsOnApproval { get; set; } = true;

        public bool IsDeleted { get; set; } = false;
    }
}

ApplicationDbContext.cs

namespace Infrastructure.Data
{
    
    public partial class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>, IApplicationDbContext
    {                        
        public ApplicationDbContext(DbContextOptions options) : base(options)
        {
            
        }

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


            modelBuilder.ApplyConfiguration(new Configurations.UserConfiguration());                    
            modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());                            
        }
                   
        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
        {
            var entries = ChangeTracker.Entries().Where(x => x.Entity is AuditEntity && (x.State == EntityState.Added || x.State == EntityState.Modified));

            foreach (var entry in entries)
            {
                if (entry.State == EntityState.Added)
                {
                    ((AuditEntity)entry.Entity).CreatedBy = _currentUserService.UserId;
                    ((AuditEntity)entry.Entity).CreatedDate = DateTime.UtcNow;
                }
                ((AuditEntity)entry.Entity).LastModifiedBy = _currentUserService.UserId;
                ((AuditEntity)entry.Entity).LastModifiedDate = DateTime.UtcNow;
            }
            return base.SaveChangesAsync(cancellationToken);
        }

    }
}

ApplicationRole.cs

namespace Infrastructure.Identity
{
    
    public class ApplicationRole : IdentityRole<int> //, ISoftDeletable
    {
        public ApplicationRole() : base() { }
        public ApplicationRole(string name) : base(name) { }
    }
}

ApplicationUser.cs

namespace Infrastructure.Identity
{    
    public class ApplicationUser: IdentityUser<int>
    {      
        
    }
}

DependencyInjection.cs

namespace Infrastructure
{
    public static class DependencyInjection
    {
        public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config)
        {            
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    config.GetConnectionString("eCMConnection"),
                    context => context.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)));
         
            services.AddIdentity<ApplicationUser, ApplicationRole>(
                    options =>
                    {
                        options.Password.RequireDigit = true;
                        options.Password.RequiredLength = 8;
                        options.Password.RequireNonAlphanumeric = true;
                        options.SignIn.RequireConfirmedAccount = false;
                    }
                )
                .AddRoleManager<RoleManager<ApplicationRole>>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders(); // two factor authentication
            
            services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());

            services.AddTransient<IEmailService, EmailService>();

            return services;
        }
    }
}

所以,我的问题是,能够拥有一个自定义 UserProfile 实体的最佳方法是,该实体的数据存储在与 AspNetUsers 表相关的表中,而无需在 UserProfile 域实体中引用 ApplicationUser 和 Identity。能够创建和删除身份用户以及我的 UserProfile 表中的相关数据的最佳方法是什么。有人见过这样的例子吗?

谢谢!希望有人可以对此有所了解。

【问题讨论】:

    标签: c# .net identity clean-architecture


    【解决方案1】:

    我认为首先您必须决定是否最终要将附加属性存储在同一个表中,或者作为身份实体存储在不同的表中。正如您已经指出的那样,这两种方法都有优点和缺点,但我没有看到任何其他选择,只能选择一种。

    一旦决定您仍然可以将原始身份实体和附加属性封装在一个域对象中,而无需创建对基础架构层的依赖关系。一种选择是存储库模式,其中接口位于领域层,实现位于基础设施层。在存储库中,您将域对象映射到一个或多个表。这样,您还可以根据需要封装事务处理。

    【讨论】:

    • 您好,感谢您的意见。不想让 Identity 原始表中包含自定义属性,所以我显然更喜欢第二种选择。第一个很容易设置,我不想在那个上寻求帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-14
    • 1970-01-01
    • 2017-07-11
    • 2018-07-31
    • 2020-08-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多