Windows Communication Foundation 4 开发人员简介
Aaron Skonnard, Pluralsight
原始:2009 年 11 月
更新为 RTM:2010 年 4 月
概述
.NET 4 附带了一些引人注目的新功能,并欢迎在 Windows Communication Foundation (WCF) 方面进行改进。 这些 WCF 增强功能主要侧重于简化开发人员体验、启用更多通信方案,并通过使“工作流服务”成为一流的公民来提供与 Windows Workflow Foundation (WF) 的丰富集成。
好消息是,WCF 4 的大部分更改都侧重于简化当今的常见方案,并使新的通信方案和开发样式成为可能。 因此,将现有 WCF 解决方案迁移到 .NET 4 在迁移方面将相当无缝。 然后,只需确定要在解决方案中利用哪些 WCF 4 功能即可。 本文的其余部分介绍每个新的 WCF 4 功能区域,并演示它们的工作原理。
WCF 4 中的新增功能
WCF 4 附带了各种特定功能,但图 1 描述了以下文章将重点介绍的main功能领域。 这些功能区域汇总了 WCF 4 中的大部分新增功能,并突出显示了此版本的 .NET Framework 提供的顶级机会。
图 1:WCF 4 功能区域
功能区 | 说明 |
---|---|
简化配置 |
通过支持默认终结点、绑定和行为配置来简化 WCF 配置部分。 这些更改使托管无配置服务成为可能,从而大大简化了最常见的 WCF 方案的开发人员体验。 |
发现 |
针对即席和托管服务发现行为的新框架支持,这些行为符合标准WS-Discovery协议。 |
路由服务 |
可在 WCF 解决方案中使用的可配置路由服务的新框架支持。 提供基于内容的路由、协议桥接和错误处理的功能。 |
REST 改进 |
WCF WebHttp Services 的增强功能与一些简化 REST 服务开发的附加功能和工具。 |
工作流服务 |
丰富的框架支持,用于将 WCF 与 WF 集成,以实现声明性长时间运行的工作流服务。 这一新的编程模型为你提供了两个框架 (WCF & WF) 的最佳功能。 |
完成这些main功能领域的介绍后,我们将简要讨论 .NET 4 附带的一些更高级的较低级别的 WCF 功能,包括改进的类型解析功能、支持与竞争使用者 (“接收上下文”) 的队列、通过字节流编码器支持未包装的二进制数据以及支持基于 ETW 的高性能跟踪。
完成后,你将看到 WCF 4 变得更加易于使用,并为当今一些最常见的方案和开发样式提供更内置的支持。
简化配置
WCF 在 3.x 中提供的统一编程模型既是一种祝福,也是一种诅咒-它简化了为各种不同的通信方案编写服务逻辑,但它也增加了配置方面的复杂性,因为它提供了许多不同的基础通信选项,你必须先了解这些基础通信选项才能开始。
现实情况是,WCF 配置通常成为当今实际使用 WCF 最昂贵的领域,其中大部分复杂性都落在 IT/运营人员身上,他们没有准备好处理它。
鉴于此现实,当你考虑使用 WCF 3.x 的网络复杂性时,可能会合理地得出结论,它比其前身 ASP.NET Web 服务更难使用 (ASMX) 。 使用 ASMX,可以定义 [WebMethod] 操作,运行时会自动为基础通信提供默认配置。 另一方面,迁移到 WCF 3.x 时,开发人员必须充分了解各种 WCF 配置选项,才能定义至少一个终结点。 而且,令人望而生畏的配置选项数量通常会让一些开发人员望而生畏。
为了使整体 WCF 体验像 ASMX 一样简单,WCF 4 附带了一个新的“默认配置”模型,完全不需要任何 WCF 配置。 如果未为特定服务提供任何 WCF 配置,则 WCF 4 运行时会自动使用某些标准终结点和默认绑定/行为配置来配置服务。 这使得 WCF 服务的启动和运行变得更加容易,尤其是对于那些不熟悉各种 WCF 配置选项并且乐于接受默认值的用户,至少是入门。
默认终结点
使用 WCF 3.x 时,如果尝试承载没有任何配置的终结点的服务,ServiceHost 实例将引发异常,通知需要配置至少一个终结点。 对于 WCF 4,情况不再如此,因为运行时会自动为你添加一个或多个“默认终结点”,从而使服务无需任何配置即可使用。
工作原理如下。 当主机应用程序在 ServiceHost 实例上调用 Open 时,它会从应用程序配置文件以及主机应用程序可能已显式配置的任何内容生成内部服务说明,如果配置的终结点数仍为零,则调用 AddDefaultEndpoints,这是 ServiceHost 类上找到的新公共方法。 此方法根据 IIS 方案中的服务基址 (向服务说明添加一个或多个终结点,这是 .svc 地址) 。 由于 方法是公共的,因此还可以在自定义托管方案中直接调用它。
确切地说,AddDefaultEndpoints 的实现为服务实现的每个服务协定为每个基址添加一个默认终结点。 例如,如果服务实现两个服务协定,并且你使用单个基址配置主机,则 AddDefaultEndpoints 将为服务配置两个默认终结点, (每个服务协定) 一个终结点。 但是,如果服务实现两个服务协定,并且主机配置了两个基址 (一个用于 HTTP,一个用于 TCP) ,则 AddDefaultEndpoints 将使用四个默认终结点配置该服务。
让我们看一个完整的示例来说明它的工作原理。 假设你具有以下 WCF 服务协定和以下服务实现:
[ServiceContract]
公共接口 IHello
{
[OperationContract]
void SayHello (字符串名称) ;
}
[ServiceContract]
公共接口 IGoodbye
{
[OperationContract]
void SayGoodbye (字符串名称) ;
}
public 类 GreetingService :IHello、IGoodbye // service 实现这两个协定
{
public void SayHello (字符串名称)
{
Console.WriteLine (“Hello {0}”, name) ;
}
public void SayGoodbye (字符串名称)
{
Console.WriteLine (“Goodbye {0}”, name) ;
}
}
使用 WCF 4,现在可以使用 ServiceHost 托管 GreetingService 服务,而无需进行任何应用程序配置。 在自定义托管方案中使用 ServiceHost 时,需要指定一个或多个要使用的基址。 下面演示如何在控制台应用程序中托管 GreetingService,可以假设没有与此程序关联的app.config文件:
class Program
{
static void Main(string[] args)
{
主机配置了两个基址,一个用于 HTTP,一个用于 TCP
ServiceHost 主机 = new ServiceHost (typeof (GreetingService) ,
new Uri (“https://localhost:8080/greeting") ,
new Uri (“net.tcp://localhost:8081/greeting”) ) ;
主机。Open () ;
foreach (host 中的 ServiceEndpoint se。Description.Endpoints)
Console.WriteLine (“A: {0}, B: {1}, C: {2}”,
硒。地址、se.Binding.Name、se.Contract.Name) ;
Console.WriteLine (“按 <Enter> 停止服务”。) ;
Console.ReadLine();
主机。Close () ;
}
}
此示例使用两个基址配置 ServiceHost:一个用于 HTTP,另一个用于 TCP。 运行此程序时,将看到四个终结点打印到控制台窗口,如图 2 所示。 对于 HTTP 基址,你将获得两个,每个协定一个,两个用于 TCP 基址,每个协定一个。 这全部由 ServiceHost 实例在后台提供。
图 2:控制台窗口中显示的默认终结点
请注意 WCF 如何选择将 BasicHttpBinding 用于默认 HTTP 终结点,将 NetTcpBinding 用于默认 TCP 终结点。 稍后我将介绍如何更改这些默认值。
请记住,仅当服务未配置任何终结点时,才会启动此默认终结点行为。 如果我将控制台应用程序更改为使用至少一个终结点配置服务,则输出中将不再显示任何这些默认终结点。 为了说明这一点,我只需添加以下代码行,用于在构造 ServiceHost 实例后调用 AddServiceEndpoint:
...
ServiceHost 主机 = new ServiceHost (typeof (GreetingService) ,
new Uri (“https://localhost:8080/greeting") ,
new Uri (“net.tcp://localhost:8081/greeting”) ) ;
主机。AddServiceEndpoint (typeof (IHello) , new WSHttpBinding () , “myendpoint”) ;
...
如果在插入此行代码的情况下运行控制台应用程序,你会注意到现在只有一个终结点显示在输出中 - 我们在上面的代码中手动配置的终结点 (请参阅图 3) 。
图 3:使用单个终结点配置主机后的控制台输出
但是,如果仍要添加默认终结点集以及你自己的终结点,则始终可以自行调用 AddDefaultEndpoints。 下面的代码示例演示了如何执行此操作:
...
ServiceHost 主机 = 新的 ServiceHost (typeof (GreetingService) ,
new Uri (“https://localhost:8080/greeting") ,
new Uri (“net.tcp://localhost:8081/greeting”) ) ;
主机。AddServiceEndpoint (typeof (IHello) ,新的 WSHttpBinding () ,“myendpoint”) ;
主机。AddDefaultEndpoints () ;
...
如果使用此更改再次运行控制台应用程序,你将在控制台窗口中看到五个终结点-我手动配置的终结点以及四个默认终结点 (见图 4) 。
图 4:手动调用 AddDefaultEndpoints 后的控制台输出
现在,我们了解了在运行时向服务添加默认终结点的算法和机制,下一个问题是 WCF 如何确定要用于基于特定地址的绑定?
默认协议映射
这个问题的答案很简单。 WCF 定义传输协议方案之间的默认协议映射, (例如 http、net.tcp、net.pipe 等) 和内置 WCF 绑定。 默认协议映射位于 .NET 4 machine.config.comments 文件中,如下所示:
<system.serviceModel>
<protocolMapping>
<add scheme=“http” binding=“basicHttpBinding” bindingConfiguration=“” />
<add scheme=“net.tcp” binding=“netTcpBinding” bindingConfiguration=“”/>
<add scheme=“net.pipe” binding=“netNamedPipeBinding” bindingConfiguration=“”/>
<add scheme=“net.msmq” binding=“netMsmqBinding” bindingConfiguration=“”/>
</protocolMapping>
...
可以通过将此部分添加到machine.config并修改每个协议方案的映射,在计算机级别重写这些映射。 或者,如果只想在应用程序范围内重写它,则可以在应用程序/Web 配置文件中重写此部分。
例如,如果你的组织主要专注于使用 WCF 生成 RESTful 服务,那么将“http”协议方案的默认绑定更改为 WebHttpBinding 可能有意义。 以下示例演示如何在应用程序配置文件中完成此操作:
<configuration>
<system.serviceModel>
<protocolMapping>
<add scheme=“http” binding=“webHttpBinding”/>
</protocolMapping>
</system.serviceModel>
</配置>
现在,如果我使用此app.config重新运行前面显示的控制台应用程序,则两个基于 HTTP 的默认终结点现在将显示它们正在使用 WebHttpBinding (见图 5) 。
图 5:替代默认 HTTP 协议映射后的控制台输出
WCF 通过协议映射表确定要使用的绑定后,它会在配置默认终结点时使用默认绑定配置。 如果对内置绑定默认值不满意,还可以替代特定绑定的默认配置。
默认绑定配置
每个 WCF 绑定都附带一个默认配置,除非特定终结点的主机应用程序显式重写该配置。 使用的每个绑定实例始终附带内置默认值,除非选择通过应用显式绑定配置来替代。
在 WCF 3.x 中,通过定义可以通过 bindingConfiguration 属性应用于终结点定义的命名绑定配置来执行此操作。 正确执行此操作的机制很繁琐且容易出错。 以下配置文件显示了一个典型示例:
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name=“BasicWithMtom” messageEncoding=“Mtom”/>
</basicHttpBinding>
</绑定>
<services>
<service name=“GreetingService”>
<endpoint address=“mtom” binding=“basicHttpBinding”
bindingConfiguration=“BasicWithMtom”
contract=“IHello”/>
</service>
</服务>
</system.serviceModel>
</配置>
在上面的示例中,“BasicWithMtom”绑定配置通过将消息编码更改为 MTOM 来替代 BasicHttpBinding 的默认值。 但是,仅当通过“bindingConfiguration”属性将其应用到特定终结点时,此绑定配置才会生效- 这是开发人员和操作人员通常无法执行的步骤,从而导致配置问题。
使用 WCF 4,现在只需在定义新配置时省略绑定配置名称即可定义默认绑定配置。 然后,WCF 将对使用该绑定的任何终结点使用该默认配置,这些终结点上没有设置显式绑定配置。
例如,如果将以下 app.config 文件添加到前面显示的控制台应用程序,则两个默认 HTTP 终结点将选取此默认的 BasicHttpBinding 配置,这将启用 MTOM:
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<绑定 messageEncoding=“Mtom”/><--请注意,没有名称属性 -->
</basicHttpBinding>
</绑定>
</system.serviceModel>
</配置>
当然,如果希望这些默认绑定配置在计算机上运行的所有服务生效,也可以将这些默认绑定配置添加到 machine.config,或者通过在应用程序配置文件中添加默认绑定配置,在应用程序的基础上定义这些默认绑定配置。
此功能提供了一个简单的机制来定义一组标准的绑定默认值,你可以在所有服务中使用这些默认值,而无需将绑定配置的复杂性强加给其他开发人员或 IT/操作人员。 他们只需选择适当的绑定,并确保托管环境将提供适当的默认配置。
除了默认绑定配置之外,服务和终结点要考虑的另一件事是其默认行为配置。
默认行为配置
WCF 4 还可用于定义服务和终结点的默认行为配置,当您希望在计算机或解决方案中运行的所有服务或终结点之间共享标准默认行为配置时,可以简化操作。
在 WCF 3.x 中,必须定义通过“behaviorConfiguration”属性显式应用于服务和终结点的命名行为配置。 使用 WCF 4,可以通过省略配置定义中的名称来定义默认行为配置。 如果将这些默认行为添加到 machine.config,它们将应用于计算机上托管的所有服务或终结点。 如果将它们添加到 app.config,则它们仅在主机应用程序范围内生效。 下面是一个示例:
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior><--注意没有名称属性 -->
<serviceMetadata httpGetEnabled=“true”/>
</行为>
</serviceBehaviors>
</行为>
</system.serviceModel>
</配置>
此示例为未附带显式行为配置的任何服务启用服务元数据。 如果我们将此默认行为配置添加到前面显示的控制台应用程序的 app.config 文件中,然后再次运行该应用程序,则可以浏览到基 HTTP 地址以检索服务帮助页和服务的 WSDL 定义 (见图 6) 。
图 6:浏览到默认行为配置启用的服务元数据
WCF 4 中的另一项新功能是行为配置现在支持继承模型。 如果应用程序使用与 machine.config 中已定义的行为配置相同的名称定义行为配置,则特定于应用程序的行为配置将与计算机范围的配置合并,从而向派生的复合行为配置添加任何其他行为。
标准终结点
与默认终结点相关的是另一项称为“标准终结点”的新 WCF 4 功能。 可以将标准终结点视为内置于 WCF 4 框架中的常用预配置终结点定义,只需使用即可。 标准终结点定义通常不会更改的“标准”终结点配置,但如果需要,可以立即看到。
图 7 描述了 WCF 4 附带的标准终结点。 它们为一些最常见的 WCF 4 功能和通信方案提供标准终结点定义。 例如,对于 MEX 终结点,始终需要为服务协定指定 IMetadataExchange,并且最有可能选择 HTTP。 因此,WCF 不会强制你始终手动执行此操作,而是为名为“mexEndpoint”的 metdata 交换提供了一个易于使用的标准终结点定义。
图 7:WCF 4 中的标准终结点
标准终结点名称 | 说明 |
---|---|
mexEndpoint |
为 MEX 定义一个标准终结点,其中为服务协定配置了 IMetadataExchange,mexHttpBinding 作为可以更改此) 的默认绑定 (,以及一个空地址。 |
dynamicEndpoint |
定义配置为在 WCF 客户端应用程序中使用 WCF 发现的标准终结点。 使用此标准终结点时,不需要地址,因为在首次调用期间,客户端将查询与指定协定匹配的服务终结点,并自动连接到它。 默认情况下,发现查询通过多播 UDP 发送,但可以指定发现绑定和搜索条件,以便在需要时使用。 |
discoveryEndpoint |
定义为客户端应用程序中的发现操作预先配置的标准终结点。 使用此标准终结点时,用户需要指定地址和绑定。 |
udpDiscoveryEndpoint |
定义一个标准终结点,该终结点在多播地址上使用 UDP 绑定,为客户端应用程序中的发现操作预先配置。 派生自 DiscoveryEndpoint。 |
announcementEndpoint |
定义为发现的公告功能预先配置的标准终结点。 使用此标准终结点时,用户需要指定地址和绑定。 |
udpAnnouncementEndpoint |
定义一个标准终结点,该终结点在多播地址上通过 UDP 绑定预配置公告功能。 此终结点派生自 announcementEndpoint。 |
workflowControlEndpoint |
定义用于控制工作流实例的执行(创建、运行、挂起、终止等)的标准终结点。 |
webHttpEndpoint |
定义配置有 WebHttpBinding 和 WebHttpBehavior 的标准终结点。 使用 公开 REST 服务。 |
webScriptEndpoint |
定义使用 WebHttpBinding 和 WebScriptEnablingBehavior 配置的标准终结点。 使用 公开 Ajax 服务。 |
只需按名称引用这些终结点,即可在自己的服务配置中利用这些标准终结点中的任何一个。 终结点<>元素现在附带了可用于指定标准终结点名称的“kind”属性。 例如,以下示例利用标准“mexEndpoint”定义为 GreetingService 配置 MEX 终结点:
<configuration>
<system.serviceModel>
<services>
<service name=“GreetingService”>
<endpoint kind=“basicHttpBinding” contract=“IHello”/>
<endpoint kind=“mexEndpoint” address=“mex” />
</service>
</服务>
</system.serviceModel>
</配置>
尽管标准终结点可保护你免受大多数配置详细信息 (例如,使用 mexEndpoint 我不必指定绑定或协定) ,但有时你可能仍要使用它们,但需要以略微不同的方式配置标准终结点定义。
如果需要执行此操作,可以使用 <standardEndpoints> 部分并替代标准终结点的终结点配置。 然后,可以在通过 endpointConfiguration 属性定义新 <终结点> 时引用该配置,如下所示:
<configuration>
<system.serviceModel>
<services>
<service name=“GreetingService”>
<endpoint binding=“basicHttpBinding” contract=“IHello”/>
<endpoint kind=“udpDiscoveryEndpoint” endpointConfiguration=“D11”/>
</service>
</服务>
<standardEndpoints>
<udpDiscoveryEndpoint>
<standardEndpoint name=“D11” discoveryVersion=“WSDiscovery11”/>
</udpDiscoveryEndpoint>
</standardEndpoints>
<behaviors>
<serviceBehaviors>
<行为>
<serviceDiscovery/>
<serviceMetadata httpGetEnabled=“true”/>
</行为>
</serviceBehaviors>
</行为>
</system.serviceModel>
</配置>
此示例恰好更改了名为“udpDiscoveryEndpoint”的标准终结点的默认WS-Discovery版本, (我们将在稍后) 详细介绍服务发现。
简化 IIS/ASP.NET 托管
鉴于默认终结点、默认绑定配置和默认行为配置的这些新功能,IIS/ASP.NET 中的托管在 WCF 4 中变得更加容易。 习惯使用 ASMX 服务的 ASP.NET 开发人员现在可以定义本质上同样简单的 WCF 服务。
事实上,检查以下 WCF 服务定义是多么简单:
<-- HelloWorld.svc -->
<%@ ServiceHost Language=“C#” Debug=“true” Service=“HelloWorldService
CodeBehind=“~/App_Code/HelloWorldService.cs” %>
[ServiceContract]
公共类 HelloWorldService
{
[OperationContract]
公共字符串 HelloWorld ()
{
返回“hello, world”;
}
}
这是 WCF 服务定义的最简单形式,因为我们未使用单独的接口定义来定义服务协定,并且所有内容都是在一个文件中定义的,HelloWorld.svc (注意:我不建议使用此方法,只是请注意,可以与 ASMX) 进行比较。 这看起来应该很像典型的 ASMX 服务,主要区别在于在服务类 (使用的属性名称,例如 [WebService] 和 [WebMethod]) 。 移动部件肯定更少。
使用上一部分所述的新 WCF 4 功能,现在无需任何其他 WCF 配置即可浏览到 HelloWorld.svc,WCF 激活逻辑将在后台创建 ServiceHost 实例,并使用单个默认 HTTP 终结点对其进行配置。 如果已向启用服务元数据的machine.config文件添加了默认服务行为,则在浏览到 HelloWorld.svc (请参阅图 8) 时,会看到 WCF 帮助页和 WSDL 定义的链接。
图 8:HelloWorldService 帮助页
如果尚未在计算机范围内启用服务元数据,可以通过将以下默认行为配置添加到 web.config 文件,在 Web 应用程序中启用它:
...
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior><--请注意,没有名称属性 -->
<serviceMetadata httpGetEnabled=“true”/>
</行为>
</serviceBehaviors>
</行为>
</system.serviceModel>
...
还可以按照前面部分中概述的过程更改其他默认设置。 例如,可以更改默认协议映射、添加默认绑定配置或其他默认行为配置。 如果服务实现了多个服务协定,则生成的 ServiceHost 实例将为每个协定配置一个 HTTP 终结点。
例如,假设我们通过此处所示的 .svc 文件托管前面) 中的 GreetingService (:
<-- GreetingService.svc -->
<%@ServiceHost Service=“GreetingService”%>
鉴于 GreetingService 的定义,第一次浏览到 GreetingService.svc 时,WCF 激活逻辑将创建 ServiceHost 实例,并将为 GreetingService 类型添加两个默认 HTTP 终结点, (每个服务协定) 一个。 可以通过浏览到 WSDL 定义来验证这一点,可以在 service> 元素中找到<两个<端口>元素。
总的来说,这些 WCF 配置简化应使 ASP.NET 开发人员能够更轻松地在其 Web 应用程序中启动和运行 WCF 服务,并且使最简单的情况更接近开发人员习惯 ASP.NET Web 服务的体验。
无文件激活
尽管 .svc 文件可以轻松公开 WCF 服务,但更简单的方法是在 Web.config 中定义虚拟激活终结点,从而完全不需要 .svc 文件。
在 WCF 4 中,可以定义映射到 Web.config 中的服务类型的虚拟服务激活终结点。这样,无需将物理 .svc 文件 (“无文件激活”) 即可激活 WCF 服务。 以下示例演示如何配置激活终结点:
<configuration>
<system.serviceModel>
<serviceHostingEnvironment>
<serviceActivations>
<add relativeAddress=“Greeting.svc” service=“GreetingService”/>
</serviceActivations>
</serviceHostingEnvironment>
</system.serviceModel>
</配置>
完成此操作后,现在可以使用相对于 Web 应用程序) 基址的相对路径“Greeting.svc” (激活 GreetingService。 为了说明这一点,我在我的计算机上创建了一个名为“GreetingSite”的 IIS 应用程序,并将其分配给“ASP.NET v4.0”应用程序池,并将其映射到包含上面所示web.config的 GreetingService 项目目录。 现在,我只需浏览到 https://localhost/GreetingSite/Greeting.svc ,而磁盘上实际上没有物理 .svc 文件。 图 9 显示了浏览器中的显示效果。
图 9:无文件激活示例
发现
我们要讨论的下一个主要 WCF 4 功能是服务发现。 在某些面向服务的专用环境中,有些服务的运行时位置是动态的且不断变化的。 例如,假设有不同类型的支持服务的设备不断加入和离开网络作为整体业务解决方案的一部分的环境。 处理这种现实需要客户端动态发现服务终结点的运行时位置。
WS-Discovery 是一种 OASIS 规范,用于定义基于 SOAP 的协议,用于在运行时动态发现服务终结点的位置。 协议允许客户端探测符合某些条件的服务终结点,以便检索合适的候选项列表。 然后,客户端可以从发现的列表中选择特定的终结点,并使用其当前运行时终结点地址。
WS-Discovery定义了两种主要操作模式:即席模式和托管模式。 在即席模式下,客户端通过发送多播消息来探测服务。 框架为此即席模式提供 UDP 多播机制。 与探测匹配的服务直接响应客户端。 为了最大程度地减少对客户端轮询的需求,服务还可以在加入或离开网络时通过向可能正在“侦听”的客户端发送多播消息来“宣布”自己。 临时发现受用于多播消息的协议的限制,对于 UDP,只有在本地子网上侦听的服务才能接收消息。
使用托管服务发现,可以在网络上提供一个发现代理,用于“管理”可发现的服务终结点。 客户端直接与发现代理通信,以根据探测条件查找服务。 发现代理需要一个可以与查询匹配的服务存储库。 如何使用此信息填充代理是实现详细信息。 发现代理可以轻松连接到现有服务存储库,可以使用终结点列表预先配置它们,或者发现代理甚至可以侦听更新其缓存的公告。 在托管模式下,通知可以直接通过发现代理单播给收件人。
.NET 4.0 框架提供实现自己的发现代理所需的基类。 基类将发现协议详细信息抽象化,因此只需专注于希望发现代理包含的逻辑即可。 例如,只需定义发现代理在响应探测消息、公告消息和解析消息时将执行的操作。
WCF 4 提供了WS-Discovery协议的完整实现,并且同时支持即席发现模式和托管发现模式。 下面我们将简要介绍其中每一项。
简单服务发现
启用服务发现的最简单方法是通过即席模式。 WCF 通过提供一些标准发现终结点和服务发现行为,可以轻松地在服务主机应用程序中启用服务发现。 若要配置服务以用于发现,只需添加标准“udpDiscoveryEndpoint”终结点,然后在服务上启用 <serviceDiscovery> 行为。
下面是说明如何执行此操作的完整示例:
<configuration>
<system.serviceModel>
<services>
<service name=“CalculatorService”>
<endpoint binding=“wsHttpBinding” contract=“ICalculatorService” />
<--添加标准 UDP 发现终结点->
<endpoint name=“udpDiscovery” kind=“udpDiscoveryEndpoint”/>
</service>
</服务>
<behaviors>
<serviceBehaviors>
<行为>
<serviceDiscovery/><--启用服务发现行为 -->
</行为>
</serviceBehaviors>
</行为>
</system.serviceModel>
</配置>
通过执行此操作,可以通过本地子网上的 UDP 发现服务。 然后,客户端可以利用运行时WS-Discovery来“发现”正在运行的服务的实际地址。 WCF 4 使客户端可以通过 dynamicEndpoint 标准终结点轻松完成此操作。
只需使用用于连接到服务的现有客户端终结点,删除地址并添加 kind=“dynamicEndpoint”标记即可。
<configuration>
<system.serviceModel>
<客户端>
<端点
name=“calculatorEndpoint”
kind=“dynamicEndpoint”
binding=“wsHttpBinding”
contract=“ICalculatorService”>
</端点>
</客户>
</system.serviceModel>
</配置>
进行第一次服务调用时,客户端将发送多播查询,查找与 ICalculatorService 协定匹配的服务,并尝试连接到其中一个服务。 通过各种设置,可以微调 serach、调整发现绑定并控制发现过程。 还可以使用 DiscoveryClient 类以编程方式完成所有这些操作。
以下示例进一步介绍了如何以编程方式使用 UdpDiscoveryEndpoint 发现 ICalculatorService 终结点,然后调用它:
创建 DiscoveryClient
DiscoveryClient discoveryClient =
new DiscoveryClient (new UdpDiscoveryEndpoint () ) ;
查找指定范围内的 ICalculatorService 终结点
FindCriteria findCriteria = new FindCriteria (typeof (ICalculatorService) ) ;
FindResponse findResponse = discoveryClient.Find (findCriteria) ;
只需选择发现的第一个终结点
EndpointAddress address = findResponse.Endpoints[0]。地址;
创建目标服务客户端
CalculatorServiceClient 客户端 =
new CalculatorServiceClient (“calculatorEndpoint”) ;
连接到发现的服务终结点
客户。Endpoint.Address = address;
Console.WriteLine (“调用 CalculatorService at {0}”, 地址) ;
调用“添加服务”操作。
double result = client。添加 (100,15.99) ;
Console.WriteLine (“Add ({0},{1}) = {2}”, 100, 15.99, result) ;
客户端程序检索到发现的终结点集合后,可以使用其中一个终结点来实际调用目标服务。 图 10 显示了运行上面所示的客户端代码的输出,假设服务也在同时运行。 注意:在此示例中,发现客户端上的查找操作是同步的;discovery 也支持异步查找操作。
图 10:运行发现客户端代码的输出
发现终结点时使用范围
在前面的示例中,客户端只是根据服务协定类型探测了服务。 客户端可以通过在发送发现探测时提供其他范围信息来缩小发现结果范围。 让我们看一个简单的示例,了解如何在发现过程中使用“范围”。
首先,服务需要将一个或多个范围与要发布以用于发现的每个终结点相关联。 WCF 4 附带一个 <endpointDiscovery> 行为,可用于定义可与终结点定义关联的一组范围。 以下示例演示如何将两个范围与服务上定义的单个终结点相关联:
<configuration>
<system.serviceModel>
<services>
<service name=“CalculatorService”
behaviorConfiguration=“calculatorServiceBehavior”>
<endpoint binding=“wsHttpBinding”
contract=“ICalculatorService”
behaviorConfiguration=“ep1Behavior” />
<endpoint name=“udpDiscovery” kind=“udpDiscoveryEndpoint”/>
</service>
</服务>
<behaviors>
<serviceBehaviors>
<behavior name=“calculatorServiceBehavior”>
<serviceDiscovery/>
</行为>
</serviceBehaviors>
<endpointBehaviors>
<behavior name=“ep1Behavior”>
<endpointDiscovery>
<与此终结点行为关联的--范围 -->
<scopes>
<add scope=“http://www.example.org/calculator"/>
<add scope=“ldap:///ou=engineering,o=exampleorg,c=us”/>
</范围>
</endpointDiscovery>
</行为>
</endpointBehaviors>
</行为>
</system.serviceModel>
</配置>
客户端可以在运行时基于特定范围探测服务终结点。 为此,可以将目标范围列表添加到你提供给 Find 操作的 FindCriteria 实例。 以下代码演示如何发现与特定 LDAP 范围匹配的 ICalculatorService 终结点:
...
创建 DiscoveryClient
DiscoveryClient discoveryClient = new DiscoveryClient (“udpDiscoveryEndpoint”) ;
查找指定范围内的 ICalculatorService 终结点
Uri 范围 = 新 URI (“ldap:///ou=engineering,o=exampleorg,c=us”) ;
FindCriteria findCriteria = new FindCriteria (typeof (ICalculatorService) ) ;
findCriteria.Scopes.Add (scope) ;
FindResponse findResponse = discoveryClient.Find (findCriteria) ;
...
利用范围可以微调发现实现,以便客户端可以更轻松地发现他们感兴趣的特定服务终结点。 发现还允许进一步自定义。 例如,服务可以将自定义 XML 元数据添加到终结点。 此信息随客户端一起发送到客户端,以响应客户端的查询。
服务公告
WCF 4 还便于配置服务,以便在服务启动时“宣布”其终结点。 这样,“侦听”的客户端就可以在加入网络时了解新的服务终结点,从而减少客户端执行的探测 (和多播消息传送) 量。
可以使用 serviceDiscovery> 行为配置具有公告终结点的服务<。 serviceDiscovery <> 行为允许定义将由服务公开的公告终结点集合。 在大多数情况下,可以使用标准“udpAnnouncementEndpoint”。
如果希望服务响应客户端启动的发现探测,则仍需使用标准“udpDiscoveryEndpoint”来配置该服务。 以下示例演示了一个典型配置:
<configuration>
<system.serviceModel>
<services>
<service name=“CalculatorService”>
<endpoint binding=“wsHttpBinding” contract=“ICalculatorService”/>
<endpoint kind=“udpDiscoveryEndpoint”/>
</service>
</服务>
<behaviors>
<serviceBehaviors>
<行为>
<serviceDiscovery>
<announcementEndpoints>
<endpoint kind=“udpAnnouncementEndpoint”/>
</announcementEndpoints>
</serviceDiscovery>
</行为>
</serviceBehaviors>
</行为>
</system.serviceModel>
</配置>
完成此配置后,服务将在联机时通知自身,并且还会在脱机时发出通知。 为了利用这些公告,必须专门设计客户端,以便在运行时侦听它们。 为此,可以在实现WS-Discovery公告协议的客户端应用程序中托管公告服务。
WCF 4 附带一个名为 AnnouncementService 的类,该类专为此目的而设计。 AnnouncementService 提供两个事件处理程序:OnlineAnnouncementReceived 和 OfflineAnnouncementReceived。 客户端应用程序只需使用 ServiceHost 托管 AnnouncementService 的实例,并为这两个事件注册事件处理程序。
每当服务联机并报出自身时,客户端托管的 AnnouncementService 将收到“联机”公告,OnlineAnnouncementReceived 将在客户端中触发。 当服务脱机时,它将发送“脱机”通知,并且 OfflineAnnouncementReceived 将在客户端中触发。 下面演示了托管 AnnouncementService 并为两个公告事件实现处理程序的示例客户端应用程序:
类客户端
{
public static void Main ()
{
创建 AnnouncementService 实例
AnnouncementService announcementService = new AnnouncementService () ;
订阅公告事件
announcementService.OnlineAnnouncementReceived += OnOnlineEvent;
announcementService.OfflineAnnouncementReceived += OnOfflineEvent;
为 AnnouncementService 创建 ServiceHost
using (ServiceHost 公告ServiceHost =
new ServiceHost (announcementService) )
{
侦听通过 UDP 多播发送的公告
announcementServiceHost.AddServiceEndpoint (
new UdpAnnouncementEndpoint () ) ;
announcementServiceHost.Open () ;
Console.WriteLine (“侦听服务公告”。) ;
Console.WriteLine();
Console.WriteLine (“按 <Enter> 终止”。) ;
Console.ReadLine();
}
}
static void OnOnlineEvent (对象发送方,AnnouncementEventArgs e)
{
Console.WriteLine();
Console.WriteLine (“收到来自 {0}:”的在线公告,
e.EndpointDiscoveryMetadata.Address) ;
PrintEndpointDiscoveryMetadata (e.EndpointDiscoveryMetadata) ;
}
static void OnOfflineEvent (对象 sender, AnnouncementEventArgs e)
{
Console.WriteLine();
Console.WriteLine (“已从 {0}收到脱机通知:”,
e.EndpointDiscoveryMetadata.Address) ;
PrintEndpointDiscoveryMetadata (e.EndpointDiscoveryMetadata) ;
}
...
}
图 11:侦听发现公告消息
现在,假设我运行此客户端程序,并使其保持启动并运行一段时间。 然后,我运行服务主机应用程序的几个实例。 每次启动时,我们都会在客户端控制台窗口中看到一条“联机”通知消息。 关闭每个服务主机应用程序时,客户端控制台窗口中会显示一条“脱机”公告消息。 图 11 显示了执行我刚才所述的操作后生成的客户端控制台窗口。
请记住,即席发现模式仅适用于本地子网。 如果要使用超出本地网络边界的WS-Discovery,则需要转到托管发现模式。 WCF 4 也支持生成必要的托管发现组件。
托管服务发现
实现托管发现模式比临时模式要多一点,因为它需要你实现发现代理服务。 发现代理服务是跟踪所有可用服务终结点的组件。 在此示例中,我们使用公告功能更新发现代理。 可通过许多其他方法为发现代理提供相关的发现信息,例如,可以连接现有终结点数据库并从那里捕获数据。 那么,如何实现发现代理服务呢?
WCF 4 附带一个名为 DiscoveryProxy 的基类,你可以从中派生该基类来实现发现代理服务。 图 12 显示了自定义发现代理服务实现的开始。 .NET 4 SDK 示例包含完整的示例实现以供参考。 完成发现代理服务的实现后,需要将其托管在某个位置。
图 12:实现自定义发现代理服务
[ServiceBehavior (InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple) ]
public 类 MyDiscoveryProxy : DiscoveryProxyBase
{
用于存储 EndpointDiscoveryMetadata 的存储库。
也可以改用数据库或平面文件。
Dictionary<EndpointAddress、 EndpointDiscoveryMetadata> onlineServices;
public MyDiscoveryProxy ()
{
this.onlineServices =
new Dictionary<EndpointAddress, EndpointDiscoveryMetadata> () ;
}
代理收到 Hello 消息时调用 OnBeginOnlineAnnouncement
protected override IAsyncResult OnBeginOnlineAnnouncement (
DiscoveryMessageSequence messageSequence, EndpointDiscoveryMetadata
endpointDiscoveryMetadata、AsyncCallback 回调、对象状态)
{
这。AddOnlineService (endpointDiscoveryMetadata) ;
返回新的 OnOnlineAnnouncementAsyncResult (回调、状态) ;
}
protected override void OnEndOnlineAnnouncement (IAsyncResult 结果)
{
OnOnlineAnnouncementAsyncResult.End (结果) ;
}
代理收到 Bye 消息时调用 OnBeginOfflineAnnouncement
protected override IAsyncResult OnBeginOfflineAnnouncement (
DiscoveryMessageSequence messageSequence, EndpointDiscoveryMetadata
endpointDiscoveryMetadata、AsyncCallback 回调、对象状态)
{
这。RemoveOnlineService (endpointDiscoveryMetadata) ;
返回新的 OnOfflineAnnouncementAsyncResult (回调、状态) ;
}
protected override void OnEndOfflineAnnouncement (IAsyncResult 结果)
{
OnOfflineAnnouncementAsyncResult.End (result) ;
}
代理收到探测请求消息时调用 OnBeginFind
protected override IAsyncResult OnBeginFind (
FindRequestContext findRequestContext、AsyncCallback 回调、对象状态)
{
这。MatchFromOnlineService (findRequestContext) ;
返回新的 OnFindAsyncResult (回调、状态) ;
}
protected override void OnEndFind (IAsyncResult 结果)
{
OnFindAsyncResult.End (结果) ;
}
...
对于此示例,我只需在控制台应用程序中托管 MyDiscoveryProxy 服务。 我将使用两个终结点配置主机:发现终结点和公告终结点。 以下示例演示了如何使用这两个终结点正确托管 MyDiscoveryProxy 服务:
class Program
{
public static void Main ()
{
Uri probeEndpointAddress = new Uri (“net.tcp://localhost:8001/Probe”) ;
URI announcementEndpointAddress =
new Uri (“net.tcp://localhost:9021/Announcement”) ;
ServiceHost proxyServiceHost = new ServiceHost (new MyDiscoveryProxy () ) ;
DiscoveryEndpoint discoveryEndpoint = new DiscoveryEndpoint (
new NetTcpBinding () , new EndpointAddress (probeEndpointAddress) ) ;
discoveryEndpoint.IsSystemEndpoint = false;
AnnouncementEndpoint announcementEndpoint = new AnnouncementEndpoint (
new NetTcpBinding () , new EndpointAddress (announcementEndpointAddress) ) ;
proxyServiceHost.AddServiceEndpoint (discoveryEndpoint) ;
proxyServiceHost.AddServiceEndpoint (announcementEndpoint) ;
proxyServiceHost.Open () ;
Console.WriteLine (“代理服务已启动”。) ;
Console.WriteLine();
Console.WriteLine (“按 <Enter> 终止服务”。) ;
Console.WriteLine();
Console.ReadLine();
proxyServiceHost.Close () ;
}
}
启动并运行发现代理服务后,可以将服务配置为直接向发现代理服务报出自己。 同样,可以将客户端应用程序配置为直接探测发现代理服务, (不再) 多播消息传送。
在服务主机应用程序中创建 AnnouncementEndpoint 时,通过指定发现代理的公告地址,将服务配置为直接向发现代理服务报出自身。 以下示例演示如何实现此目的:
...
Uri baseAddress = new Uri (“net.tcp://localhost:9002/CalculatorService/” +
Guid.NewGuid () 。ToString () ) ;
Uri announcementEndpointAddress = new Uri (“net.tcp://localhost:9021/Announcement”) ;
ServiceHost serviceHost = new ServiceHost (typeof (CalculatorService) , baseAddress) ;
ServiceEndpoint netTcpEndpoint = serviceHost.AddServiceEndpoint (
typeof (ICalculatorService) ,新的 NetTcpBinding () ,字符串。空) ;
创建指向托管代理服务的公告终结点
AnnouncementEndpoint announcementEndpoint = new AnnouncementEndpoint (
new NetTcpBinding () , new EndpointAddress (announcementEndpointAddress) ) ;
ServiceDiscoveryBehavior serviceDiscoveryBehavior = new ServiceDiscoveryBehavior () ;
serviceDiscoveryBehavior.AnnouncementEndpoints.Add (announcementEndpoint) ;
serviceHost.Description.Behaviors.Add (serviceDiscoveryBehavior) ;
serviceHost.Open () ;
...
然后,可以通过在客户端应用程序中创建 DiscoveryEndpoint 时指定发现代理的探测地址,将客户端应用程序配置为直接与发现代理服务通信。 以下示例演示了执行此操作的一种方法:
...
创建指向代理服务的发现终结点。
Uri probeEndpointAddress = new Uri (“net.tcp://localhost:8001/Probe”) ;
DiscoveryEndpoint discoveryEndpoint = 新的 DiscoveryEndpoint (
new NetTcpBinding () , new EndpointAddress (probeEndpointAddress) ) ;
使用以前创建的 discoveryEndpoint 创建 DiscoveryClient
DiscoveryClient discoveryClient = new DiscoveryClient (discoveryEndpoint) ;
查找 ICalculatorService 终结点
FindResponse findResponse = discoveryClient.Find (
new FindCriteria (typeof (ICalculatorService) ) ) ;
...
现在,让我们演练一个完整的示例。 首先,我将运行发现代理应用程序,以便发现代理服务可供使用。 然后,我将运行服务主机应用程序的实例,该应用程序将通过发现代理报出自身。 发生这种情况后,我们将看到一条消息输出到发现代理应用程序的控制台窗口, (见图 13) 。 这表明服务已成功向发现代理宣布自身,并且发现代理保存了有关新的“联机”服务终结点的信息。 现在,如果我们运行上面所示的客户端代码,它将直接探测发现代理,并检索当前运行的目标服务的终结点地址。
图 13:发现代理服务在运行时的输出
托管服务发现的好处是它可以跨网络边界工作, (它基于传统的服务调用) ,并且减少了发现解决方案中多播消息传送的需求。 此外,由于客户端通过发现代理来查找服务,因此服务本身不需要一直启动并运行即可被发现。
高级发现代理使用情况
WCF 编程模型在实现发现代理方面提供了很大的灵活性。 接收通知是填充服务列表的一种方式;但它不是唯一的方法。 例如,如果环境已包含服务存储库,则可以在该存储的顶部轻松构建发现代理外观,以使存储库在运行时可发现。
可以在即席模式或管理模式下设置发现代理。 在托管模式下运行时,客户端使用公告、探测和解析以单播方式直接与代理通信。 代理还会以单播方式将响应传输回发送方。
如果在即席模式下操作,代理可以侦听多播发现消息并直接响应发送方。 在此临时模式下,还可以专门配置代理以禁止多播消息。 也就是说,如果代理收到多播消息,它会通知发送方其存在,并通知发送方在代理上定向进一步查询,从而避免进一步的多播消息。
有关这些高级发现方案的详细信息,请参阅 中的WS-Discovery入门 http://www.oasis-open.org/committees/download.php/32184/WS-D-primer-wd-04.docx。
路由服务
在某些面向服务的环境中,利用集中的“路由”服务通常很有用,这些服务充当组织中分散的实际业务服务的代理或网关。 这会将使用者与实际业务服务分离,并使得在路由节点中执行各种不同类型的中间处理成为可能。
例如,某些环境使用路由来实现所有传入消息都必须通过的集中式安全边界。 有些使用基于内容的路由技术根据特定传入消息的内容确定要使用的目标服务。 另一些则使用路由来实现协议桥接,从而允许使用者使用一组协议进行通信,而路由器则使用一组不同的协议来与目标服务通信。 将路由用于各种负载均衡甚至服务版本控制技术也并不少见。
无论出于什么原因,“中间路由”模式都是当今构建大规模 SOA 解决方案时的常见要求。 在 WCF 3.x 中,没有对路由的官方支持。 尽管该框架提供了实现你自己的路由服务所需的 API,但要正确执行此操作需要做大量工作。 MSDN 杂志上发表了几篇文章,介绍了如何实现此目的。
由于路由是如今常见的要求,因此 WCF 4 现在在框架中附带了官方的“路由服务”,只需在自己的解决方案中托管和配置即可。
了解 RoutingService
WCF 4 附带一个名为 RoutingService 的新类,该类提供一个泛型 WCF 路由实现,以便在应用程序中使用。 RoutingService 可以使用各种不同的消息传送模式(例如单向、请求-响应和双工消息传送) )通过任何 WCF 支持的协议处理路由消息。 下面显示了 RoutingService 类定义:
[ServiceBehavior (AddressFilterMode = AddressFilterMode.Any,
InstanceContextMode = InstanceContextMode.PerSession,
UseSynchronizationContext = false,ValidateMustUnderstand = false) ,
AspNetCompatibilityRequirements (RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed) ]
公共密封类 RoutingService:// 协定允许不同的通信模式
ISimplexDatagramRouter、ISimplexSessionRouter、IRequestReplyRouter、
IDuplexSessionRouter、IDisposable
{
... // 实现已省略
}
如你所看到的,RoutingService 类派生自多个服务协定,以支持多个消息传送模式。 每个服务协定都提供对不同消息传送模式的支持,包括在适当的时候支持基于会话的通信。
RoutingService 的全部用途是接收来自使用者的传入消息,并将其“路由”到相应的下游服务。 RouterService 通过针对一组消息筛选器评估每个传入消息来确定要使用的目标服务。 因此,开发人员通常通过在配置文件中定义消息筛选器来控制路由行为。 目标服务可能与 RouterService 位于同一台计算机上,但它们不必这样做 ,它们也可以跨网络分布,并且可能需要各种不同的协议。
托管 RoutingService
可以像托管任何其他 WCF 服务一样在应用程序中托管 RoutingService。 只需创建 ServiceHost 实例并为服务类型指定 RoutingService。 在 ServiceHost 实例上调用 Open 后,RoutingService 将准备好“路由”消息,如下所示:
using System;
using System.ServiceModel;
using System.ServiceModel.Routing;
public static void Main ()
{
为 RoutingService 类型创建 ServiceHost。
using (ServiceHost serviceHost =
new ServiceHost (typeof (RoutingService) ) )
{
尝试
{
serviceHost.Open () ;
Console.WriteLine (“路由服务现在正在运行”。) ;
Console.WriteLine (“按 <Enter> 终止路由器”。) ;
现在可以访问该服务。
Console.ReadLine();
serviceHost.Close () ;
}
catch (CommunicationException)
{
serviceHost.Abort () ;
}
}
}
还可以像配置任何其他服务一样配置 RoutingService,这是定义路由筛选器的位置。 首先,需要使用一个或多个终结点对其进行配置。 定义路由终结点时,请选择 WCF 绑定和路由服务协定之一,如上 (所示,例如 ISimplexDatagramRouter、IRequestReplyRouter 等) 。 如果要支持多个消息传递模式或 WCF 绑定,可以在 RoutingService 上公开多个终结点。
以下示例演示如何使用四个路由终结点配置 RoutingService:两个使用 BasicHttpBinding (单向和请求-答复) ,两个使用 WSHttpBinding (单向和请求-答复) 。 请注意,它就像配置任何其他 WCF 服务一样:
<configuration>
<system.serviceModel>
<services>
<--路由服务 -->
<service behaviorConfiguration=“routingData”
name=“System.ServiceModel.Routing.RoutingService”>
<主机>
<baseAddresses>
<add baseAddress=“https://localhost:8000/routingservice/router"/>
</baseAddresses>
</主机>
<!--
定义并配置我们希望路由器侦听的终结点,以及
我们希望它使用的协定。 路由器提供的合同包括:
ISimplexDatagramRouter、ISimplexSessionRouter、IRequestReplyRouter 和
IDuplexSessionRouter。
-->
<endpoint address=“oneway-basic”
binding=“basicHttpBinding”
name=“onewayEndpointBasic”
contract=“System.ServiceModel.Routing.ISimplexDatagramRouter” />
<endpoint address=“oneway-ws”
binding=“wsHttpBinding”
name=“onewayEndpointWS”
contract=“System.ServiceModel.Routing.ISimplexDatagramRouter” />
<endpoint address=“twoway-basic”
binding=“basicHttpBinding”
name=“reqReplyEndpointBasic”
contract=“System.ServiceModel.Routing.IRequestReplyRouter” />
<endpoint address=“twoway-ws”
binding=“wsHttpBinding”
name=“reqReplyEndpointWS”
contract=“System.ServiceModel.Routing.IRequestReplyRouter” />
</service>
</服务>
...
ISimplexDatagramRouter 和 IRequestReplyRouter 接口定义了可与特定于业务的服务协定一起使用的通用单向和请求-回复服务协定定义。 下面显示了如何在 WCF 中定义这些接口:
[ServiceContract (Namespace=“https://schemas.microsoft.com/netfx/2009/05/routing",
SessionMode = SessionMode.Allowed) ]
公共接口 ISimplexDatagramRouter
{
[OperationContract (AsyncPattern = true, IsOneWay = true, Action = “*”) ]
IAsyncResult BeginProcessMessage (Message 消息,AsyncCallback 回调,
对象状态) ;
void EndProcessMessage (IAsyncResult 结果) ;
}
[ServiceContract (Namespace=“https://schemas.microsoft.com/netfx/2009/05/routing",
SessionMode = SessionMode.Allowed) ]
公共接口 IRequestReplyRouter
{
[OperationContract (AsyncPattern = true, IsOneWay= false, Action = “*”,
ReplyAction = “*”) ]
[GenericTransactionFlow (TransactionFlowOption.Allowed) ]
IAsyncResult BeginProcessRequest (消息,AsyncCallback 回调,
对象状态) ;
消息 EndProcessRequest (IAsyncResult 结果) ;
}
上述终结点配置公开了供使用者使用的路由终结点。 客户端应用程序将选择要在其客户端代码中使用的其中一个终结点,并将所有服务调用直接定向到 RoutingService。 当 RoutingService 通过其中一个终结点接收消息时,它会评估路由消息筛选器,以确定将消息转发到何处。
使用消息筛选器配置 RoutingService
可以通过代码或配置 (配置消息筛选器来配置 RoutingService,就像 WCF) 中的其他所有内容一样。 WCF 4 提供了用于管理路由消息筛选器的 RoutingBehavior。 首先需要在 RouterService 上启用 RoutingBehavior,然后指定要用于 RoutingService 的此特定实例的筛选器表的名称:
<configuration>
<system.serviceModel>
...
<behaviors>
<serviceBehaviors>
<behavior name=“routingData”>
<serviceMetadata httpGetEnabled=“True”/>
<--定义路由行为并指定筛选器表名称 -->
<routing filterTableName=“filterTable1” />
</行为>
</serviceBehaviors>
</行为>
...
如果查看前面的示例,其中我们配置了 RoutingService 的终结点,你将看到我们已通过 behaviorConfiguration 属性将“routingData”行为应用于服务。 接下来,我们需要定义一个名为“filterTable1”的筛选器表。
但是,在定义筛选器表之前,我们需要要路由到的目标服务的终结点定义。 在 WCF <客户端> 配置部分中定义这些目标终结点,因为 RoutingService 在将消息转发到目标服务时实质上是“客户端”。 以下示例演示如何定义我们可以路由到的两个目标终结点:
<configuration>
...
<--定义我们希望路由器与之通信的客户端终结点。
这些是路由器将向其发送消息的目标。 -->
<客户端>
<endpoint name=“CalculatorService1”
address=“https://localhost:8000/servicemodelsamples/calcservice1"
binding=“wsHttpBinding” contract=“*” />
<endpoint name=“CalculatorService2”
address=“https://localhost:8001/servicemodelsamples/calcservice2"
binding=“wsHttpBinding” contract=“*” />
</客户>
...
现在,我们可以定义实际的筛选器表,该表将确定运行时的路由逻辑。 在 filterTables> 元素中<定义筛选器表条目。 filterTable> 中的每个<条目定义路由“筛选器”和目标终结点之间的映射。 定义要在 filters 元素中使用的<“筛选器”– 每个<筛选器>条目指定要使用的筛选器类型,以及特定于筛选器的数据 (,例如操作值、XPath 表达式等>) 。
以下示例演示如何使用映射到 CalculatorService1 终结点的单个筛选器配置筛选器表。 在这种情况下,“MatchAll”筛选器匹配所有传入消息:
<configuration>
...
<--路由部分 -->
<routing>
<--定义我们希望路由器使用的筛选器。 -->
<filters>
<filter name=“MatchAllFilter1” filterType=“MatchAll” />
</过滤 器>
<--定义包含 matchAll 筛选器的筛选器表 -->
<filterTables>
<filterTable name=“filterTable1”>
<--将筛选器映射到以前定义的客户端终结点。
与此筛选器匹配的消息将发送到此目标。 -->
<add filterName=“MatchAllFilter1” endpointName=“CalculatorService1” />
</filterTable>
</filterTables>
</路由>
</system.serviceModel>
</配置>
通过运行路由服务主机应用程序、CalculatorService1 主机应用程序以及旨在将消息发送到路由器终结点之一的客户端,我们可以验证路由是否正常工作。 运行客户端时,应看到消息在中间 RoutingService“路由”后到达 CalculatorService 1, (见图 14、图 15 和图 16) 。
图 14:RoutingService 主机应用程序
图 15:面向 RoutingService 的客户端 https://localhost:8000/routingservice/router
图 16:目标服务 (CalculatorService1)
消息筛选器和基于内容的路由
WCF 附带了多个内置 MessageFilter 类,你可以将这些类与路由消息筛选器结合使用来检查传入消息的内容。
例如,WCF 提供 ActionMessageFilter,可用于匹配特定WS-Addressing“action”值。 WCF 还提供 EndpointAddressMessageFilter、EndpointNameMessageFilter 和 PrefixEndpointAddressMessageFilter,用于匹配特定终结点详细信息。 最灵活的方法之一是 XPathMessageFilter,它允许针对传入消息计算 XPath 表达式。 所有这些筛选器都允许在解决方案中执行基于内容的路由。
除了这些内置的 MessageFilter 类型外,WCF 4 还可用于定义自定义消息筛选器,该筛选器恰好是 RoutingService 的主要扩展点之一。
让我们看一个基于操作值执行基于内容的路由的示例。 假设我们要将 CalculatorService 操作的一半路由到 CalculatorService1,将另一半路由到 CalculatorService2。 为此,可以为每个不同的 CalculatorService 操作值定义筛选器,并将其中一半映射到每个目标服务终结点,如下所示:
<configuration>
...
<--路由部分 -->
<routing>
<--定义我们希望路由器使用的筛选器。 -->
<filters>
<filter name=“addFilter” filterType=“Action”
filterData=“http://Microsoft.Samples.ServiceModel/ICalculator/Add"/>
<filter name=“subFilter” filterType=“Action”
filterData=“http://Microsoft.Samples.ServiceModel/ICalculator/Subtract"/>
<filter name=“mulFilter” filterType=“Action”
filterData=“http://Microsoft.Samples.ServiceModel/ICalculator/Multiply"/>
<filter name=“divFilter” filterType=“Action”
filterData=“http://Microsoft.Samples.ServiceModel/ICalculator/Divide"/>
</过滤 器>
<filterTables>
<filterTable name=“filterTable1”>
<add filterName=“addFilter” endpointName=“CalculatorService1”/>
<add filterName=“subFilter” endpointName=“CalculatorService2”/>
<add filterName=“mulFilter” endpointName=“CalculatorService1”/>
<add filterName=“divFilter” endpointName=“CalculatorService2”/>
</filterTable>
</filterTables>
</路由>
</system.serviceModel>
</配置>
现在,当我们运行解决方案并执行调用所有四个操作的客户端时,我们将看到每个服务控制台窗口中显示一半的操作 (见图 17 和图 18) 。
图 17:CalculatorService1 的输出
图 18:CalculatorService2 的输出
XPathMessageFilter 提供更大的灵活性,因为可以提供各种不同的 XPath 表达式来针对传入消息进行评估。 XPath 表达式可以计算传入消息的任何部分,包括 SOAP 标头或 SOAP 正文。 生成基于内容的消息筛选器时,这为你提供了极大的灵活性。 为了了解 XPathMessageFilter 的机制,下面演示如何使用 XPath 表达式重写最后一个示例:
<configuration>
...
<--路由部分 -->
<routing>
<--定义我们希望路由器使用的筛选器。 -->
<filters>
<filter name=“addFilter” filterType=“XPath”
filterData=“/s:Envelope/s:Header/wsa:Action =
'http://Microsoft.Samples.ServiceModel/ICalculator/Add'"/>
<filter name=“subFilter” filterType=“XPath”
filterData=“/s:Envelope/s:Header/wsa:Action =
'http://Microsoft.Samples.ServiceModel/ICalculator/Subtract'"/>
<filter name=“mulFilter” filterType=“XPath”
filterData=“/s:Envelope/s:Header/wsa:Action =
'http://Microsoft.Samples.ServiceModel/ICalculator/Multiply'"/>
<filter name=“divFilter” filterType=“XPath”
filterData=“/s:Envelope/s:Header/wsa:Action =
'http://Microsoft.Samples.ServiceModel/ICalculator/Divide'"/>
</过滤 器>
<namespaceTable>
<add prefix=“s” namespace=“http://www.w3.org/2003/05/soap-envelope" />
<add prefix=“wsa” namespace=“http://www.w3.org/2005/08/addressing" />
</namespaceTable>
<filterTables>
<filterTable name=“filterTable1”>
<add filterName=“addFilter” endpointName=“CalculatorService1”/>
<add filterName=“subFilter” endpointName=“CalculatorService2”/>
<add filterName=“mulFilter” endpointName=“CalculatorService1”/>
<add filterName=“divFilter” endpointName=“CalculatorService2”/>
</filterTable>
</filterTables>
</路由>
</system.serviceModel>
</配置>
请注意,filterData 属性包含一个 XPath 表达式,该表达式将针对传入消息 (表达式检查此示例中的操作值) 。 请注意,我还如何使用 namespaceTable> 元素定义一组命名空间前缀绑定<。 如果要在 XPath 表达式中使用命名空间前缀,如我上面所做的那样,则这是必需的。 使用此配置重新运行解决方案会产生与之前相同的结果 (请参阅图 17 和图 18) 。
每当需要基于自定义 SOAP 标头或 SOAP 消息正文中的内容路由消息时,都需要使用此 XPath 筛选器技术。
协议桥接
在前面的示例中,我们使用相同的 WCF 绑定 (WSHttpBinding) 在客户端和路由器之间以及路由器与目标服务之间通信。 RoutingService 能够跨大多数 WCF 绑定桥接通信。 例如,你可能想要配置路由器,以便客户端通过 WSHttpBinding 与其通信,但路由器随后使用 NetTcpBinding 或 NetNamedPipeBinding 与下游目标服务进行通信。
让我们看看如何配置 RoutingService 来处理此方案。 我们将保留与上述相同的 RoutingService 终结点配置,这允许使用者通过 BasicHttpBinding 或 WSHttpBinding 与 RoutingService 通信。 但现在,我们将更改目标服务的客户端终结点定义,以使用 NetTcpBinding 和 NetNamedPipeBinding,如下所示:
<configuration>
...
<--定义我们希望路由器与之通信的客户端终结点。
这些是路由器将向其发送消息的目标。 -->
<客户端>
<endpoint name=“CalculatorService1”
address=“net.tcp://localhost:8001/servicemodelsamples/calcservice1”
binding=“netTcpBinding” contract=“*” />
<endpoint name=“CalculatorService2”
address=“net.pipe://localhost/servicemodelsamples/calcservice2”
binding=“netNamedPipeBinding” contract=“*” />
</客户>
...
当然,我们需要更新 CalculatorService1 和 CalculatorService2 应用程序,以便分别支持兼容的 NetTcpBinding 和 NetNamedPipeBinding 终结点。 完成此配置后,使用者可以使用 BasicHttpBinding/WSHttpBinding 与 RoutingService 通信,路由器将使用 NetTcpBinding 或 NetNamedPipeBinding 与目标服务通信,具体取决于消息要路由到的服务。
错误处理和容错
RoutingService 还提供内置机制来处理运行时通信错误并支持基本级别的容错。 定义筛选器表时,可以定义不同的备用终结点列表,如果与初始目标终结点通信导致错误,路由服务将使用这些列表。 这实质上可以创建“备份”终结点列表。
以下示例演示了如何在 backupLists> 元素中<定义备份终结点列表,我们可以将其与筛选表条目相关联:
<configuration>
...
<--ROUTING 部分 -->
<routing>
... <!-- 定义我们希望路由器使用的筛选器。 -->
<filterTables>
<filterTable name=“filterTable1”>
<add filterName=“addFilter” endpointName=“CalculatorService1”
alternateEndpoints=“backupEndpoints”/>
<add filterName=“subFilter” endpointName=“CalculatorService1”
alternateEndpoints=“backupEndpoints”/>
<add filterName=“mulFilter” endpointName=“CalculatorService1”
alternateEndpoints=“backupEndpoints”/>
<add filterName=“divFilter” endpointName=“CalculatorService1”
alternateEndpoints=“backupEndpoints”/>
</filterTable>
</filterTables>
<backupLists>
<backupList name=“backupEndpoints”>
<add endpointName=“CalculatorService2”/>
</backupList>
</backupLists>
</路由>
</system.serviceModel>
</配置>
请注意,在此示例中,我们如何将所有筛选表条目配置为转发到 CalculatorService1,因为 CalculatorService2 现在是我们的“备份”终结点,仅在 CalculatorService1 导致 TimeoutException、CommunicationException 或派生异常类型时才使用。 例如,如果我再次运行解决方案并关闭 CalculatorService1,然后执行客户端程序,我们将看到所有消息最终出现在 CalculatorService2 中。 请务必再次注意,所有这些路由配置都可以在主机应用程序代码中动态执行。
多播路由行为
RoutingService 还支持以“多播”方式自动将特定传入消息路由到多个目标。 当传入消息与配置的筛选器表中的多个筛选器匹配时,RoutingService 将自动将消息路由到与“匹配”筛选器关联的每个目标终结点。
以下示例演示配置了同一通配符筛选器的两个路由条目,这些筛选器与所有传入消息匹配:
<configuration>
...
<--ROUTING 部分 -->
<routing>
<--定义我们希望路由器使用的筛选器。 -->
<filters>
<filter name=“wildcardFilter” filterType=“MatchAll” />
</过滤 器>
<filterTables>
<filterTable name=“filterTable1”>
<add filterName=“wildcardFilter” endpointName=“CalculatorService1”/>
<add filterName=“wildcardFilter” endpointName=“CalculatorService2”/>
<add filterName=“wildcardFilter” endpointName=“CalculatorService3”/>
</filterTable>
</filterTables>
</路由>
</system.serviceModel>
</配置>
完成此配置后,无论消息中发现什么,每个传入的 SOAP 消息都将自动路由到所有目标终结点。
此多播行为与前面部分讨论的协议桥接和错误处理功能组成。 唯一的问题是多播仅适用于单向或双工通信,而不适用于请求-响应通信,因为基础系统需要在传出请求和传入响应之间保持一对一的比率。
改进了 REST 支持
WCF 4 附带了一些新功能,这些功能在使用 WCF 生成 RESTful 服务时会派上用场。 这一组功能现在称为 WCF WebHttp 服务。 它们包括对自动帮助页面的支持,该页向使用者描述 RESTful 服务、简化的 HTTP 缓存、消息格式选择、REST 友好的异常、ASP.NET 路由集成、一些新的 Visual Studio 项目模板等。 我们将没有空间来详细介绍所有这些功能,但我将简要介绍以下我的一些收藏夹,以及指向其他详细信息的链接。
其中许多功能是去年 WCF REST 初学者工具包首次引入的,现在正将其纳入官方框架。 将来可能会看到更多 WCF REST 初学者工具包功能。
自动帮助页
在 WCF 4 中使用 WebServiceHost 类时,RESTful 服务将自动享受自动帮助页功能的优势。 考虑到缺少 WSDL 元数据和客户端代码生成,使用 REST 时,这是一个急需的附加内容,它使使用者能够更轻松地了解如何开始使用你的服务,因此启用这项新功能通常是一个好主意。
使用 WebServiceHost 类承载服务时,它会使用 WebHttpBehavior 自动配置服务,并在基本 HTTP 地址) 添加使用 WebHttpBinding (配置的默认 HTTP 终结点。 从 WCF 4 起,WebHttpBehavior 类附带一个 HelpEnabled 属性,该属性控制是否在主机中启用新的帮助页。 以下配置示例演示如何为特定 REST 终结点启用自动帮助页功能:
<configuration>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled=“true” />
<behaviors>
<endpointBehaviors>
<behavior name=“HelpBehavior”>
<webHttp helpEnabled=“true” />
</行为>
</endpointBehaviors>
</行为>
<services>
<service name=“CounterResource”>
<endpoint behaviorConfiguration=“HelpBehavior”
binding=“webHttpBinding”
contract=“CounterResource” />
</service>
</服务>
</system.serviceModel>
</配置>
可以通过浏览到 URL 末尾追加“help”的服务基址来查看帮助页面 (请参阅图 19) 。
图 19:RESTFul 服务的自动帮助页
帮助页提供了使用 [WebGet] 或 [WebInvoke] 批注的每个操作的可读说明,并且对于每个操作,它都描述了 URI 模板、支持的 HTTP 操作和支持的消息格式,基本上是使用者需要知道的所有内容, (请参阅图 20) 。 还可以通过将 [Description] 属性应用于每个操作来提供更人性化的描述。
对于每个请求/响应,帮助页还提供 XML 架构和相应的示例 XML 实例,使用者可以使用该实例与服务集成。 使用者可以使用架构来生成适当的客户端可序列化类型,或者只需检查示例 XML 文档即可手动确定如何编写相应的 XML 处理代码。 这两种方法都很有用。
图 20:特定操作的自动帮助页
此新的帮助页面功能自动使 RESTful 服务更易于发现,最终使其他人更容易使用它们。 使用者可以发现服务的 URI 设计、支持的 HTTP 操作和请求/响应格式,并且说明将始终与 WCF 代码保持同步,这与使用 ASP.NET Web 服务的方式类似。
HTTP 缓存支持
REST 的主要潜在优势之一是 HTTP 缓存。 但是,为了实现这一优势,必须在请求和响应消息中利用各种 HTTP 缓存标头。 可以通过 WebOperationContext 实例手动访问请求/响应标头,在 WCF 服务操作中完成此操作,但正确执行并非易事。
因此,WCF 4 附带了一个更简单的模型,用于通过 [AspNetCacheProfile] 属性控制缓存,可以声明性地应用于 GET 操作。 此属性允许为每个操作指定 ASP.NET 缓存配置文件名称,在后台有一个缓存检查器 (CachingParameterInspector) 负责处理所有基础 HTTP 缓存详细信息。
[AspNetCacheProfile] 实现基于标准 ASP.NET 输出缓存机制。 以下示例演示如何将 [AspNetCacheProfile] 属性应用于 [WebGet] 操作:
...
[AspNetCacheProfile (“CacheFor60Seconds”) ]
[WebGet (UriTemplate=XmlItemTemplate) ]
[OperationContract]
public Counter GetItemInXml ()
{
return HandleGet () ;
}
...
完成此操作后,需要在web.config文件中定义名为“CacheFor60Seconds”的 ASP.NET 输出缓存配置文件。 以下web.config演示了如何执行此操作:
<configuration>
<system.web>
<缓存>
<outputCacheSettings>
<outputCacheProfiles>
<add name=“CacheFor60Seconds” duration=“60” varyByParam=“format” />
</outputCacheProfiles>
</outputCacheSettings>
</缓存>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled=“true” />
</system.serviceModel>
</配置>
请注意,ASP.NET 缓存配置文件如何设置为将输出缓存 60 秒,并配置为通过“format”查询字符串变量更改缓存。 这一点很重要,因为有问题的服务同时支持 XML 和 JSON,可通过格式变量 (例如“?format=json”) 进行控制。 服务需要独立于彼此缓存 XML 和 JSON 响应。
此配置文件还演示了如何启用 ASP.NET 兼容模式,这是希望利用各种 ASP.NET 功能(如输出缓存)所必需的。
[AspNetCacheProfile] 属性使利用 HTTP 缓存变得更容易,而无需直接使用 HTTP 缓存标头。 基础 WCF 行为负责在响应中注入 HTTP Cache-Control、Date、Expires 和 Vary HTTP 标头,客户端还可以利用这些标头来确定有关未来往返的正确缓存语义。
邮件格式选择
如果回顾图 19,你会注意到 CounterResource 服务支持 GET、POST 和 PUT 操作的 XML 和 JSON 格式。 自 WCF 3.5 起,可以通过 [WebGet] 和 [WebInvoke] 属性上的 RequestFormat 和 ResponseFormat 属性实现此目标。
我在 CounterResource 服务中通过为每个版本定义单独的操作协定(一个用于 XML 版本,另一个用于 JSON 版本)来实现这一点,如下所示:
...
[WebGet (UriTemplate=“”) ]
[OperationContract]
public Counter GetItemInXml ()
{
return HandleGet () ;
}
[WebGet (UriTemplate = “?format=json”, ResponseFormat=WebMessageFormat.Json) ]
[OperationContract]
public Counter GetItemInJson ()
{
return HandleGet () ;
}
...
遗憾的是,对于每个逻辑操作,如果希望同时支持 XML 和 JSON 格式,则需要两个操作协定。 在 CounterResource 服务中,需要有六个操作协定来支持 GET、POST 和 PUT 操作的 XML 和 JSON。
WCF 4 通过提供基于 HTTP“Accept”标头的自动格式选择的支持,使此方案更易于处理,这是一种更好的方法。 让我来解释一下这是如何工作的。
首先,每个逻辑操作只需要一个操作协定:
[WebGet (UriTemplate=“”) ]
[OperationContract]
public Counter GetItem ()
{
return HandleGet () ;
}
然后,我们在标准 WebHttpEndpoint 上自动选择格式,如下所示:
<configuration>
<system.serviceModel>
<standardEndpoints>
<webHttpEndpoint>
<--“”标准终结点用于自动创建 Web 终结点。 -->
<standardEndpoint name=“” helpEnabled=“true”
automaticFormatSelectionEnabled=“true”/>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
</配置>
完成此操作后,服务现在可以处理和返回 XML 或 JSON 消息。 它通过首先检查在请求消息中找到的 HTTP Accept 标头来确定要使用的格式。 如果这不起作用,它将使用与请求消息相同的消息格式(假设它是 XML 或 JSON),否则它将对特定操作使用默认格式。
现在由客户端决定通过 HTTP 内容类型和 Accept 标头使用哪种格式。 Content-Type 标头指定请求消息中的格式,而 Accept 标头指示客户端从服务“接受”回的格式。 例如,如果客户端想要从服务接收回 JSON,则应在请求中指定以下 HTTP Accept 标头:
Accept: application/json
这在任何 HTTP 客户端编程模型中都很容易实现,使用 Fiddler 等工具也很容易实现。 图 21 显示了如何使用 Fiddler 从新改进服务中获取 JSON。
图 21:使用 HTTP Accept 标头检索 JSON
WCF 4 还提供新的 API,使用户可以轻松地在运行时显式指定消息格式。 以下代码演示如何通过编写代码来扩展 GetItem 操作,该代码首先查找“format”查询字符串参数并相应地显式设置响应格式。 如果找不到“format”参数,则只需像以前一样依赖于 Accept 标头。
[WebGet (UriTemplate=“”) ]
[OperationContract]
public Counter GetItem ()
{
如果已指定格式查询字符串参数,
将响应格式设置为该格式。 如果没有此类
查询字符串参数存在,将使用 Accept 标头
string formatQueryStringValue =
WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters[
“format”];
如果 (!string,则为 。IsNullOrEmpty (formatQueryStringValue) )
{
如果 (formatQueryStringValue.Equals (“xml”,
System.StringComparison.OrdinalIgnoreCase) )
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Xml;
}
如果 (formatQueryStringValue.Equals (“json”,
System.StringComparison.OrdinalIgnoreCase) )
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
}
else
{
引发新的 WebFaultException<字符串> (字符串。格式 (“不支持的格式'{0}”,
formatQueryStringValue) , HttpStatusCode.BadRequest) ;
}
}
return HandleGet () ;
}
最后,这些新功能使你不再像在 WCF 3.5 中那样将消息格式类型硬编码到 [WebGet] 和 [WebInvoke] 操作协定中。
RESTful 错误处理
由于 RESTful 服务不使用 SOAP,因此不再拥有标准的 SOAP 错误机制,因此可以在 .NET 异常和 SOAP 错误消息之间自动映射。 在 WCF 3.5 中生成 REST 服务时,每当要自定义发回客户端的 HTTP 错误消息时,必须手动构造 HTTP 响应消息。
在 WCF 4 中,你将找到一个名为 WebFaultException<T> 的新类,该类可以更轻松地将“Web 错误” (例如 HTTP 错误) 回使用者。 它的工作方式非常类似于 WCF 中的 FaultException<T> ,但你可以指定 HTTP 状态代码和详细信息类型以提供更多详细信息。
以下示例演示如何在用户指定不受支持的格式类型时返回 HTTP 400 (错误请求) 错误 – 我只是将字符串用于详细信息类型:
如果 (!string,则为 。IsNullOrEmpty (格式) )
{
如果 (格式,则为 。等于 (“xml”, System.StringComparison.OrdinalIgnoreCase) )
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Xml;
}
如果 (格式,则为 else。等于 (“json”, System.StringComparison.OrdinalIgnoreCase) )
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
}
else
{
引发新的 WebFaultException<字符串> (字符串。格式 (“不支持的格式'{0}”,
format) ,HttpStatusCode.BadRequest) ;
}
}
此新功能使返回标准 HTTP 错误消息更加自然,只需像在基于 SOAP 的服务中通常一样引发异常。
将 WCF 与 ASP.NET 路由集成
WCF 4 和 ASP.NET 4 中发现基于 URL 的路由功能之间有很多相似之处。 它们都允许你执行相同操作 - 实质上将 URL 模板映射到类上的方法。 但是,这两个模型之间存在一个显著差异。
WCF 4 方法在开发过程中通过应用于类定义的 [WebGet] 和 [WebInvoke] 属性将 URL 模板设计绑定到单个类。 随着服务随时间的增长和演变,这可能会导致无法分解为较小的区块的大型整体设计。 另一方面,ASP.NET 4 方法将路由逻辑与目标类定义分离,从而允许根据需要跨多个类定义映射服务路由。
WCF 4 现在提供将 ASP.NET 路由引擎与 WCF 4 服务集成的功能,从而可以从 WCF 服务之上的 ASP.NET 路由模型中获益。
可以通过利用新的 ServiceRoute 类在 Global.asax RegisterRoutes 方法中完成此操作,该类允许将 ASP.NET 路由映射到 WCF 服务类:
private void RegisterRoutes ()
{
WebServiceHostFactory 工厂 = new WebServiceHostFactory () ;
RouteTable.Routes.Add (new ServiceRoute (“Bookmarks”, factory,
typeof (BookmarkService) ) ) ;
RouteTable.Routes.Add (new ServiceRoute (“Users”, factory,
typeof (UserService) ) ) ;
}
我调用 RouteTable.Routes.Add 两次,为两个不同的 WCF 服务类添加新路由。 第一个路由将“/Bookmarks”映射到 BookmarkService 类,而第二个路由将“/Users”映射到 UserService 类。 请注意如何将这两个路由配置为使用 WebServiceHostFactory。
现在,当我们在 WCF 服务类定义上使用 [WebGet] 和 [WebInvoke] 属性时,我们将使用相对路径 , 它们将相对于此处指定的 ASP.NET 路由。
REST 项目模板
Visual Studio 2010 中的一项整洁功能是扩展管理器,可从工具访问它 |扩展管理器。 借助扩展管理器,Microsoft 和其他第三方只需单击一个按钮,即可将新的项目模板、控件和工具集成到 Visual Studio 2010 体验中。 对于 Microsoft 产品团队来说,这非常出色,因为它可以在 RTM 后交付内容,并且仍然使其成为 Microsoft 提供的官方扩展。
如果打开扩展管理器并展开“模板”节点,则会找到一个名为“WCF”的子节点。 单击“WCF”,应会看到四个新的 WCF REST 服务模板 - 每个 .NET 版本一个 (3.5 与 4.0) ,每个语言一个 (C# 与 VB.NET) ,如图 22 所示。
图 22:扩展管理器中的新 WCF REST 项目模板
可以选择这些新项目模板并将其下载到本地 Visual Studio 2010 安装,并将其与任何其他项目类型一样使用。 你将在 Visual C# 下找到新的 REST 项目类型 |Web |WCF REST 服务应用程序 (假定你安装了 C# 版本) 。
使用它创建新项目时,你会注意到,它生成的 WCF REST 服务应用程序使用我在本部分中重点介绍的大部分新 WCF 4 功能。
更多信息
除了我们在此处讨论的内容外,WCF 4 还附带了一些更高级的 REST 功能和新的 WCF API 扩展,这些扩展可简化一些操作,例如处理自定义消息格式 (而不是 XML 或 JSON) 、使用新的 T4 功能返回基于模板的“视图”、实现条件 GET 和 ETag 支持、实现乐观并发以及生成出站链接。
有关所有这些新的 WCF 4 功能(包括此处没有空间介绍的功能)的详细信息,请浏览到 .NET Endpoint 博客,找到在 .NET 4 中介绍 WCF WebHttp Services 的条目。 该团队提供了一个完整的 12 个部分博客系列,其中介绍了每个新功能,一次一个博客条目,以及大量示例代码和分步说明。
工作流服务
.NET 4 中最受关注的功能区域之一是“工作流服务”。 Microsoft 在改进 WCF 与 WF 之间的集成方面投入了大量资金,以便为生成各种应用程序提供丰富的声明性编程模型。
了解工作流服务
WF 提供了一个声明性编程模型,该模型可提高编写逻辑的用户的抽象级别。 WF 最终提供的是通过定义自己的业务领域活动库来编写自己的语言的框架。 然后,它提供一个可视化设计器,用于将这些活动组合到程序中,以及一个知道如何管理这些程序的执行的运行时。
WF 提供了一个特别好的模型,用于实现需要等待不同类型的外部事件发生(例如从另一个系统接收消息)的长时间运行的应用程序。 其持久性模型使有效利用系统资源成为可能,只需在实际运行时将程序保留在内存中。 当程序等待外部事件发生时,它可以持久保存到数据库,从而释放它正在使用的系统资源。
图 23:工作流服务
WF 与其他开发框架的不同之处在于,它提供了这些功能,同时仍然允许使用顺序流控制编程逻辑进行编程,这通常让开发人员阅读和编写代码更容易理解。
如果构建的 WCF 服务本质上是长期运行的,或者可能受益于我刚才介绍的一些其他优势,则可以使用 WF 工作流实现 WCF 服务。 可以使用 WF 来协调使用其他外部服务的逻辑。
图 23 演示了这些概念。
尽管自 .NET 3.5 以来,这一点已经实现,但 .NET 4 在改进开发人员体验和提供 .NET 3.5 中缺少的一些所需功能方面取得了重大进展。
通过将 WCF 和 WF 的世界组合在一起,可以充分利用这两个世界。 你将获得声明性编程模型、WF 设计器的开发人员友好体验、用于管理长时间运行的服务的强大运行时以及 WCF 提供的丰富通信灵活性。
生成第一个工作流服务
为了让你了解新的工作流服务开发人员体验,我将引导你完成使用 .NET 4 和新的 Visual Studio 2010 设计器生成一个完整示例。
如果打开 Visual Studio 2010 并选择“文件 ” |新建项目,你将在 WCF 模板下找到一个新的工作流项目, 它称为“WCF 工作流服务应用程序”。 我将选择此项目模板并为其命名为“HelloWorldWorkflowService”,并创建新项目。
图 24:Visual Studio 2010 工作流服务项目模板
创建后,新项目将仅包含两个文件 - 一个包含声明性工作流服务定义的 Service1.xamlx 文件和一个包含服务配置的 Web.config 文件。
关于 .NET 4 中新的工作流服务模型,最引人注目的一点就是可以在 XAML 中定义整个服务定义。 在新项目中,Service1.xamlx 文件包含服务定义,实现只是一个基于 XAML 的工作流。 Web.config 文件包含 WCF 服务配置 - 这是定义工作流服务终结点和行为的位置。
图 25 显示了 Visual Studio 2010 设计器在 Service1.xamlx 文件中的外观。 请注意,它只是标准工作流设计器,仅在此特定情况下,我们正在设计的工作流将用作 WCF 服务实现。 将此工作流定义与 WCF 集成的关键是新的 WorkflowServiceHost 和 WCF“消息传递”活动集 (见图 25 中的工具箱) 。
图 25:在 Visual Studio 2010 中设计工作流服务
随此项目模板一起提供的主干工作流服务包含一个 Receive 活动,后跟一个 Send 活动。 请注意,Receive 活动包含 OperationName 属性,并且当前设置为“GetData”。 它还具有 Content 属性,用于将传入消息绑定到 Sequence 活动中定义的局部变量 - 在本例中,变量称为“数据”,其类型为 Int32, (见图 25) 中工作流设计服务下方的“变量”窗口。
Receive 活动还配置为激活服务,这意味着传入消息可能会导致创建工作流的新实例。 请注意,CanCreateInstance 属性在图 25) 右侧的属性窗口中选中。
让我们稍微自定义一下此工作流服务。 首先,我将服务文件名从 Service1.xamlx 更改为 HelloWorld.xamlx。 接下来,在适当窗口中将服务名称更改为“HelloWorldService”。 接下来,我将 Receive 活动的 OperationName 属性更改为“SayHello”。 最后,我将定义序列中 String 类型的名为“personName”的新变量。
然后,我将 Receive 活动的 Content 属性绑定到“personName”变量,如图 26 所示。 接下来,我将 Send 活动的 Content 属性绑定到格式为“hello {0}!” 将 personName 变量插入占位符。 然后保存生成的 Service1.xamlx 文件。
图 26:定义 Receive 活动的 Content 属性
此时,继续打开 HelloWorld.xamlx 文件并检查其内容。 为此,可以右键单击解决方案资源管理器中的文件,然后选择“打开方式...” 后跟“XML 编辑器”。 这使你可以查看服务的原始 XAML 定义。 请务必注意,XAML 定义表示完整的服务实现。 根本没有 C# 代码。
对于此示例,我们将依赖于默认的 HTTP 终结点,并假设我们有一个标准服务行为,该行为会自动启用服务元数据,因此我们不需要任何 WCF 配置。
现在,我们已准备好测试基于 XAML 的工作流服务。 这与在 Visual Studio 2010 中按 F5 一样简单。 按 F5 会将工作流服务加载到 ASP.NET Development Server 中,并导致 WCF 测试客户端出现。 WCF 测试客户端自动连接到工作流服务,下载 WSDL 元数据,并可以测试工作流服务逻辑 (见图 27) 。
图 27:测试工作流服务
这表明工作流服务基础结构能够通过检查工作流定义中使用的发送和接收活动及其发送和接收的消息类型来动态生成服务的 WSDL 定义。
这将完成在 .NET 4 中生成第一个声明性工作流服务的简单演练。 我们发现服务实现完全是在 XAML 中定义的,并且你使用发送/接收活动为工作流中的消息传递交互建模。 我们还发现,.NET 4 附带了对 .xamlx 文件的托管支持,因此不需要其他 .svc 文件。 我们将在以下各节中继续更详细地探讨其中每个方面。
承载工作流服务
.NET 4 附带了用于工作流服务的全新和改进的托管基础结构。 可以使用 WorkflowServiceHost 在 IIS/WAS 或你自己的应用程序中托管工作流服务。 .NET 4 托管基础结构提供了管理长时间运行的工作流实例以及同时运行多个服务实例时关联消息的必要部分。 .NET 4 还提供用于远程管理工作流实例的标准工作流控制终结点。
在 IIS/ASP.NET 中托管
我们在上一部分中介绍的简单 HelloWorldWorkflowService 示例演示了如何在 IIS/ASP.NET 中托管工作流服务。 尽管我们使用 ASP.NET Development Server 测试了该服务,但它在 IIS 中使用 ASP.NET 4 应用程序池的工作方式相同。 .NET 4 为 .xamlx 请求安装必要的处理程序,该处理程序在后台创建 WorkflowServiceHost 以及激活各个工作流实例。
尽管我们在第一个示例中使用了“零配置”方法,但你可以像配置任何其他 WCF 服务一样在Web.config中配置工作流服务。 这是配置要使用的任何 WCF 终结点和行为的位置。 可以在工作流服务上公开任意数量的终结点,但应考虑使用管理特定于实例的通信的 BasicHttpContextBinding、WSHttpContextBinding 或 NetTcpContextBinding) (新的“上下文”绑定之一。
以下示例演示如何使用两个 HTTP 终结点配置工作流服务:
<configuration>
<system.serviceModel>
<services>
<service name=“HelloWorldWorkflowService”>
<endpoint binding=“basicHttpContextBinding” contract=“IHelloWorld”/>
<endpoint address=“ws” binding=“wsHttpContextBinding”
contract=“IHelloWorld”/>
</service>
...
还可以使用 WCF 行为配置工作流服务。 以下示例演示如何使用一些标准 WCF 行为配置此工作流服务:
<configuration>
<system.serviceModel>
<services>
<service name=“HelloWorldWorkflowService”
behaviorConfiguration=“HelloWorldWorkflowService.Service1Behavior” >
<endpoint binding=“basicHttpContextBinding” contract=“IHelloWorld”/>
<endpoint address=“ws” binding=“wsHttpContextBinding”
contract=“IHelloWorld”/>
</service>
</服务>
<behaviors>
<serviceBehaviors>
<behavior name=“HelloWorldWorkflowService.Service1Behavior”>
<serviceDebug includeExceptionDetailInFaults=“False” />
<serviceMetadata httpGetEnabled=“True”/>
</行为>
</serviceBehaviors>
...
除了这些标准 WCF 行为之外,.NET 4 还附带了一些特定于 WF 的新行为,用于与工作流服务一起控制工作流持久性、工作流跟踪和其他工作流运行时行为。 有关更多详细信息,请参阅 .NET 4 SDK 示例。
使用 WorkflowServiceHost 进行自承载
尽管 .NET 3.5 附带了用于托管工作流服务的 WorkflowServiceHost 类,但考虑到对 .NET 4 中的 WF 运行时和编程模型所做的所有更改,需要对其进行重新设计。 因此,.NET 4 附带 System.ServiceModel.Activities 程序集中的新 WorkflowServiceHost 类。
使用 WorkflowServiceHost 类可以轻松地在自己的应用程序中托管工作流服务,无论是控制台应用程序、WPF 应用程序还是 Windows 服务。 以下代码片段演示如何在自己的应用程序代码中使用 WorkflowServiceHost:
...
WorkflowServiceHost host = new WorkflowServiceHost (“HelloWorld.xamlx”,
new Uri (“https://localhost:8080/helloworld") ) ;
主机。AddDefaultEndpoints () ;
主机。Description.Behaviors.Add (
new ServiceMetadataBehavior { HttpGetEnabled = true }) ;
主机。Open () ;
Console.WriteLine (“主机已打开”) ;
Console.ReadLine();
...
可以看到,无论你选择在 IIS/ASP.NET 中托管工作流服务,还是在你自己的应用程序中托管工作流服务,它们都与任何 WCF 服务一样易于托管。
WorkflowControlEndpoint
托管工作流服务负责初始化 WF 运行时,并在激活通过公开的终结点之一到达的消息时激活新的工作流实例。 除了此基本托管功能外,能够远程管理这些正在运行的工作流实例的配置和执行也很重要。 .NET 4 通过提供可在工作流服务上公开的标准 WorkflowControlEndpoint,使此操作变得容易。
以下示例演示如何在自定义托管方案中将标准工作流控制终结点添加到服务:
...
WorkflowServiceHost 主机 = new WorkflowServiceHost (“HelloWorld.xamlx”,
new Uri (“https://localhost:8080/helloworld") ) ;
主机。AddDefaultEndpoints () ;
WorkflowControlEndpoint wce = new WorkflowControlEndpoint (
new NetNamedPipeBinding () ,
new EndpointAddress (“net.pipe://localhost/helloworld/WCE”) ) ;
主机。AddServiceEndpoint (wce) ;
主机。Open () ;
...
如果要在 IIS/ASP.NET 中托管,则可以添加标准 WorkflowControlEndpoint,如下所示:
<configuration>
<system.serviceModel>
<services>
<service name=“HelloWorldWorkflowService”
behaviorConfiguration=“HelloWorldWorkflowService.Service1Behavior” >
<endpoint address=“” binding=“basicHttpContextBinding” contract=“IHelloWorld”/>
<endpoint address=“ws” binding=“wsHttpContextBinding” contract=“IHelloWorld”/>
<endpoint address=“wce” binding=“wsHttpBinding” kind=“workflowControlEndpoint”/>
</service>
...
使用 WorkflowControlEndpoint,可以在不同的托管环境中管理工作流服务。 Windows Server AppFabric 提供了一个这样的环境,该应用将在 Windows Server 的未来版本中提供。
发送/接收活动
.NET 3.5 附带了两个消息活动- 发送和接收 - 用于使用 WCF 发送和接收消息,但它们的功能相当有限。 .NET 4 通过一些附加功能 ((例如关联) )增强了发送和接收活动,并添加了一些额外的消息传递活动(SendReply 和 ReceiveReply),可简化请求-回复操作建模。
在工作流服务中使用发送/接收活动时,实质上是使用它们来为将通过 WSDL 定义向客户端公开的服务协定建模。 使用 Receive 活动时,请为其指定操作名称,并将传入消息映射到 .NET 类型。 尽管到目前为止,我们一直在使用简单字符串,但你也可以使用复杂的用户定义的数据协定。 让我们更新 HelloWorldWorkflowService 以使用以下数据协定类型:
[DataContract (Namespace=“”) ]
public class Person
{
[DataMember]
公共字符串 ID;
[DataMember]
公共字符串 FirstName;
[DataMember]
公共字符串 LastName;
}
如果将此类定义添加到 Web 项目并返回到 HelloWorld.xamlx,则可以定义一个名为“personMsg”类型的新变量, (上面定义的数据协定) 。 然后,可以通过“Content”属性将 ReceiveRequest 和 SendResponse 活动映射到 personMsg 变量。 为此,只需选择每个活动并按“内容”按钮即可打开“内容定义”窗口。 然后,在“消息数据”文本框中输入“personMsg”,在“消息类型”文本框中输入“Person”。 你需要为这两个活动执行此操作。
发送/接收活动还可以配置 WCF 服务的其他方面,例如操作名称、服务协定名称、操作、已知类型的集合、保护级别和在运行时使用的序列化程序 (例如 DataContractSerializer 与 XmlSerializer) 。 在“发送”活动上,还可以指定目标终结点详细信息。 选择“发送/接收”活动时,可以通过属性窗口配置所有这些设置。
完成这些更改后,可以再次测试 HelloWorld.xamlx,以查看 SayHello 操作现已定义为接收和返回 Person 类型的消息。
定义Request-Reply操作
为了更轻松地为请求-回复操作建模,.NET 4 引入了一些新活动来为这些类型的交互建模。 一个是 SendReply 活动,你可以将其与 Receive 活动组合在一起,以在服务中实现请求-回复操作。 在工作流中调用外部服务时,还有一个 ReceiveReply 活动,你可以将其与 Send 活动合并。
.NET 4 还附带了一些更高级别的活动,称为 ReceiveAndSendReply 和 SendAndReceiveReply,你将在 Visual Studio 2010 工具箱中看到这些活动。 这些活动只需分别使用 SendReply/ReceiveReply 编写 Receive/Send,并使其易于使用。 将它们拖到工作流设计图面上时,你将看到它们扩展到一个序列,其中包含发送或接收活动,后跟相应的“回复”活动。
中的
为了在使用外部服务时简化操作,Visual Studio 2010 还提供了一个“添加服务引用”功能,该功能的工作方式与预期相同,只是它不会生成客户端代理类定义,而是生成一组客户端活动。 选择“添加服务引用”并指定 WSDL 定义的地址后,Visual Studio 将下载 WSDL,并为 WSDL 定义中找到的每个操作生成自定义活动。
让我们看一个简单的示例。 假设有一个 CalculatorService,其中包含要从工作流服务中使用的添加、减法、乘法和除法运算。 只需选择“添加服务引用”并指定 CalculatorService WSDL 定义的位置,如图 28 所示。
图 28:添加服务引用
按“确定”添加服务引用后,Visual Studio 将下载 WSDL 定义并生成四个将显示在工具箱中的自定义活动。 现在,你已拥有易于在工作流中使用来调用外部服务的“加”、“减”、“乘”和“除法”活动。
关联
构建由长时间运行的工作流服务组成的系统时,通常会让单个工作流服务的多个实例同时运行,等待同一事件发生 (例如,等待特定消息到达,然后继续) 。 此方案的挑战在于,需要一种方法将传入的消息与正确的工作流实例相关联。 通常,确定正确的工作流实例的方式是检查传入消息的内容。
.NET 4 附带了一个基于内容的复杂消息关联功能,可与我们刚刚讨论的发送和接收活动结合使用。 此功能的实现围绕所谓的“关联句柄”(.NET 4 中的另一个新概念)展开。
若要开始使用相关性,必须先定义 CorrelationHandle 类型的工作流变量。 可以将此变量视为连接来自 (的一段数据(可能) 两个不同消息 (由两个不同的消息传送活动) 处理)的交会点。 Send 和 Receive 活动都提供 CorrelatesWith 属性,用于指定 CorrelationHandle 变量。
若要关联多个发送/接收活动发送的消息,必须在希望参与关联的所有活动中指定相同的关联句柄。 然后,在每个活动上配置将映射到该特定活动正在处理的消息的相关键, (例如,一条消息中的 SSN 元素可以与另一条消息中的 CustomerId 元素关联) 。 可以使用针对消息计算的 XPath 表达式定义相关键。
让我们扩展 HelloWorldWorkflowService,查看其工作原理的示例。 目前,我们一直在处理的示例接收一条 Person 消息,并将其返回给客户端。 让我们在工作流底部的 SendResponse 活动下方添加另一个 ReceiveAndSendReply 活动。 执行此操作会添加包含另一个 Receive 的 Sequence,后跟另一个 SendReply。 我将为 Receive 活动提供操作名称“Finish”,我们将要求客户端在 Finish 消息中提供用于构造最终问候语响应的称呼。
换句话说,客户端将首先调用 SayHello,提供其全名来启动新的工作流服务实例。 然后,工作流实例将等到客户端调用 Finish 来提供称呼 (,这可能需要几分钟、小时或数天的时间,在此期间工作流可以保留) 。 客户端调用 Finish 以提供称呼后,工作流将使用称呼和原始名称生成问候语并返回它。 在此方案中,我们可以轻松地让多个正在运行的工作流实例等待调用 Finish 操作,因此我们肯定需要将 Finish 消息与前面的 SayHello 消息相关联。 由于存在这种情况,我需要将“Finish”的 Receive 活动与我们在“SayHello”活动上指定的相同关联句柄相关联, (nameHandle) 。
现在,让我们看一下要在这两个活动中关联的消息内容。 对于此示例,我将使用以下两种数据协定类型来为消息建模:
[DataContract (Namespace=“”) ]
public class Person
{
[DataMember]
公共字符串 FirstName { get; set; }
[DataMember]
公共字符串 LastName { get; set; }
}
[DataContract (Namespace = “”) ]
public 类问候语
{
[DataMember]
public string Salutation { get; set; }
[DataMember]
public string NameKey { get; set; }
}
“SayHello”的接收活动配置为使用 Person 消息,“完成”的接收活动配置为使用问候消息。 我们将假定 LastName 值始终是唯一的,因此我们可以将其用作关联的唯一值。 因此,当客户端发送“完成”消息时,它必须提供在前面的“SayHello”消息中使用的相同 LastName 值。
现在,让我们看看如何配置相关句柄来设置此项。 我已经在名为“handle”的序列中定义了 CorrelationHandle 变量。 我们需要做的第一件事是“初始化”“SayHello”Receive 活动中的相关句柄。 因此,选择“SayHello”接收活动,然后按“CorrelationInitializers”文本框旁边的按钮。
此处,需要从下拉框中选择“查询相关初始值设定项”,然后应该能够为查询字段选择“LastName”属性, (应生成“sm:body () /xg0:Person/xg0:LastName”的 XPath 表达式,如图 29) 所示。
然后,我们需要指定具有 的“完成”接收活动在同一句柄上关联。 选择“完成”接收活动,然后按“CorrelatesOn”按钮。 然后为“CorrelatesWith”句柄指定“句柄”,然后为查询字段选择“NameKey”, (请参阅图 30) 。
图 29:定义“SayHello”的相关键
图 30:定义“Finish”的相关键
这最终意味着 Person 消息中的 LastName 元素需要跨两个单独的请求匹配 Greeting 消息中的 NameKey 元素。 这样,工作流基础结构将能够自动关联消息,并根据“LastName/NameKey”) 将传入的“完成”消息路由到正确的工作流实例 (。
最终的 SendReply 活动向调用方返回以下格式化字符串,包括原始“personMsg”和新的“greetingMsg”中的信息:
String.Format (“{0}{1}{2}!”,
greetingMsg.Salutation、personMsg.FirstName、personMsg.LastName)
我将使用 WCF 测试客户端通过多次使用不同的 FirstName 和 LastName 值调用 SayHello 来启动多个工作流实例来测试相关功能。 执行此操作后,应有多个正在运行的工作流服务的实例。 现在,我们可以调用 Finish 操作,指定在一条 SayHello 消息中使用的相同 LastName 值之一,应看到最终问候语中使用的相应 FirstName 值返回到客户端 (请参阅图 31) 。
这说明了基于内容的操作关联,这是 .NET 4 附带的一项引人注目的工作流服务功能。 请务必注意,还可以根据通道和协议级数据执行关联,就像在 .NET 3.5 (一样,例如,使用通道或工作流实例 ID) 。 基于内容的关联是一种更灵活、更复杂的方法来执行相同操作。
图 31:测试基于内容的关联示例
这让我们结束了工作流服务覆盖。 我们仅能够在此文中介绍工作流服务,但你将能够在 .NET 4 SDK 示例和在线查找的日益增多的 MSDN 文档中找到其他信息。 如你所看到的,WCF 和 WF 4 的组合为全新的开发模型打开了大门,用于构建声明性、长时间运行的异步服务,这些服务可以享受这两个框架必须提供的最佳功能。
其他高级功能
除了本文中介绍的其他所有内容外,WCF 4 还附带了一些更高级的功能,你可能会觉得这些功能很有用。 这些高级功能包括通过 DataContractResolver 改进的类型解析支持、使用 ReceiveContext 处理争用队列使用者、新的字节流编码器以及基于 ETW 的高性能跟踪。
使用 DataContractResolver 进行类型解析
在 WCF 3.x 中,有一个称为“已知类型”的类型解析功能。 在反序列化期间,当序列化程序遇到与声明的类型不相同的实例时,它会检查声明的“已知类型”列表,以确定要使用的类型。 作为服务的作者,可以使用 [KnownType] 或 [ServiceKnownType] 属性批注类型/方法,以定义可能替换的列表。 此功能通常用于支持继承和多态性。
遗憾的是,在运行时执行此类动态类型解析时,WCF 3.x 不提供替代 DataContractSerializer 使用的类型映射算法的简单方法。 为了解决此问题,WCF 4 提供了抽象的 DataContractResolver 类,可以从该类派生,以实现自己的自定义类型解析算法。 下面展示了如何开始:
MyDataContractResolver 类: DataContractResolver
{
程序集程序集;
public MyDataContractResolver (程序集程序集)
{
this.assembly = assembly;
}
在反序列化时使用
允许用户将 xsi:type 名称映射到任何类型
public 重写 Type ResolveName (string typeName, string typeNamespace,
Type declaredType、DataContractResolver knownTypeResolver)
{
... // 实现映射
}
在序列化时使用
将任意类型映射到新的 xsi:type 表示形式
public override bool TryResolveType (Type type, Type declaredType,
DataContractResolver knownTypeResolver, out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace)
{
... // 实现映射
}
}
实现自定义 DataContractResolver 后,可以将其提供给 DataContractSerializer,如下所示:
DataContractSerializer dcs = new DataContractSerializer (
typeof (Object) ,null,int。MaxValue、false、true、null、
new MyDataContractResolver (assembly) ) ;
现在,使用此 DataContractSerializer 实例序列化/反序列化对象时,将调用自定义 DataContractResolver 来执行自定义类型解析。
若要将自定义 DataContractResolver 注入到后台的 WCF 运行时,需要编写一个 WCF 协定行为,该行为插入 DataContractSerializerOperationBehavior 并重写默认解析程序。 .NET 4 SDK 附带了一个完整的示例,演示如何通过协定行为和名为 [KnownAssembly] 的自定义属性来实现此目的。
如果要替代 DataContractSerializer 默认值、精确控制用于序列化的类型或动态管理已知类型,则自定义类型解析非常有用。
使用 ReceiveContext 处理排队消息
使用 WCF 3.x,通过使用事务队列并在 MSMQ 事务中登记 WCF 服务操作,可以保证使用 NetMsmqBinding 时消息的精确一次传递。 处理消息时引发异常时,WCF 将通过将消息返回到队列来确保消息不会丢失, (消息可能会返回到发起队列、有害消息队列或死信队列,具体取决于配置) 。
虽然此功能很重要,但人们通常会遇到一些问题。 一个是事务成本高昂,它会产生大量读取/写入队列,这会带来更多的开销和复杂性。 另一个问题是,无法确保同一服务在下次从队列 (拉取消息时处理消息,例如,特定服务无法“锁定”消息) ,这是在某些情况下可能需要做出的保证。
为了解决这些问题,WCF 4 引入了一个名为 ReceiveContext 的新 API,用于处理排队的消息。 使用 ReceiveContext,服务可以“速览”队列上的消息以开始处理该消息,如果出现任何问题并引发异常,它将保留在队列中。 服务还可“锁定”消息以便在稍后的某个时间点重试处理。 ReceiveContext 提供了一种机制,用于在处理消息后“完成”消息,以便可以从队列中删除该消息。
此方法在多个方面简化了操作,因为消息不再通过网络读取和重新写入队列,并且单个消息在处理过程中不会跨不同的服务实例弹跳。 让我们看一个简单的示例来说明它的工作原理。
以下示例演示如何使用 ReceiveContext 处理到达队列的消息:
...
[OperationBehavior (TransactionScopeRequired = true, TransactionAutoComplete = true) ]
[ReceiveContextEnabled (ManualControl = true) ]
public void CalculateProduct (int firstNumber,int secondNumber)
{
ReceiveContext receiveContext;
如果 (!ReceiveContext.TryGet (OperationContext.Current.IncomingMessageProperties,
out receiveContext) )
{
Console.WriteLine (“未在此计算机上安装/找到 ReceiveContext”。) ;
return;
}
if ( (firstNumber * secondNumber) % 2 == (receiveCount % 2) )
{
receiveContext.Complete (TimeSpan.MaxValue) ;
Console.WriteLine (“{0} x {1} = {2}”, firstNumber, secondNumber,
firstNumber * secondNumber) ;
}
else
{
receiveContext.Abandon (TimeSpan.MaxValue) ;
Console.WriteLine (“{0}&{1} not processed”, firstNumber, secondNumber) ;
}
receiveCount++;
}
...
假设成功通过,我们将调用 ReceiveContext.Complete 以完成处理导致消息从基础队列中删除的消息。 否则,我们将调用“放弃”,这将消息保留在队列中供将来重试。 .NET 4 SDK 附带了一个完整的示例,可用于更深入地探索这项新功能。
使用 ByteStreamMessageEncodingBindingElement 简化编码
在某些消息传送方案中,你只想传输二进制数据的“blob”,而无需进行任何包装或其他处理。 为了简化此方案,WCF 4 附带了一个新的 ByteStreamMessageEncodingBindingElement,用于执行此操作。 遗憾的是,.NET 4 不附带此编码器的新绑定,因此必须创作自定义绑定才能使用它。
.NET 4 SDK 附带一个完整的示例,演示如何通过自定义绑定类定义使用 ByteStreamMessageEncodingBindingElement 的自定义绑定。 有了新的绑定类后,字节流编码器将变得非常易于与服务一起使用。
基于 ETW 的高性能跟踪
WCF 4 还在跟踪和诊断方面进行了一些改进。 WCF 4 现在使用 ETW 进行跟踪,这大大提高了跟踪性能,并提供了与其他相关技术(如 Windows Workflow Foundation、Windows Server AppFabric)和 Windows Server 中各种管理技术的更好的集成,从而为平台提供了更好的模型。
使用 ETW,事件提供程序将事件写入会话,会话可以选择将这些事件保存到日志中。 使用者可以侦听实时会话事件,或者在事后从日志中读取这些事件。 ETW 控制器负责启用/禁用会话并将提供程序与会话关联。
.NET 4 SDK 提供了一个简单的示例,演示如何将此新的高性能跟踪体系结构与 WCF 服务结合使用。
结论
WCF 4 带来了许多改进和一些全新的功能,适用于当今一些最常见的通信方案。 首先,通过简化的配置模型和对常见默认值的更好支持,WCF 4 更易于使用。
此外,WCF 4 为服务发现和路由提供一流的支持,这是大多数企业环境和大型 SOA 计划中的常见要求 - 仅这些功能就使 WCF 与许多相互竞争的框架区分开来。 WCF 4 还简化了基于 REST 的服务开发,并提供了一些其他更高级的 WCF 功能,以解决目前一些特定的难题。
除此之外,WCF 4 还提供与 WF 的复杂集成,以便为开发声明性工作流服务提供新模型。 工作流服务使开发长期运行的异步服务成为可能,这些服务受益于 WF 编程模型和基础运行时。 由于 Visual Studio 2010 中新的基于 WCF 的活动和设计器支持,这一新的编程模型正成为创作服务的一流选项。
在 .NET 4 中,WCF 和 WF 的世界正在合并在一起,以提供一个连贯的编程模型,为你提供最好的两个世界必须提供。 有关 WF 4 中的新增功能的详细信息,检查 .NET 4 中 Windows Workflow Foundation 开发人员简介。
关于作者
Aaron Skonnard 是 Pluralsight 的联合创始人,Pluralsight 是一家 Microsoft 培训提供商,提供讲师引导式和按需 .NET 开发人员课程。 这些天,亚伦花大部分时间录制 Pluralsight On-Demand! 课程重点介绍云计算、Windows Azure、WCF 和 REST。 Aaron 花了数年时间撰写、演讲和教授世界各地的专业开发人员。 你可以通过 http://pluralsight.com/aaron 和 http://twitter.com/skonnard联系他。