【发布时间】:2021-10-05 10:55:18
【问题描述】:
我最近在使用 .NET 5 时开始搞乱清洁架构概念。我遇到的问题是,虽然身份在基础设施层中被很好地隔离,但如果我想在不混合的情况下使用用户的其他属性AspNetUsers 表中的数据,问题就开始了。
我必须在域层中创建一个新实体,例如 UserProfile,并想办法将该实体与 ApplicationUser 实体相关联,而无需
- 在我的新实体中包含 ApplicationUser 引用
- 将 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