Service StationWeb Service Software Factory Modeling Edition

Gerardo de Geest and Gerben van Loon

代码下载位置: ServiceStation2008_Launch.exe (199 KB)
Browse the Code Online
Web 服务软件工厂:建模版本也称为“服务工厂”,它是一个资源集合,这些资源可帮助您以简单有效的方式在 Windows® Communication Foundation (WCF) 和 ASMX 中建模和构建 Web 服务。与先前版本的服务工厂相比,最新版本使用的是模型,而先前版本使用的是基于 Guidance Automation Toolkit (GAT) 的向导。服务工厂现在允许您通过创建三种不同的模型来构建 Web 服务:数据约定模型、服务约定模型和宿主模型。我们将首先讨论这三种模型,然后再为您介绍如何自定义服务工厂。您可以在“在线服务工厂资源”侧栏中列出的网站内找到更多信息。
使用 GAT 向导生成代码的主要缺点是在对生成的代码进行必要更改时会比较复杂。假设您想要重命名生成服务中的某个操作,您必须保存自定义代码,然后重做要在其中使用新名称或者在生成的代码中重命名操作的整个向导。重做向导是一项耗时的工作;因为向导没有记忆,您必须重新指定全部内容。更改由向导生成的代码也不是理想的做法,因为您要返回去重新处理许多细节问题,而向导的目的恰恰是要避免这一点。模型为此问题提供了一种不错的解决方案,与向导相比,它可以记住您输入的内容。
构建集成到 Visual Studio® 中的建模语言在过去是一件很困难的事情,但是附带有 Visual Studio SDK 的 Domain-Specific Language (DSL) Tools 使此项工作变得非常容易。现在服务工厂附带了使用 DSL Tools 构建的三种模型。请注意,没有针对数据访问和服务安全的模型。先前版本服务工厂中的“数据访问指导包”现在成为一个单独的工厂(称为存储库工厂),而“安全性指导包”也成为服务工厂社区站点单独的一个可用数据包。
新版本的服务工厂还为如何构建自己的软件工厂提供了良好示例,因为它使用并组合了所有可用工具来构建一个:
  • 用来生成项目并提供上下文菜单的向导 GAT
  • 用于三种不同模型的 DSL Tools
  • 支持交叉模型引用的 Designer Integration Service (DIS)
服务工厂源代码还包含一些有趣的库,这些库拥有目前工厂工具没有的一些功能。如果要构建自己的工厂,则重用以下各项内容可能会让您受益匪浅:
  • 代码生成库,允许您在单独的文件中生成代码
  • 交叉模型验证库,可协助您验证交叉模型引用
  • Visual Studio 模拟对象库,可帮助您方便地对工厂进行单元测试
正如我们曾提到的,服务工厂由三种模型构成。在数据约定模型中,定义了 Web 服务的数据约定。在服务约定模型中,定义了服务、服务约定、操作以及消息。最后,宿主模型被用来建模服务端点和客户端代理。
为了介绍工厂,我们将创建一个从 Peedy 的比萨饼店订购比萨饼的 Web 服务。此 Web 服务可以接受比萨饼订单,并允许您选购大小和浇头不同的比萨饼。提交订单后,会将订单价格返回给客户作为确认。

数据约定模型
在数据约定模型中,可同时建模不同种类的数据。除了定义基本类型作为约定元素外,您还可以定义集合、枚举以及错误约定。当服务中出现某些错误时可以返回错误约定。
在 Peedy 的比萨饼店的数据约定模型中,我们将 PizzaOrder 定义为客户要订购的比萨饼的集合。每个比萨饼都有大小和浇头。这是使用枚举来建模的。此外,我们还定义了错误约定 OutOfOrder,当订单上的比萨饼缺货时即会引发此约定,表明 Peedy 的比萨饼店因缺少某部分的浇头而无法交货。即使出现错误约定时也可以返回值。例如,本例中我们在任何情况下都要向用户显示订单价格。图 1 显示了完整的数据约定模型。
 
图 1 Peedy 比萨饼店的数据约定模型 (单击该图像获得较大视图)

服务约定模型
数据约定在服务约定模型中使用。在本例中,要为 Peedy 比萨饼店 Web 服务的服务约定和操作建模。模型(请参见图 2)实际上非常简单,因为我们只需要一个订购比萨饼的操作。

