将 ASP.NET 身份与核心域模型解耦 - 洋葱架构

Posted

技术标签:

【中文标题】将 ASP.NET 身份与核心域模型解耦 - 洋葱架构【英文标题】:Decoupling ASP.NET Identity from the Core Domain Models - Onion Architecture 【发布时间】:2014-12-22 04:44:34 【问题描述】:

我使用这个示例项目 (https://github.com/imranbaloch/ASPNETIdentityWithOnion) 作为我的应用程序架构,在这个示例中,核心完全从包括身份框架在内的基础设施中解构出来。

在这个示例中,作者使用了适配器模式来解耦核心身份类(IdentityUser、IdentityRole ...),并在核心层提供类似的类。

现在这个示例项目中的问题是域模型(产品、图像)没有与模仿身份的虚拟类(AppUser、ApplicationRole、AppliationUserRoles...)链接。

然后我修改了代码,添加了对 AppUser 的引用

public sealed class Image : BaseEntity

    public Image()
    
        Products = new HashSet<Product>();
    

    public string Path  get; set; 

    public AppUser AppUser  get; set;  // The  Added Reference ...

    public ICollection<Product> Products  get; set; 

如果我将“AppUser”导航属性放在“Image”类中,则创建的数据库将有四个新表,而不是身份框架的默认五个表。

我需要将这些表合并到默认表中。 怎么样?

编辑:

这是驻留在数据层中的身份模型(我无法从核心引用)。

public class ApplicationIdentityUser :
    IdentityUser<int, ApplicationIdentityUserLogin, ApplicationIdentityUserRole, ApplicationIdentityUserClaim>, IDomainUser 

    public ApplicationIdentityUser()
        : base() 
        Images = new HashSet<Image>();
    

    public string Name  get; set; 
    public virtual ICollection<Image> Images  get; set; 



public class ApplicationIdentityRole : IdentityRole<int, ApplicationIdentityUserRole>

    public ApplicationIdentityRole()

    public ApplicationIdentityRole(string name)Name = name;


public class ApplicationIdentityUserRole : IdentityUserRole<int> 

public class ApplicationIdentityUserClaim : IdentityUserClaim<int>

public class ApplicationIdentityUserLogin : IdentityUserLogin<int>

这也是我在 OnModelCreating 方法中的模型构建器:

  modelBuilder.Entity<Image>()
            .Property(e => e.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<Image>()
            .HasMany(e => e.Products)
            .WithRequired(e => e.Image)
            .WillCascadeOnDelete(false);
        modelBuilder.Entity<ApplicationIdentityUser>()
             .Property(e => e.Id)
             .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<ApplicationIdentityRole>()
            .Property(e => e.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<ApplicationIdentityUserClaim>()
             .Property(e => e.Id)
             .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

【问题讨论】:

AspNet* 表是多余的。需要摆脱它们。检查您的 ApplicationDbContext 对这些表做了什么? 我的 ApplicationDbContext 实际上是从 IdentityDbContext 驱动的,我需要一种方法来存储用户信息,您的意思是我可以使用以 Application* 开头的表吗? 我是说应该只有一组 *Users、*UserClaims 等表。看起来您在 ApplicationDbContext 中进行继承的方式已被破坏,并且您没有覆盖 UsersRoles 等,因此 EF 为您创建了两组表。检查一下。 好的,我会更新我的问题以包含实现细节 @trailmax 我已经更新了问题,请检查... 【参考方案1】:

好的,我已经通过执行以下操作解决了这个问题:

    在核心中包含对 Microsoft.AspNet.Identity.Core 的依赖项 在AppUser上实现IUser接口(该接口来自 Microsoft.AspNet.Identity.Core)。 在ApplicationRole上实现IRole接口。 完全摆脱 IdentityDbContext 并仅从 DbContext 继承。 实现您自己的 IUserStore* 版本,提供您的 AppUser 实现您自己的 IRoleStore 版本,提供您的 ApplicationRole

我知道依赖 Microsoft.AspNet.Identity.Core 听起来很奇怪,但我们只需要 IUser 接口,它基本上也被视为您的应用程序的核心域模型。

这里的终极想法是彻底摆脱 Microsoft.AspNet.Identity.EntityFramework

有兴趣的开发者可以为此 +1,所以我可以在 GitHub 上上传完整的工作示例。

【讨论】:

我正要踏上这段旅程,因为我得出了相同的结论,即 Microsoft.AspNet.Identity.EntityFramework 是很好的示例代码,但如果您打算让用户成为一流的域,则会干扰您的域模型。 真的很想看看您是如何摆脱 IdentityDbContext 的,以及您是如何在 Image 实体中引用 ApplicationIdentityUser 的,因为它位于核心而不是数据层中!任何示例将不胜感激。 有机会在 GitHub 上放一个例子吗? 你要放样本吗? 我不这么认为,搜索存储库,你会发现 Microsoft.AspNet.Identity.EntityFramework【参考方案2】:

我正在使用这个框架,不需要在每个实体中都有一个链接 为了获取 userID 参考,我在 BaseEntity 中添加了一个属性 UserIDBy ,这样每个实体都会继承它。

public abstract class BaseEntity

    public int Id  get; set; 
    public string UserIDBy  get; set; 

接下来,在 web 项目中,IdentityExtensions.cs 中已经有一个名为 GetUserId(this IIdentity identity) 的扩展方法,因此要在每个 Create 和 Edit 操作结果中存储 UserIDBy:

创建操作结果:

 // POST: /Region/Create
    [HttpPost]
    public async Task<ActionResult> Create([Bind(Include = "RegionName")] Region region)
    
        if (ModelState.IsValid)
        
            // TODO: Add insert logic here
            var id = User.Identity.GetUserId();
            region.UserIDBy = id.ToString(); 

            await _regionService.AddAsync(region);
            return Json(new  success = true );
        

        return PartialView("_Create", region);
    

编辑操作结果:

 //// POST: /Region/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "id,RegionName")] Region region)
    
        if (ModelState.IsValid)
        
            var id = User.Identity.GetUserId();
            region.UserIDBy = id.ToString(); 
            _regionService.Update(region);
            return Json(new  success = true );
        
        return PartialView("_Edit", region);
    

别忘了导入它:

using Myapp.Web.Extensions;

【讨论】:

非常聪明。感谢分享。 ASPNETIdentityWithOnion 是一个很棒的 shell,但是创建与自定义表和身份框架的关系的麻烦并不值得。 你有实现 ASPNETIdentityWithOnion 的开源项目吗?【参考方案3】:

偶然发现了这个,遇到了同样的问题。

标记答案的问题是它仍然引用 Microsoft.AspNet,这使得未来的 .NET Core 计划变得粗糙。

主要问题实际上是在内核中构建身份验证功能的一般尝试,这违背了目的。

考虑让用于 Web 身份验证和授权的内置功能保留在 Web 层中,并引用代表 Core 需求的 Core 用户对象(UserProfile?)。这也将简化切换到另一种身份验证方法 (AD)。

然后,根据您的偏好,您可以从 AspNetUser 引用 Core.UserProfile 以避免多次 SQL 调用,或者只是确保对 Core.UserProfile 操作具有良好的缓存策略。

这允许您控制与您的核心模型分开的身份验证方法。

【讨论】:

以上是关于将 ASP.NET 身份与核心域模型解耦 - 洋葱架构的主要内容,如果未能解决你的问题,请参考以下文章

在洋葱、六边形或干净架构中,域模型是不是可以包含与数据库中的域模型不同的属性?

我的 Asp.net 核心应用程序事件处理程序不起作用

Asp.net核心身份声明与基于角色的授权[重复]

带有身份的asp.net核心中的依赖注入错误

如何在 ASP.NET 中将业务模型与身份模型分开?

将 ASP.NET 核心身份用户属性保存在另一个表中