数据层中的IdentityUser

she*_*mus 5 design-patterns entity-framework repository-pattern asp.net-identity

我正在努力设计(或过度设计)我正在做的网络项目.

我有一个MyProject.Data,MyProject.Business和MyProject.Web DLL.

我的数据层(基于EF6)包含我的实体,db context.

我的业务层包含我的存储库(是的,可能不是真正的repo模式).

我已经将IdentityFramwork添加到Web项目中,并且cource创建了ApplicationUser.我的数据层中已有用户POCO.我想将Application用户移动到Data层,因此我可以将它与其他实体一起使用.

一种方法是让我的Data.User扩展IdentityUser,并让我的Data.MyContext扩展IdentityDbContext.这导致数据层与asp.net.identity框架强烈耦合,这种框架感觉不太对劲.

这里的最佳做法是什么?

Ali*_*son 2

我不建议进行Userextends ApplicationUser。我认为这ApplicationUser只是用户访问您的应用程序的一种方式(通过电子邮件登录并通过,通过 facebook、google)。您的用户实体属于您的域,因此您在那里不需要密码(作为示例)。实际上,一个User应用程序可以关联多个应用程序用户,为什么同一个用户不能使用不同的帐户登录呢?

让您的数据层引用 Identity 的 dll是可以的,您打算在项目中多久更改一次?我真诚地从未见过这种情况发生,一旦你让一个项目使用定义的框架运行,你很少改变它,当然你的应用程序可能会增长,但你很少需要改变这样的事情。这样,您的数据层就不会耦合到 Web 项目,而是耦合到库(例如sunil提到的)。AspNetUser 的表将在那里,但 WPF 项目不一定需要使用它作为登录方法

此外,我建议您将实体保留在业务层中(我也鼓励不要DataAnnotations在那里使用),然后让您的数据层使用 Fluent API将它们映射到实体框架,并将 EF 封装在您的具体存储库中,该存储库应该位于数据中该层仅实现业务层的存储库接口。

让您的 DBContext 扩展 IdentityDbContext,但不要让您的 User 扩展 ApplicationUser,您可以让 ApplicationUser 拥有一个 User。每当新用户登录您的应用程序时,您通常会创建一个新的 ApplicationUser,您也可以创建一个User并与其关联。登录后,该用户可以将另一个外部登录关联到他的帐户(这意味着许多应用程序用户与同一用户)。

下面的代码示例我没有获得任何现有来源,我只是为了分析而发明的

业务层 - 不应该了解其他层,它是最简单的,应该表达您的真实业务并成为通用语言的一部分

public class User: ITrackable
{
    public int ID { get; protected set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public UserStatus Status { get; set; }
    public DateTime? Birthday { get; set; }
    public virtual ICollection<Address> Addresses { get; protected set; }

    public string LastKnownPlace { get; set; }

    public virtual bool IsPremium()
    {
        // some logic
    }

    public string GetTrackingIdentification()
    {
        // gets some unique key representing this object. Shouldn't be ID, since I might track other objects, soon IDs would dup for different objects...
    }

}

public interface ITrackable
{

    string GetTrackingIdentification();
    string LastKnownPlace { get; set; }

}

public interface ITrackingService<T> where T: ITrackable
{

    void Track(IEnumerable<T> source);

}

public interface IUserTrackingService: ITrackingService<User>
{

    IEnumerable<User> GetDeadbetUsersWithTracking();

}

public interface IUserRepository
{
    IEnumerable<User> GetPremiumUsers();
    IEnumerable<User> GetDeadbets();
}
Run Code Online (Sandbox Code Playgroud)

基础设施层——应该处理如何执行应用程序请求的操作。它可以通过实现存储库来使用数据库持久性,或者通过使用实体框架或任何其他提供程序,或者写入文本文件、排队任务、发送电子邮件、日志记录,可能写入注册表,这些可以称为基础设施服务。这里你应该引用Business 层,你也可以引用 Identity 的 dll。为了保持示例简单,我只是实现存储库:

public class YourContext : IdentityDbContext<ApplicationUser>
{

    DbSet<User> DomainUsers { get; set; }
    DbSet<Address> Addresses { get; set; }

    public YourContext(): base("DefaultConnection", throwIfV1Schema: false) { }

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

        modelBuilder.Entity<IdentityRole>().Property(r => r.Name).HasMaxLength(200);
        modelBuilder.Entity<ApplicationUser>().Property(a => a.UserName).HasMaxLength(200);
        modelBuilder.Entity<User>().Property(u => u.Name).IsRequired().HasMaxLength(50);
    }

}
Run Code Online (Sandbox Code Playgroud)

服务层 - 将参考业务层,也许还有基础层,实现业务服务。服务在这里是一个含糊不清的术语。有应用程序服务、领域服务和基础设施服务。领域服务是一种复杂/通用的操作,是业务逻辑的一部分,但不属于特定实体。应用程序服务是一种通过 API、WCF 等向外部世界公开操作的方法。我之前解释过的基础服务。我将服务层理解为实现业务服务并向应用程序公开的层。由于业务部门不知道服务是如何实现的,因此可以委托给外部 API 等。这样,业务仍然与一切脱钩。

public class UserTrackingService : IUserTrackingService
{

    private IUserRepository repo;

    // Injects an UserRepository to the service...
    public UserTrackingService(IUserRepository repository)
    {
        this.repo = repository;
    }

    public IEnumerable<User> GetDeadbetUsersWithTracking()
    {
        var users = this.repo.GetDeadbets();
        this.Track(users);
        return users
    }

    public void Track(IEnumerable<User> source)
    {
        // This would use each ITrackable's (in this case each User) Identification to request some external API, get last known place and set to ITrackable's LastKnownPlace property...
        api.Track(source);
    }

}
Run Code Online (Sandbox Code Playgroud)

应用层 - 可能指业务层、基础设施/数据层和服务层。在这里,您应该表达/定义应用程序可以执行哪些操作,而不是如何执行。应用程序仅接收请求,对请求者进行身份验证并保证他们有权执行上述操作。一旦一切正常,它就会委托给服务/业务/基础设施层。这里强烈建议使用DTO(或ViewModel)来接收和返回数据到外部世界。它可能从服务/业务/基础层接收域数据,转换为 DTO 并返回。这可以解释为控制器。

public class TrackedUserDTO
{
    public string Name { get; set; }
    public string MainAddress { get; set; }
    public string LastKnownPlace { get; set; }
    public decimal TotalDebt { get; set; }
    public bool ShouldBeContacted { get; set; }

}

public class UserController : Controller
{

    private IUserTrackingService service;
    private IUserRepository repository;

    // Injected - IoC
    public UserController(IUserTrackingService service, IUserRepository repository)
    {
        this.service = service;
        this.repository = repository;
    }

    public ActionResult Index()
    {
        return View();
    }

    [Authorize(Roles="Manager,Admin", ErrorMessage="You aren't allowed do see this content...")]
    public ActionResult GetDeadbetUsersWithTracking()
    {
        IEnumerable<User> users = this.service.GetDeadbetUsersWithTracking();
        IEnumerable<TrackedUserDTO> dtoUsers = Mapper.Map<IEnumerable<TrackedUserDTO>>(users);
        return View(dtoUsers);
    }
Run Code Online (Sandbox Code Playgroud)

我强烈建议阅读这些:

创建域服务 作者:Philip Brown

Jimmy Bogard 的领域驱动设计服务

为什么不在业务层内使用 DTO?避免贫血域模型(作者:Martin Fowler)