图 2 Peedy 的比萨饼店的服务约定模型 (单击该图像获得较大视图)
在服务约定模型中,每个操作可以最多有两个消息约定与之关联:一个用于请求消息,另一个用于响应消息。您可以通过右键单击某个消息约定来向其中添加部件。部件可以是基元类型(如 System.Decimal),也可以是数据约定(在数据约定模型中定义)。DSL 模型元素选择器(如图 3 所示)显示了项目中可供使用的所有数据约定。
图 3 选择数据约定 

ASMX 还是 WCF?
在转到宿主模型之前,我们必须首先创建解决方案结构。它是一个项目组,在其中将会生成 Web 服务的代码。我们必须在此时选择我们的实现技术。必须注意,在服务工厂中已经提供了两种技术(ASMX 和 WCF),您也可以使用自己的服务技术方便地对服务工厂进行扩展。我们的演示将使用 WCF。为了进行此选择,我们只需右键单击解决方案,图 4 所示的选项就会显示出来。我们选择菜单底部的“WCF Implementation Projects”(WCF 实现项目),服务工厂将创建数个项目,从模型生成的代码将会存储在其中。
图 4 创建 WCF 解决方案结构 
图 5 显示了生成的解决方案结构。注意,其中已生成了多种项目—这可以使代码更易于管理。在讨论项目映射表的时候,我们还会更多涉及此解决方案结构。
图 5 Peedy 比萨饼店的解决方案结构 
我们还需要指明在数据约定模型和服务约定模型中我们是想使用 ASMX 还是想使用 WCF。其中每个模型都有一个名为 Implementation Technology 的属性,可以将其设置为 ASMX、WCF 或您自定义的实现技术。设置了此属性后,模型中的许多实体都会获得一些特定于所选技术的新属性(请参见图 6a)。如您在图 6b 中所见,在选择了 WCF 作为实现技术后,WCF 设置类别被添加到了属性窗口中。这可以让我们选择 PizzaOrder 集合的类型。
图 6a 选择技术前 
图 6b 选择技术后 

宿主模型
宿主模型被用来建模 Web 服务端点和客户端代理。此模型看上去与我们先前讨论的其他两种模型截然不同。此时,您可以通过在主机资源管理器中右键单击元素开始建模,而不是简单地将元素拖放到设计图面上。通过右键单击宿主模型元素,您可以添加新的主机和新的客户端,如图 7 所示。
图 7 向宿主模型中添加客户端和主机 
在添加了 PizzaHost 和 PizzaClient 后,即可为它们选择实现技术。我们已选择将它们均设置为 WCF。还必须为二者选择一个实现项目。幸运的是,服务工厂已经为我们生成了这些内容。正如您所见,解决方案结构中的 Tests 文件夹(如图 5 所示)包含了项目 PizzaService.Host 和 PizzaService.Client。它们可以被用作实现项目。
我们通过右键单击 PizzaHost 创建一个新的 ServiceReference。对于这个新的 ServiceReference,我们需要选择一个服务实现—它是对我们服务模型的一个引用。我们可以采用与在图 2 中选择数据约定同样的方式来选择实现。
对于创建的每个 ServiceReference,您都可以定义不同的端点。我们已经创建了一个名为 PizzaEndpoint 的端点,并已将其配置为使用 wsHttpBinding。
由于我们已经有了一个端点,因此我们可以开始为客户端来创建代理。为了添加新代理,我们在主机资源管理器中右键单击 PizzaClient。利用代理属性窗口中的组合框,我们可以选择端点。图 8 显示了完整的宿主模型。
图 8 完整的比萨饼服务宿主模型 

