使用依赖关系注入在ASP.NET Core中编写干净代码

发表于:2016-5-23 09:25

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Steve Smith    来源:51Testing软件测试网采编

  ASP.NET Core 1.0 是 ASP.NET 的完全重新编写,这个新框架的主要目标之一就是更多的模块化设计。即,应用应该能够仅利用其所需的框架部分,方法是框架在它们请求时提供依赖关系。此外,使用 ASP.NET Core 构建应用的开发人员应该能够利用这一相同功能保持其应用松散耦合和模块化。借助 ASP.NET MVC,ASP.NET 团队极大地提高了框架的支持以便编写松散耦合代码,但仍非常容易落入紧密耦合的陷阱,尤其是在控制器类中。
  紧密耦合
  紧密耦合适用于演示软件。如果你看一下说明如何构建 ASP.NET MVC(版本 3 到 5)站点的典型示例应用程序,你很可能会找到如下所示代码(从 NerdDinner MVC 4 示例的 DinnersController 类):
private NerdDinnerContext db = new NerdDinnerContext();
private const int PageSize = 25;
public ActionResult Index(int? page)
{
int pageIndex = page ?? 1;
var dinners = db.Dinners
.Where(d => d.EventDate >= DateTime.Now).OrderBy(d => d.EventDate);
return View(dinners.ToPagedList(pageIndex, PageSize));
}
  这类代码难以进行单元测试,因为 NerdDinnerContext 作为类的构造的一部分而创建,并需要一个要连接的数据库。毫无疑问,这种演示应用程序通常不包括任何单元测试。但是,你的应用程序可能会从一些单元测试受益,即使你不是测试驱动开发,但最好是编写代码以便进行测试。
  另外,此代码违反了切勿重复 (DRY) 原则,因为每个执行任何数据访问的控制器类都在其中具有相同的代码以创建 Entity Framework (EF) 数据库上下文。这使未来更改的成本更高且更容易出错,尤其是随着时间的推移应用程序不断增长。
  在查看代码以评估其耦合度时,请记住这句话“新关键字就是粘附”。 也就是说,在看到“新”关键字实例化类的任何地方,应意识到你正在将你的实现粘贴到该特定实现代码。依赖关系注入原则 (bit.ly/DI-Principle) 指出: “抽象不应依赖于详细信息,详细信息应依赖于抽象。” 在本示例中,控制器如何将数据整合在一起以传入视图的详细信息依赖于如何获取此数据(即 EF)的详细信息。
  除了新关键字外,“墨守成规”是造成紧密耦合的另一个原因,使得应用程序更加难以进行测试和维护。在上述示例中,执行计算机的系统时钟上存在一个依赖关系,其形式为对 DateTime.Now 的调用。此耦合度可能导致难以创建一组用于某些单元测试的测试 Dinners,因为其 EventDate 属性需要相对于当前时钟的设置进行设置。有多种方法可以将耦合度从此方法中删除,其中最简单的方法就是让返回 Dinners 的任何新抽象来处理这一问题,因此,这不再是此方法的一部分。
  此外,我赋予此值一个参数,因此方法可能会在提供的 DateTime 参数后返回所有 Dinners,而不是始终使用 DateTime.Now。最后,我创建当前时间的抽象,并通过该抽象引用当前时间。如果应用程序经常引用 DateTime.Now,这将是一个不错的方法。(另外还应该注意,由于这些 Dinners 可能会出现在不同时区中,所以在实际应用中 DateTimeOffset 类型可能是一个更好的选择)。
  诚实
  这类代码在可维护性方面的另一个问题在于它对它的协作者并不诚实。你应避免编写可在无效状态中实例化的类,因为这些类经常会成为错误的来源。因此,类为了执行其任务所需的一切都应通过其构造函数提供。如显式依赖关系原则 (bit.ly/ED-Principle) 所述:“方法和类应显式要求正常工作所需的任何协作对象。”
  DinnersController 类只有一个默认构造函数,这意味着它应该不需要任何协作者就能正常工作。但是如果你测试这个类会发生什么情况? 如果你从引用 MVC 项目的新控制台应用程序运行此类,这个代码会执行哪些操作?
  var controller = new DinnersController();
  var result = controller.Index(1);
  在这种情况下,代码执行的第一个操作就是尝试实例化 EF 上下文。代码引发 InvalidOperationException: “应用程序配置文件中找不到名为‘NerdDinnerContext’的连接字符串。” 我被骗了! 该类需要比其构造函数所声明的更多内容才能正常工作。 如果类需要一种访问 Dinner 实例集合的方法,则应通过其构造函数进行请求(或者,作为其方法上的参数)。
  依赖关系注入
  依赖关系注入 (DI) 引用将某个类或方法的依赖关系作为参数传递的技术,而不是通过新的或静态调用对这些关系进行硬编码。这是 .NET 开发中一种越来越常见的技术,因为该技术向使用此技术的应用程序提供分离。ASP.NET 的早期版本没有利用 DI,尽管 ASP.NET MVC 和 Web API 在支持 DI 的问题上取得了一些进展,但都没有生成对产品的完全支持,包括用于管理依赖关系及其对象生命周期的容器。借助 ASP.NET Core 1.0,DI 不仅得到现成支持,还被产品本身广泛使用。
  ASP.NET Core 不仅支持 DI,它还包括一个 DI 容器—又称为控制反转 (IoC) 或服务容器。每个 ASP.NET Core 应用使用 Startup 类的 ConfigureServices 方法中的此容器配置其依赖关系。此容器提供所需的基本支持,但如果需要,可将其替换为自定义实现。而且,EF Core 还提供对 DI 的内置支持,因此,在 ASP.NET Core 应用程序中配置 DI 就像调用扩展方法一样简单。我为本文创建了 NerdDinner 衍生,称为 GeekDinner。配置 EF Core,如此处所示:
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<GeekDinnerDbContext>(options =>
options.UseSqlServer(ConnectionString));
services.AddMvc();
}
  配置好后,即可非常轻松地使用 DI 从诸如 DinnersController 的控制器类请求 GeekDinnerDbContext 的实例。
public class DinnersController : Controller
{
private readonly GeekDinnerDbContext _dbContext;
public DinnersController(GeekDinnerDbContext dbContext)
{
_dbContext = dbContext;
}
public IActionResult Index()
{
return View(_dbContext.Dinners.ToList());
}
}
31/3123>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号