使用干净的架构和存储库/工作单元来构建具有 ASP.NET Identity 的应用程序

问题描述 投票:0回答:1
我正在努力使用 Clean Architecture(后端部分)创建一个项目。在此应用程序中,我将处理可以使用 jwt 身份验证注册/登录并使用另一个实体(比方说,使用产品)进行操作的用户。因此,根据清洁架构原则,我需要创建 4 层:

    域 - 实体、IRepository、IUnitOfWork
  1. 持久性 - DbContext,存储库的实现。
  2. 应用程序 - 将使用 UnitOfWork 对象来操作存储库的命令和查询。
  3. UI - 在我的例子中是 web-api 应用程序。
我开始开发领域级别的项目:

这是 IRepository:

public interface IRepository<T> where T : Entity { //methods go here }
这是 IUnitOfWork:

public interface IUnitOfWork { IRepository<Product> ProductRepository { get; } IRepository<User> UserRepository { get; } //User is a simple class not inheritated from IdentityUser (I can't use ApplicationUser in Domain) public Task SaveAllAsync(); public Task DeleteDataBaseAsync(); public Task CreateDataBaseAsync(); }
我的问题是,如果用户的存储库必须使用不同的 dbcontext,如何处理产品和用户?例如,产品的 AppDbContext 可能如下所示:

public class AppDbContext:DbContext { public AppDbContext(DbContextOptions<AppDbContext> options):base(options) { Database.EnsureCreated(); } public DbSet<Product> Products{ get; set; } }
但是对于用户来说它将继承自IdentityDbContext:

public class AppDbContext:IdentityDbContext<IdentityUser> { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<ApplicationUser> Users{ get; set; } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); } }
因此,这里我处理两个不同的上下文,而我需要在 IRepository 和 IUnitOfWork 实现中使用一个上下文。

我该怎么办?我应该创建两个不同的存储库(如 IUserRepository 和 IProductRepository)并创建两个单独的类来实现它们吗? (就像 UserRepository 将注入 UserDbContext 和 ProductRepository 将注入 AppDbContext)

我尝试在网上查找相关信息,但没有任何与我的情况类似的例子。当身份验证逻辑是 UI 层的一部分并且看起来像服务时,我只见过一种实现,但这不是我想要的。 我还发现了一个关于将 ApplicationUser 添加到域项目的有趣想法。这是不正确的,因为这意味着添加对“Microsoft.AspNetCore.Identity.EntityFrameworkCore”的引用,这违背了干净的架构。

asp.net repository-pattern identity dbcontext clean-architecture
1个回答
0
投票
正如 Panagiotis Kanavos 所强调的那样,

DbContext

 已经是一个工作单元,而 
DbSet
 实际上是一个存储库。创建单独的抽象通常会增加不必要的复杂性,除非您需要模拟或自定义查询逻辑。通过正确的设置,您可以专注于功能而不是管理基础设施的复杂性。
为了解决您的问题,您不需要多个 
DbContext
 实例。相反,扩展 
IdentityDbContext
 来处理身份和域实体。这可以隔离身份问题,同时保持数据访问的单一入口点。
具体方法如下:

public class ApplicationDbContext : IdentityDbContext<IdentityUser, IdentityRole, string> { public DbSet<Product> Products => Set<Product>(); public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } }
完整的工作示例:

务实干净的架构

这种方法避免了管理多个上下文的开销。对于大多数CRUD操作,您可以直接使用DbContext。但是,如果需要,您仍然可以将复杂的查询封装在存储库中:

public class ProductRepository { private readonly ApplicationDbContext _dbContext; public ProductRepository(ApplicationDbContext dbContext) => _dbContext = dbContext; public async Task<Product?> GetByIdAsync(int id, CancellationToken cancellationToken) => await _dbContext.Products.FindAsync(new object[] { id }, cancellationToken); }
完整的工作示例:

Pragmatic Clean Architecture ProjectRepository.cs

但是,在您的情况下,如果您不想要解耦的域 User,而是希望使用自定义的

ApplicationUser

,它继承自 
IdentityUser
。要实现此目的,您不需要将其单独声明为 
DbSet
,因为当您将其作为泛型参数传递时,它已经在 
IdentityDbContext
 中定义。

以下是此用例的示例:

https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Infrastruct/Data/ApplicationDbContext.cs

通过

IdentityServer 提供对这些身份用户的访问。通常,您可以使用 UserManager

RoleManager
 类与它们进行交互,如以下示例所示:

public class IdentityService(UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager) : IIdentityService { public async Task<User> GetUserAsync(string id) { var identityUser = await userManager.FindByIdAsync(id) ?? throw new NotFoundException(nameof(User), id); var roles = await userManager.GetRolesAsync(identityUser); return new User(identityUser.Id, identityUser.UserName, new Email(identityUser.Email)) { Roles = roles.Select(role => new Role(role, role, new List<string>())).ToList() }; } public async Task DeleteUserAsync(string userId) => await userManager.DeleteAsync(await userManager.FindByIdAsync(userId) ?? throw new NotFoundException(nameof(User), userId)); public async Task CreateRoleAsync(Role role) => await roleManager.CreateAsync(new IdentityRole { Name = role.Name }); }
完整的工作示例:

实用的清洁架构IdentityService.cs

© www.soinside.com 2019 - 2024. All rights reserved.