生成代码
现在比萨饼订购服务所需的所有模型均已定义完毕,我们已准备好开始生成服务。为此,我们首先必须填充每个模型的项目映射表字段(这可以在各个模型的属性窗口中进行定义)。选择此属性时,会弹出一个选择窗口。在本例中,只有一个选项:PizzaService。这是因为所有角色都已在创建解决方案结构时定义完毕。这些角色可以在文件 projectmapping.xml 中找到,此文件也是我们解决方案结构的一部分。在此文件中,角色可以与项目建立关联。
接下来要实际生成代码。右键单击数据约定模型,将会出现“Generate Code”(生成代码)选项。选择此选项将在 PizzaService.DataContracts 项目中生成数据约定。对服务约定模型执行同样的操作,该模型将会在 PizzaService.MessageContracts、PizzaService.ServiceContracts 以及 PizzaService.ServiceImplementation 项目中生成代码。
宿主模型的代码生成分为两个步骤:一次是生成主机代码,一次是生成客户端代码。要生成主机代码,我们单击 PizzaService ServiceReference,Visual Studio 设计器会显示一个名为“Validate Model”(验证模型)的按钮。我们单击此按钮后,会正确验证模型,然后出现“Generate Service”(生成服务)按钮。单击此按钮,正如您猜想的那样,这将在 PizzaService.Host 项目中生成代码。
现在为了生成客户端,我们首先必须运行主机,因此我们将编译 PizzaService.Host 项目并在 Web 浏览器中运行它。接下来我们在宿主模型中单击 PizzaProxy,然后单击生成代码。这将打开一个向导(如图 9 所示),利用它我们可以对客户端进行配置。在服务工厂自动填充服务地址的时候,我们可以继续操作,定义主机的安全设置。例如,如果要求连接到此服务时必须提供 X.509 证书,则我们可以使用此向导将其添加到客户端。
 
图 9 用于生成客户端的向导 (单击该图像获得较大视图)
此时,服务已准备就绪,您可以开始使用或在 IIS 中进行发布。您可以将此服务的业务逻辑和实体添加到 PizzaService.BusinessEntities 和 PizzaService.BusinessLogic 项目中。现在唯一需要做的是在 PizzaService.ServiceImplementation 项目中新建一个名为 PizzaServicePeedy 的局部类。它可以用来向生成的服务实现中添加业务逻辑。遗憾的是,如果我们直接编辑生成的代码,我们的逻辑会在下次生成代码时被覆盖。但是如果使用局部类则不会出现这种情况,因为生成器不会触动我们自行创建的文件。
为了演示这一点,我们在 PizzaService.BusinessLogic 项目中定义了一个名为 PriceCalculator 的类和一个名为 CalculatePrice 的方法。(必须注意,这并不是推荐的做法—如果业务逻辑不“知道”有关服务的任何内容会更好一些,这也正是引入转换层的原因。为了支持此流程,服务工厂内置了一个转换器,用来帮助用户将数据约定转换为业务实体。您有可能并不希望在生产环境中采用这种方法,我们在这里使用此项技术只是为说明一个基本点)。此方法以 PizzaOrder 数据约定作为参数并返回订单的价格。我们只需在 MessageContract 中封装此价格然后返回 MessageContract 即可。为此,我们将在 PizzaService.ServiceImplementation 项目中创建一个名为 PizzaServicePeedy 的局部类,它将覆盖服务工厂生成的 OrderPizza 方法。此局部类如图 10 所示。
为了测试我们的服务,我们将启动 PizzaService.Host 项目。在 PizzaService.Client 项目中,我们接下来可以定义下列方法来调用服务:
static void Main()
{
  PizzaOrder order 
= new PizzaOrder();
  Pizza pizza 
= new Pizza();
  pizza.PizzaSize
= PizzaSize.Large;
  pizza.Topping 
= Topping.Hawaii;
  order.Add(pizza);
  PizzaContractClient proxy 
= new PizzaContractClient();
  Console.WriteLine(proxy.OrderPizza(order));
  proxy.Close();
}
在程序运行时,在 Visual Studio 的输出窗口打印出数字 7,因为这是 Peedy 比萨饼店提供的所有大型比萨饼的价格。

