【问题标题】:How to resolve a DI class in a class library with .NET Core?如何使用 .NET Core 解析类库中的 DI 类?
【发布时间】:2017-10-06 21:55:11
【问题描述】:

我了解 .NET Core 中 DI 的基础知识,但我无法弄清楚如何将它用于多个项目。想象一下,我正在 ASP.NET Core 的 Startup 类中设置一个数据库上下文:

public void ConfigureServices(IServiceCollection services)
{
  services.AddDbContext<GalleryDb>();
}

我知道如何在 API 控制器中访问该上下文:

public class AlbumController : Microsoft.AspNetCore.Mvc.Controller
{
  private GalleryDb _ctx;

  public AlbumController(GalleryDb ctx)
  {
    _ctx = ctx;
  }
}

但是,当 API 控制器和数据访问类之间有很多层和功能时,该怎么办?最终代码到达了我的存储库类,它实际上需要上下文。它看起来像这样:

public class AlbumRepository
{
  private GalleryDb _ctx;
  public AlbumRepository(GalleryDb ctx)
  {
    _ctx = ctx;
  }

  public void Save(AlbumEntity entity)
  {
    // Use _ctx to persist to DB.
  }
}

我知道我可以将上下文从 API 入口点一直向下传递,但这似乎是一种反模式,因为这意味着通过多个对它不感兴趣的类和函数将其作为参数传递。

相反,我想在调用存储库类时执行类似的操作:

public void Save(AlbumEntity album)
{
  var ctx = DependencyResolver.GetInstance<GalleryDb>();
  var repo = new AlbumRepository(ctx);
  repo.Save(album);
}

我相信一些 DI 框架有类似的东西,但我正试图弄清楚如何使用原生 .NET Core 2.0 来做到这一点。这可能吗?最佳做法是什么?我发现一个线程 (ASP.NET Core DependencyResolver) 谈论使用 IServiceProvider,但暗示这不是一个理想的解决方案。

我希望无论解决方案是什么,我都可以将其扩展到其他 DI 类,例如 ASP.NET Identity 的 RoleManager 和 SignInManager。

【问题讨论】:

  • 您应该(几乎)永远不需要调用容器本身。到处使用构造注入。因此,您永远不需要new 对象的任何实例(有一些例外,即当您需要仅在运行时知道的参数时,您通常使用抽象工厂)

标签: dependency-injection asp.net-core .net-core asp.net-core-2.0


【解决方案1】:

我知道我可以将上下文从 API 入口点一直向下传递,但这似乎是一种反模式,因为这意味着将其作为参数传递给对它不感兴趣的多个类和函数。

不,这不是反模式。这就是你应该这样做的方式。然而,关于“对它不感兴趣的类和函数”这一点毫无意义。

简单地说,如果您正在使用诸如包装 DbContext 的存储库之类的东西(顺便说一句,这是一个可怕的想法,但我们会在其中添加一个图钉),那么您永远不应该直接进行交易与DbContext。相反,您应该将存储库注入到控制器中,然后简单地将上下文注入其中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<GalleryDb>();
    services.AddScoped<AlbumRepository>();
}

由于 ASP.NET Core 知道如何注入 GalleryDb,并且 AlbumRepositoryGalleryDb 作为构造函数参数,因此您也只需注册 AlbumRepository 以进行注入(使用“作用域”或请求生命周期)。

现在,您可以像当前注入上下文一样注入 AlbumRepository

public class AlbumController : Microsoft.AspNetCore.Mvc.Controller
{
    private AlbumRepository _repo;

    public AlbumController(AlbumRepository repo)
    {
        _repo = repo;
    }
}

当您有许多存储库时,这开始变得棘手,尤其是当您有需要与多个存储库交互的控制器时。最终,您的代码将成为服务配置和注入样板的老鼠巢。但是,此时,您也应该真正使用工作单元模式,将所有存储库封装在一个可以注入的类中。但是等等,哦,是的,这就是DbContext已经。它是一个封装多个存储库的工作单元,或DbSets。这就是为什么您不应该将存储库模式与实体框架结合使用。这是一个毫无意义的抽象,只会给你的代码添加额外的和不必要的熵。

如果你想抽象DbContext,那么你应该使用服务层模式(不要与微软称之为“服务模式”的RPC牛粪混淆)或CQRS(命令查询责任分离) ) 图案。存储库模式是为了一件事:抽象出原始 SQL。如果您没有原始 SQL,则不应实施该模式。

【讨论】:

  • chris-pratt,我确实在我的 .NET Core Web api 项目中使用 EF (softwareengineering.stackexchange.com/questions/180851/…) 遵循了您针对存储库的论点,并且对模拟 DbContext 和 DbSet 感到非常头疼。引入存储库使测试变得更加容易。如果您需要一个控制器中的多个存储库,为什么不只使用一个接口来公开您需要的所有内容并将其注入。
  • 不知道你为什么头疼。模拟 DbContext 有什么难的?无论如何,除非您不测试您的存储库,否则您仍然必须模拟它来测试它们。所以实际上你只是将测试负载加倍。
  • 我确实设法模拟了 DbContext,所以忽略“头痛”。如果我想用其他一些数据检索机制替换 EF 怎么办。如果它是抽象的接口后面会容易得多;这样,即使在替换了底层数据获取机制之后,我也不需要更新测试。测试只会测试我的接口/存储库/服务,无论你怎么称呼它。
  • 嗯,是的。没有人反对这一点。关键是该抽象实际上应该为您做一些事情。使用 repo,您​​仍在将逻辑注入应用程序代码,而服务层模式会将所有这些抽象出来
  • 同意这一点。抽象不应该只是呼叫转发,而应该做一些更有意义的事情。
【解决方案2】:

克里斯-普拉特帮助我理解的关键突破是,唯一可行的方法是通过所有层使用 DI。例如,在数据层中,我通过 DI 获得了一个 DB 上下文:

public class AlbumRepository
{
  private GalleryDb _ctx;
  public AlbumRepository(GalleryDb ctx)
  {
    _ctx = ctx;
  }
}

在业务层我使用 DI 来获取对数据层的引用:

public class Album
{
  private AlbumRepository _repo;
  public Album(AlbumRepository repo)
  {
    _repo = repo;
  }
}

然后,在 web 层,我使用 DI 来获取对业务层类的引用:

[Route("api/[controller]")]
public class AlbumController : Microsoft.AspNetCore.Mvc.Controller
{
  private Album _album;
  public AlbumController (Album album)
  {
    _album = album;
  }
}

通过在每一层使用 DI,DI 系统能够在需要的地方构造所有必要的类。

这一要求对应用程序的架构产生了深远的影响,我现在意识到,我最初希望调整现有的非 DI 应用程序以开始将 DI 用于 DB 上下文是一项艰巨的任务。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-04
    • 1970-01-01
    • 2018-07-28
    • 2021-08-13
    • 1970-01-01
    • 2022-01-17
    • 2021-08-23
    相关资源
    最近更新 更多