基于内容的路由(使用 WCF 4)
本文解释了如何使用 WCF 4 的 RoutingService 实现基于内容的路由。
引言
本文介绍了如何使用 WCF 进行基于内容的路由。基于内容的路由实现了 SOA 的四大基石之一,即模式和契约可以共享,但类不能。在面向消息的应用程序中,客户端只需要关心它需要提交的消息和一个负责路由的消息服务,即将消息提交到下游合适的 WCF 服务之一。因此,对外界而言,只暴露了路由服务和契约。这消除了点对点通信的需求,并使 WCF 的行为更像一个服务总线。
背景
要正确理解本文,需要具备 C# 4.0、WCF 4.0 和 ASP.NET 的背景知识。最好对 SOA 和 ESB 有基本的理论理解。
Using the Code
要使用 WCF 4.0 实现基于内容的路由,需要完成以下步骤:
- 创建一个名为 ContentBasedRouting 的空白 Visual Studio 2010 解决方案。
- 添加一个名为 CustomerContract 的类库项目。
- 将 Class1.cs 文件重命名为 ICustomer.cs。
- 在此文件中放入以下代码:
- 接下来,我们添加对 System.ServiceModel.dll、System.ServiceModel.Description.dll、System.Runtime.Serialization.dll 和 System.ServiceModel.Web.dll 的引用。
- 向解决方案中添加一个名为 PremiumCustomerService 的 WCF 服务应用程序。同时,在 IIS 7.0 中创建一个虚拟目录,其物理路径指向 PremiumCustomerService 文件夹。
- 将 SVC 文件重命名为 PremiumCustomerService.svc。它应该包含以下代码:
- 转到代码隐藏文件并添加以下代码:
- 接下来,需要修改 web.config 文件。web.config 文件应包含以下内容:
- 现在向解决方案中添加另一个名为 OrdinaryCustomerService 的 WCF 服务应用程序。
- 将 .svc 文件重命名为 OrdinaryCustomerService.svc。该文件应包含以下内容:
- 修改代码隐藏文件以包含以下代码:
- 接下来,相应的 web.config 文件应进行修改以包含以下内容:
- 向解决方案中添加另一个名为 CustomerService 的 WCF 服务应用程序。现在,我们将在此定义路由服务、过滤器和 XPath 表达式。
- 添加对 System.ServiceModel.Routing.dll 的引用。
- CustomerService.svc 文件应包含以下内容:
- 现在,我们介绍最重要的部分,即路由服务的 web.config 文件。在此,我们为服务行为定义了
filterTable
,并在routing
元素下添加了过滤器和 XPath 表达式。服务在 web.config 的client
部分下定义。web.config 应包含以下内容: - 现在,我们可以在 IIS 管理器中测试该服务,看到 URL 为 https:///CustomerService/CustomerService.svc。
- 接下来,我们向解决方案中添加一个名为 ProtocolBridgingClient 的 Windows Forms 应用程序,并为路由服务添加一个服务引用:https:///CustomerService/CustomerService.svc。我们还添加了对 System.ServiceModel.dll 的引用。
- 窗体的代码隐藏文件 ProtocolBridgingForm.cs 应包含以下代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.Runtime.Serialization;
namespace CustomerContract
{
[ServiceContract]
public interface ICustomer
{
[OperationContract]
string GetCustomerDetails(Customer cust);
}
[DataContract]
public class Customer
{
[DataMember]
public string CustomerID { get; set; }
[DataMember]
public string CustomerName { get; set; }
[DataMember]
public string CustomerCreditRating { get; set; }
}
}
在此,我们声明了一个 ServiceContract
,即接口 ICustomer
,其中 OperationContract
为 GetCustomerDetails
,它接受一个 Customer
对象作为参数。我们声明了一个 DataContract
,即类 Customer
,它具有三个 DataMember
作为自动属性。
<%@ ServiceHost Language="C#" Debug="true"
Service="PremiumCustomerService.PremiumCustomerService"
CodeBehind="PremiumCustomerService.svc.cs" %>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace PremiumCustomerService
{
// NOTE: You can use the "Rename" command on the "Refactor" menu
// to change the class name "Service1"
// in code, svc and config file together.
public class PremiumCustomerService : CustomerContract.ICustomer
{
public string GetCustomerDetails(CustomerContract.Customer cust)
{
return "Customer ID = " + cust.CustomerID +
" Premium CustomerName = " +
cust.CustomerName + " CustomerCreditRating = " +
cust.CustomerCreditRating;
}
}
}
在此,我们添加了一个实现 ICustomer
接口的类 PremiumCustomerService
,并随后实现了 GetCustomerDetails()
方法。
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_ICustomer" portSharingEnabled="true">
<security mode="None" />
</binding>
</netTcpBinding>
<basicHttpBinding>
<binding name="BasicHttpBinding_ICustomer">
<security mode="None"/>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="PremiumCustomerService">
<endpoint address="PremiumCustomerService.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ICustomer"
contract="CustomerContract.ICustomer"/>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https:///PremiumCustomerService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below
to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
在此,我们使用 basicHttpBinding
定义了 PremiumCustomerService
。现在服务已准备就绪。此时,编译并构建解决方案。现在我们可以从 IIS 管理器浏览该服务,看到 URL 为 https:///PremiumCustomerService/PremiumCustomerService.svc。
<%@ ServiceHost Language="C#" Debug="true"
Service="OrdinaryCustomerService.OrdinaryCustomerService"
CodeBehind="OrdinaryCustomerService.svc.cs" %>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace OrdinaryCustomerService
{
// NOTE: You can use the "Rename" command on the "Refactor"
// menu to change the class name "Service1"
// in code, svc and config file together.
public class OrdinaryCustomerService : CustomerContract.ICustomer
{
public string GetCustomerDetails(CustomerContract.Customer cust)
{
return "Customer ID = " + cust.CustomerID +
" Ordinary CustomerName = " +
cust.CustomerName + " CustomerCreditRating = " +
cust.CustomerCreditRating;
}
}
}
在此,我们添加了一个名为 OrdinaryCustomerService
的类,它实现了 ICustomer
接口,因此也实现了 GetCustomerDetails()
方法。
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_ICustomer" portSharingEnabled="true">
<security mode="None" />
</binding>
</netTcpBinding>
<basicHttpBinding>
<binding name="BasicHttpBinding_ICustomer">
<security mode="None"/>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="OrdinaryCustomerService">
<endpoint address="OrdinaryCustomerService.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ICustomer"
contract="CustomerContract.ICustomer"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https:///OrdinaryCustomerService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below
to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
我们已经完成了另一个名为 OrdinaryCustomerService
的服务。此时,再次编译并构建解决方案。在 IIS 管理器中,我们可以浏览该服务,看到 URL 为 https:///OrdinaryCustomerService/OrdinaryCustomerService.svc。
<%@ ServiceHost Language="C#" Debug="true"
Service="System.ServiceModel.Routing.RoutingService,System.ServiceModel.Routing,
version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
在此,我们使用 System.ServiceModel.Routing.RoutingService
类作为服务。此类预定义在 System.ServiceModel.Routing.dll 程序集中。我们将再次编译并构建解决方案。
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_ICustomer" portSharingEnabled="true">
<security mode="None" />
</binding>
</netTcpBinding>
<basicHttpBinding>
<binding name="BasicHttpBinding_ICustomer">
<security mode="None"/>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint
address="https:///PremiumCustomerService/PremiumCustomerService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICustomer"
contract="*" name="CustomerServiceLibrary_PremiumCustomerService"/>
<endpoint
address="https:///OrdinaryCustomerService/OrdinaryCustomerService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICustomer"
contract="*" name="CustomerServiceLibrary_OrdinaryCustomerService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</client>
<services>
<service behaviorConfiguration="RoutingServiceBehavior"
name="System.ServiceModel.Routing.RoutingService">
<host>
<baseAddresses>
<add baseAddress="https:///CustomerService/"/>
</baseAddresses>
</host>
<endpoint address="" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ICustomer"
contract="System.ServiceModel.Routing.IRequestReplyRouter"
name="RoutingServiceEndpoint">
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="RoutingServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below
to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true"/>
<routing filterTableName="routingRules" routeOnHeadersOnly="False"/>
</behavior>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<routing>
<namespaceTable>
<add prefix="cc"
namespace="http://schemas.datacontract.org/2004/07/CustomerContract" />
</namespaceTable>
<filters>
<filter name="PremiumCustomerFilter" filterType="XPath"
filterData="//cc:CustomerCreditRating = 'Good'"/>
<filter name="OrdinaryCustomerFilter"
filterType="XPath" filterData="//cc:CustomerCreditRating = 'Bad'"/>
</filters>
<filterTables>
<filterTable name="routingRules">
<add filterName="PremiumCustomerFilter"
endpointName="CustomerServiceLibrary_PremiumCustomerService"
priority="0"/>
<add filterName="OrdinaryCustomerFilter"
endpointName="CustomerServiceLibrary_OrdinaryCustomerService"
priority="0"/>
</filterTable>
</filterTables>
<backupLists>
<backupList name="CustomerBackupList">
<add endpointName="CustomerServiceLibrary_OrdinaryCustomerService"/>
</backupList>
</backupLists>
</routing>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
在此,我们看到已经通过过滤器表、过滤器和 XPath 表达式实现了路由。过滤器表实现了路由规则,为使用 XPath 表达式评估的过滤器提供了相应的终结点。Customer
类的 CustomerCreditRating
公共属性的内容用于确定消息将随后转发到的下游服务的终结点。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace ProtocolBridgingClient
{
public partial class ProtocolBridgingForm : Form
{
public ProtocolBridgingForm()
{
InitializeComponent();
}
private void ProtocolBridgingForm_Load(object sender, EventArgs e)
{
try
{
BasicHttpBinding binding = new BasicHttpBinding();
EndpointAddress address = new EndpointAddress(
"https:///CustomerService/CustomerService.svc");
CustomerContract.ICustomer proxy =
ChannelFactory<CustomerContract.ICustomer>.CreateChannel(
binding, address);
CustomerContract.Customer cust1 =
new CustomerContract.Customer { CustomerID = "ar0045855",
CustomerName = "Ambar Ray", CustomerCreditRating = "Good" };
CustomerContract.Customer cust2 =
new CustomerContract.Customer { CustomerID = "am0046067",
CustomerName = "Abhijit Mahato", CustomerCreditRating = "Bad" };
string res1 = proxy.GetCustomerDetails(cust1);
string res2 = proxy.GetCustomerDetails(cust2);
txtCustomer.Text = res1 + "\n" + res2;
}
catch (Exception ex)
{
txtCustomer.Text = (ex.InnerException != null) ?
ex.InnerException.Message + "\t" + ex.Message : ex.Message;
}
}
private void ProtocolBridgingForm_FormClosed(object sender,
FormClosedEventArgs e)
{
GC.Collect();
}
}
}
在窗体加载事件中,我们首先创建一个 BasicHttpBinding
对象,然后创建一个 EndpointAddress
对象,指定路由服务的 URL。然后,我们使用 ChannelFactory.CreateChannel()
方法创建代理对象。我们创建 Customer
对象,其中一个具有 'Good' 的 CustomerCreditRating
,另一个具有 'Bad' 的 CustomerCreditRating
,以便前者最终路由到高级客户服务,后者路由到普通客户服务。然后,我们调用 GetCustomerDetails
方法并将输出收集到富文本框中。
关注点
这可以作为使用 WCF 创建 ESB(企业服务总线)的基础。我们可以为每个模式/契约设置一个路由器服务,并设置相应的下游服务来处理各自的模式/契约。这样,只需将消息提交给相应的路由服务,就可以根据消息的内容确定消息在下游系统中的“路线”。消息的映射可以使用现成的开源代码来实现。因此,可以创建一个轻量级的 ESB,它不仅具有路由功能,还具有转换能力。