使用 nHydrate 进行控制反转






3.80/5 (3投票s)
使用 nHydrate 代码生成器通过控制反转生成应用程序。
引言
nHydrate 生成器是一个通用代码生成器,可让您构建相当健壮的 API 框架。它基于模型驱动的开发方法。模型定义了 API 的所有方面。您可以控制您的数据访问层 (DAL)、数据传输层 (DTO) 以及应用程序框架的其他方面。新增的功能是将持久化框架与客户端 API 解耦。此实现遵循常见的控制反转 (IoC) 模式。完整的 nHydrate 安装程序可在 http://nhydrate.codeplex.com 找到。
背景
软件现在运行在许多不同的环境中。我的意思是,相同的核心应用程序或其部分可能运行在网站、Windows 应用程序、移动设备等中。所有这些环境都有其自身的特点、需求、内存限制等。没有一个通用的持久化框架能够满足所有执行环境的需求。通过使用 IoC 模式,开发人员可以在所有环境中针对标准 API 实现产品。这对于内存限制更严格的移动设备尤其需要,但其他环境也可以很好地使用这种方法。例如,断开连接的 Windows 应用程序,您不希望直接连接到数据库。
nHydrate 生成框架,在其首次发布时,创建了两个主要项目:安装程序和(数据访问层)DAL。这些项目是从定义的模型生成的,并且可以轻松地用于从生成的对象构建应用程序。根据模型(即数据库结构)的大小,DAL 程序集可能会变得相当大。如果您的数据库包含数百个表,正如有些数据库那样,DAL 程序集肯定不适合放在移动设备上。即使使用标准的 Windows 应用程序或网站,也确实没有必要始终拥有所有功能。nHydrate 框架拥有新的生成器,可以从模型中创建其他项目。DAL 仍然像以前一样创建;但是,现在有可以远程访问它的项目,从而减小了客户端的占用空间。
此更改的另一个优点是能够继续推进产品,而不与特定的持久化技术绑定。最近,围绕云中的服务,特别是 Azure® 数据服务,有很多宣传。尽管这些产品可能不适合今天开发的许多应用程序,但开发人员希望编写能够适应技术转变的产品,当技术转变对他们有利时。通过使用 IoC 在客户端 API 及其实现之间提供特定的区分,开发人员可以确信平台可以在产品适合时适应技术变化。
生成的项目
IoC 框架通过定义使用数据传输对象 (DTO) 与服务器通信的代理来实现。DTO 层是粘合所有其他功能在一起的粘合剂。DTO 层具有保存数据的命名属性对象。只传递原始数据,最大限度地减少网络流量。此外,这些对象没有功能。这些对象上没有定义任何代码。这允许您使用非常小的占用空间来编写针对 DTO 层的 UI 代码。
有趣的部分在于定义通信。您的客户端应用程序,无论是 UI、单元测试等,都是针对 DTO 层编写的。它不引用您框架的任何其他层。通信由代理处理。开箱即用,nHydrate 生成器可以为您创建三个代理:DALProxy、WCFProxy 和 MockProxy。DALProxy 用于直接连接。DTO 层映射到 DAL,数据库的保存和加载执行得非常快。WCFProxy 连接到一个生成的 WCF 服务。这允许您的应用程序部署到互联网上的任何位置,并与公共 WCF 服务通信。所有 DTO 对象都透明地在 WCF 服务之间传输。您的远程客户端可以像本地连接一样与数据存储进行交互。MockProxy 允许您构建用于单元测试的模拟。代理方法论的优点在于,您编写的应用程序不需要了解任何关于代理的信息。相同的代码可以针对任何代理执行。因此,您的应用程序可以本地运行,或通过 WCF 远程运行,或使用模拟进行单元测试。此框架的强大之处在于您可以编写一次,随处运行。您手工编写的应用程序代码不需要为任何代理进行更改。它们只是与数据存储交互的机制。它们是完全可互换的。您可以通过编写任何所需功能的代理来扩展此框架。请注意,模拟代理甚至不与数据存储通信。它会引发事件,您的单元测试编写者可以使用这些事件来填充已知数据场景以进行单元测试。如果需要,您甚至可以为 nHibernate 或任何其他生成框架编写代理。
完整的 IoC 实现所需的项目如下:
- 服务接口
- IoC 扩展
- DTO 层
- 一个或多个代理
前三个在客户端上是必需的,并且非常轻量级。客户端上至少还需要一个代理程序集,它同样是轻量级的。
DAL 配置
直接连接是最简单、最快的数据通信方式。它依赖于 DAL 代理程序集在 DAL 和 DTO 层之间传输信息。下图中所有对象都是生成的,除了 UI。当然,这是您必须编写的应用程序部分。请注意,UI(您的自定义应用程序层)仅引用 DTO 和服务接口程序集。
服务接口程序集知道如何基于配置的代理创建和保存对象。代理本身不需要被引用。当您的应用程序请求创建 DTO 时,它会向服务接口请求一个新对象。反之,当应用程序需要保存一个对象(或对象列表)时,它会请求服务接口程序集来执行此操作。由于代理配置,该程序集知道如何处理请求。
WCF 配置
WCF 配置允许您拥有一个远程客户端,该客户端通过 WCF 服务与数据存储进行通信。客户端配置与直接连接非常相似,只是配置的代理不是 WCF 代理程序集。UI 与所有情况一样与服务接口通信;但是,SI 层现在正在与 WCF 代理通信。该代理知道如何与远程 WCF 服务通信。该服务也是生成的,并且是可扩展的。一旦请求到达服务器端,它就像直接连接一样进行请求,因为它在这一点上是直接连接。WCF 服务使用 DAL 代理与数据存储进行通信,就像本地连接的应用程序一样。
模拟配置
模拟配置允许您将应用程序连接到模拟。这是一个模拟的数据存储,所有数据都通过逻辑加载或保存。模拟代理程序集具有事件,您可以附加这些事件来处理数据存储可能遇到的每种情况。这允许您模拟加载、保存、高级查询等,并返回预设的一组结果。这用于构建单元测试。对于测试驱动开发 (TDD),这是必须的。模拟层允许在没有实际数据库的情况下进行断开连接的数据存储模拟。同样,您的应用程序不需要任何特殊代码来处理此附加层。只需交换配置文件中使用的代理,您就可以进行模拟了。
DAL 和 IoC
DAL(数据访问层)仍然可以用于直接针对数据库编写应用程序。这是一种非常简单的编写应用程序的方式。但是,如果您的应用程序需要 TDD(测试驱动开发)、模拟、控制反转等要求,您将需要选择其他应用程序安排,如多程序集、IoC 应用程序配置。后者更复杂一些,但提供了一种使用单一代码库部署到不同客户端的方法。这真正的卖点是“编写一次,多处部署”。使用此框架,您可以编写一次后端,并将其用于多个客户端。当然,您需要为移动、Web 或 Windows 编写不同的客户端,但后端可以为所有客户端使用和进行单元测试。
实际应用
为了实际使用此配置,我们将构建一个基于 Northwind 数据库的 Web 应用程序。实际上,这是一个修改过的 Northwind 数据库。数据库包含在示例中。它修复了原始数据库的一些问题,并添加了一些新功能,如表继承。
第一步是创建一个 Web 应用程序。我选择使用母版页,以便所有页面具有相同的外观和感觉。接下来,我们设置“web.config”文件。要将此示例与 DALProxy 一起使用,我们需要设置一个连接字符串。DALProxy 基于程序集名称读取此连接。由于我们的程序集分别具有公司名称“Acme”和项目名称“Northwind”,因此条目如下:
<connectionStrings>
<add name="Acme.Northwind"
connectionString="server=.;database=AcmeSales;Integrated Security=SSPI;"/>
</connectionStrings>
为了识别我们将使用的代理程序集,我们需要在“web.config”文件中添加一个“ProxyAssembly
”条目。我们可以随时更改此条目以使用 DAL、WCF、Mock 或自定义实现的代理。
<appSettings>
<add key="ProxyAssembly" value="Acme.Northwind.DALProxy" />
</appSettings>
最后,我们可以在配置文件底部为 WCF 添加一个条目。仅当您使用 WCFProxy 程序集时才需要此项。DAL 或 WCF 代理不需要它。
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IDataService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00"
sendTimeout="00:01:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288"
maxReceivedMessageSize="65536" messageEncoding="Text"
textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192"
maxArrayLength="16384" maxBytesPerRead="4096"
maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None"
proxyCredentialType="None" realm="">
<extendedProtectionPolicy policyEnforcement="Never" />
</transport>
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https:///DataService.svc"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IDataService"
contract="IDataService" name="AcmeNorthwind_IDataService" />
</client>
</system.serviceModel>
通过这些条目添加到“web.config”文件中,您现在可以将代理层切换到 nHydrate 框架附带的三个开箱即用代理中的任何一个。
要构建站点,我们需要为每种对象类型构建一个列表页和一个项(编辑)页。我将构建数据库中的“Region”表。您可以基于此推断出其他页面,因为它们使用相同的模式,只是显示不同的对象。
首先,我们将构建一个带有网格的 Region 列表页。该页面很简单。它有一个项标题、分页控件、数据网格和“添加”按钮。网格将以分页格式显示区域数据。分页控件将显示当前页和每页记录数。“添加”按钮用于向集合中添加新项。
网格仅在列中显示区域 ID 和名称。它还在最右边的列中包含编辑和删除链接按钮。这些允许用户直接从网格中编辑和删除项。还有一个“Territories”链接。点击后,此链接将加载按指定区域过滤的 Territory 列表页,该区域由 Region 和 Territory 之间的一对多关系定义。此模式可以扩展以遍历模型中的任何关系。
填充区域列表的代码非常简单。您声明一个区域对象的通用列表,然后调用扩展方法来加载它。
List<RegionDTO> regionCollection = new List<RegionDTO>();
PagedQueryResults<RegionDTO> results =
regionCollection.RunSelect(this.PagingControl1.PageIndex,
this.PagingControl1.RecordsPerPage,
true, RegionDTO.FieldNameConstants.RegionId.ToString(),
"");
this.PagingControl1.ItemCount = results.TotalRecords;
grdItem.DataSource = regionCollection; grdItem.DataBind();
当使用 DAL 时,您会调用一个静态方法,它会返回 `RegionCollection` 对象。但是,由于我们使用的是 DTO 层上的扩展方法,因此不存在静态方法,所以我们需要创建一个我们想要的对象的实例或列表,并调用其上的扩展方法。
网格将使用那一点点代码正常加载。所有加载都在生成的程序集中执行,这些程序集定义了在“web.config”文件中设置的 IoC 功能。唯一需要做的就是处理网格的绑定事件以设置链接按钮。此事件用于设置到编辑页面的链接,该链接仅将区域键传递到 URL。它还定义了一个指向 Territory 列表页的链接,该链接使用指定的 Region 键来加载特定列表。删除链接略有不同,因为该链接附加了一个确认对话框,并且分配了一个命令,该命令在 `RowCommand` 事件中处理,以实际从数据库中删除该项。
private void grdItem_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
RegionDTO region = (RegionDTO)e.Row.DataItem;
HyperLink linkEdit = (HyperLink)e.Row.FindControl("linkEdit");
HyperLink linkTerritories =
(HyperLink)e.Row.FindControl("linkTerritories");
LinkButton linkDelete =
(LinkButton)e.Row.FindControl("linkDelete");
linkEdit.NavigateUrl = "/RegionItem.aspx?id=" + region.RegionId;
linkTerritories.NavigateUrl =
"/TerritoryList.aspx?regionid=" + region.RegionId;
linkDelete.Attributes.Add("onclick",
"return confirm('Do you wish to delete this item?');");
linkDelete.CommandArgument = region.RegionId.ToString();
linkDelete.CommandName = "GoDelete";
}
}
删除代码也非常简单。由于 Web 是无状态的,它会重新加载 Region 项并调用其 `Delete` 扩展方法。所有复杂逻辑和持久化都由 IoC 层及其对 DAL 层的底层使用来处理。
if (e.CommandName == "GoDelete")
{
int id = int.Parse((string)e.CommandArgument);
RegionDTO region = new RegionDTO();
region.SelectByPrimaryKey(id);
region.Delete();
}
摘要
nHydrate 的新 IoC 层功能确实能够强制执行我们在编程中追求的关注点分离原则。最好的部分是,您的绝大多数代码都是生成的并且是模型驱动的。确实没有理由再编写连接层和数据库到 UI 的“管道”代码了。生成的框架可用于立即开始编写 UI。它与您一起成长。当您更改模型并定义新表、字段、属性、关系等时,只需重新生成,整个框架就会为您重构。所有这些都经过编译时检查。如果您删除了一个字段或其他对象,您手工编写的应用程序中的所有引用都会在编译时产生错误。没有理由去搜索所有损坏的引用;编译器会为您完成。
模型驱动工程 (MDE)、测试驱动开发 (TDD)、控制反转 (IoC)、模拟和关注点分离 (SoC) 是 nHydrate 框架能够更快地构建更高质量软件的原因。
构思、建模、生成!