自定义服务工厂
在构建服务时,服务工厂可以自动为您完成许多工作。但是,如果您需要略做更改,或者您需要将这些服务与现有的应用程序框架相集成该怎么办?不必担心,在 CodePlex 站点 (www.codeplex.com/servicefactory) 提供了服务工厂的完整源代码,这意味着您可以自定义工厂本身。
当您查看服务工厂源代码时,您会发现它由下列几部分组成:
特定于域的语言 具体来说,其中包括数据约定、宿主设计器以及服务约定。这些项目指定了建模语言的构造和查看方式。
扩展器 共有六种扩展器项目。这些项目指定了 DSL 支持特定技术(ASMX 和 WCF)所需的额外属性。
服务工厂指导包 其中包含解决方案模板和自定义的上下文菜单。
其中包含代码生成框架、验证框架、代码生成策略以及验证规则。
模型项目 这一自定义的项目包含各种模型。
在本次讨论中,我们把自定义内容分为两类:需要重新构建源代码的自定义和不需要重新构建源代码的自定义(扩展)。具体内容如图 11 所示。
通常,应尽量避免需要您重新构建工厂和新建安装程序的自定义。除了会变得更加复杂和带来潜在的出错可能外,当迁移到下一版本的工厂或与未来的 Microsoft 产品相集成时,这些类型的自定义还会增加出现问题的可能性。也就是说,服务工厂源代码的确包括一个安装项目,它可以在重新构建流程中为您提供帮助。此项目包括生成 WIX (Windows Installer XML) 的代码生成模板,有助于更简单快捷地完成任务。
接下来,我们将介绍如何向现有的 WCF 扩展器中添加属性以及如何更改模板以生成新属性的额外代码。对其他扩展点的研究超出了本专栏的范围,详细信息您可以查阅“服务工厂扩展动手体验”,网址为 codeplex.com/servicefactory
请注意,虽然我们要修改工厂的源代码,但是我们只更改一个动态加载的单个程序集。因此我们无需重新构建工厂。

Enterprise Library 异常屏蔽
在服务中,通常要避免向客户端发送未知的异常。Enterprise Library (EntLib) 3.1 中的“异常处理应用程序块”可以为此提供帮助,它可以将配置好的异常屏蔽策略应用到 WCF 服务。这需要服务具有 ExceptionShielding 属性,并使用异常处理块中的异常屏蔽策略名称作为参数。
在服务工厂中会生成整个服务类,因此我们可以手动设置生成代码的属性,但是每次重新生成代码时都会将其覆盖。针对此问题的解决方案是使用 EntLib 异常屏蔽属性的一个设置来扩展服务模型。请注意,EntLib 异常屏蔽仅适用于 WCF 服务。向扩展器添加新属性同样不需要我们重新构建整个工厂。

构建扩展
我们打算修改工厂的源代码,但是我们只更改单个程序集:WCF 服务约定扩展器。扩展器程序集是动态加载的,因此我们只需构建更改后的扩展器即可。为构建此扩展,我们假定您已使用二进制安装程序安装了服务工厂。您还需要安装源代码,因为我们要对它的其中一部分进行更改。您可以部分借鉴服务工厂安装说明中的“源代码安装程序安装说明”进行操作。必须跳过重新构建整个解决方案或注册指导包的步骤,因为这些步骤会更改原始的二进制已安装版本。
我们要向 WCF 服务约定扩展器中添加我们的新属性,此扩展器可在服务工厂源代码的 ServiceFactory.Extenders.ServiceContract.Wcf 中找到。只需打开这一单独项目即可,而不必打开整个解决方案,因为我们只需要对此项目进行更改。异常屏蔽设置是 WCF 服务的一个属性,因此我们可以向 WCFService 类添加一个新属性(如图 12 所示)。
在使用 ExceptionShielding 属性时,我们还需要对 EntLib DLL 的额外引用。我们可以向 ServiceLink 类添加额外的程序集引用,生成的项目可以通过这种方式自动获得这些引用:
[AssemblyReference("Microsoft.Practices.EnterpriseLibrary" +
  
".ExceptionHandling, Version=3.1.0.0, Culture=neutral," + 
  
"PublicKeyToken=b03f5f7f11d50a3a")]

[AssemblyReference(
"Microsoft.Practices.EnterpriseLibrary" +
  
".ExceptionHandling.WCF, Version=3.1.0.0, Culture=neutral," +
  
"PublicKeyToken=b03f5f7f11d50a3a")]
现在我们必须更改代码生成模板以生成新属性的额外代码。用来生成 WCF 代码的模板位于服务工厂安装文件夹下的 Guidance Package\TextTemplates\WCF\CS 子文件夹中。
我们打算向 ServiceImplementation.tt 文件中添加额外代码。首先保存一份原始文件的副本,以防您将来想改回原始模板。我们将在 ServiceImplementation.tt 文件的底部添加下列方法,仅当填充某个值后此方法才会返回 EntLib 异常策略:
private string
  GetEntLibExceptionShieldingPolicy(
    Service service)
{
  WCFService wfcService 
=
    GetObjectExtender
<WCFService>(service);
    
  
if(string.IsNullOrEmpty(
    wfcService.EntLibWCFExceptionPolicy))
  {
    
return string.Empty;
  }
  
else
  {
    
return "[ExceptionShielding(\"" + 
      wfcService.EntLibWCFExceptionPolicy +
      
"\")]";
  }        
}
要完成扩展,我们还需要在基础类定义的正上方为 EntLib 命名空间插入一个使用语句,以及一个对先前创建的方法的调用,如下所示:
using System;
using WCF = global::System.ServiceModel;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF;

namespace <#=CurrentExtender.ArtifactLink.Namespace#>
{    
  
/// <summary>
  
/// Service Class - <#= CurrentElement.Name #>
  
/// </summary>
  [WCF::ServiceBehavior(Name = "<#= CurrentElement.Name #>"
  
<#= GetEntLibExceptionShieldingPolicy(CurrentElement) #>
  
public abstract class <#= CurrentElement.Name #>Base

}

使用扩展
要使用扩展,我们只需重新构建更改后的 ServiceFactory.Extenders.ServiceContract.Wcf 项目并确保安装的服务工厂动态加载了我们更改后的 WCF 扩展器程序集即可。但是我们必须提醒您:此时不要重新构建服务工厂的整个源代码;这将更改已安装的二进制版本。只重新构建扩展器项目即可。
再次重申,保存一份原始扩展器的副本是一种明智的做法。(原始 WCF 扩展器 Microsoft.Practices.ServiceFactory.Extenders.ServiceContract.Wcf.dll 可在服务工厂安装文件夹下的 Guidance Package\Lib 子文件夹中找到)。现在我们关闭 Visual Studio 的所有实例并使用我们自己的版本来覆盖扩展器 DLL。接下来当我们重新启动 Visual Studio 并创建一个使用 WCF 扩展的新服务时,我们的新属性会在选择服务时弹出(请参见图 13)。
 
图 13 服务的新 EntLib 属性 (单击该图像获得较大视图)
当我们填充异常策略及生成代码时,会将正确的 EntLib 程序集添加到生成的项目中。此外,该使用语句和 EntLib 属性还将添加到服务实现类中。当然,现在我们添加的属性在重新生成时并不会丢失,因为我们更改的是代码生成模板而不是生成的代码。新的服务代码类似于下例所示:
using System;
using WCF = global::System.ServiceModel;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF;

namespace WCFService1.ServiceImplementation
{    
  
/// <summary>
  
/// Service Class - Service1
  
/// </summary>
  [WCF::ServiceBehavior(Name = "Service1"
  [ExceptionShielding(
"MyExceptionPolicy")]
  
public abstract class Service1Base : 
    WCFService1.ServiceContracts.IServiceContract1
  { 
    
  }
}
为了使 EntLib 异常屏蔽真正发挥作用,我们还必须使用异常屏蔽策略对异常处理应用程序块进行配置。您可以参考 EntLib 文档来了解有关创建异常处理策略的详细信息。
对于此扩展,我们向现有的 WCF 扩展器添加了一个属性、告知了扩展器我们需要额外的程序集引用、更改了模板以生成新属性的正确代码,此外还让服务工厂使用了我们更改后的 WCF 扩展器。后续的策略属性扩展点会使这些种类的扩展更为容易,因为它允许您在独立的程序集中指定新属性。这样,您就不必对现有的扩展器进行更改了。

请将您想询问的问题和提出的意见发送至 sstation@microsoft.com.


Gerardo de Geest 是 Avanade 的一名解决方案开发人员,其专长是软件工厂和特定于域的语言。他一直致力于研究特定于域的语言的演变,尤其是在“Microsoft Web 服务软件工厂建模版本”中的演变过程。可以通过 gerardod@avanade.com 与 Gerardo 取得联系。

Gerben van Loon 是 Avanade 的一名解决方案开发人员,其专长是软件工厂和特定于域的语言。他已开始在早期的 CTP 中使用 Microsoft DSL 工具。可以通过 gerbenva@avanade.com 与 Gerben 取得联系。

原文出处:http://msdn.microsoft.com/zh-cn/magazine/cc164250.aspx
原文地址:https://www.cnblogs.com/Bruce_H21/p/1243097.html