65.9K
CodeProject 正在变化。 阅读更多。
Home

基于内容的路由(使用 WCF 4)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (13投票s)

2010 年 7 月 29 日

CPOL

5分钟阅读

viewsIcon

76820

downloadIcon

917

本文解释了如何使用 WCF 4 的 RoutingService 实现基于内容的路由。

引言

本文介绍了如何使用 WCF 进行基于内容的路由。基于内容的路由实现了 SOA 的四大基石之一,即模式和契约可以共享,但类不能。在面向消息的应用程序中,客户端只需要关心它需要提交的消息和一个负责路由的消息服务,即将消息提交到下游合适的 WCF 服务之一。因此,对外界而言,只暴露了路由服务和契约。这消除了点对点通信的需求,并使 WCF 的行为更像一个服务总线。

背景

要正确理解本文,需要具备 C# 4.0、WCF 4.0 和 ASP.NET 的背景知识。最好对 SOA 和 ESB 有基本的理论理解。

Using the Code

要使用 WCF 4.0 实现基于内容的路由,需要完成以下步骤:

  1. 创建一个名为 ContentBasedRouting 的空白 Visual Studio 2010 解决方案。
  2. 添加一个名为 CustomerContract 的类库项目。
  3. Class1.cs 文件重命名为 ICustomer.cs
  4. 在此文件中放入以下代码:
  5. 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,其中 OperationContractGetCustomerDetails,它接受一个 Customer 对象作为参数。我们声明了一个 DataContract,即类 Customer,它具有三个 DataMember 作为自动属性。

  6. 接下来,我们添加对 System.ServiceModel.dllSystem.ServiceModel.Description.dllSystem.Runtime.Serialization.dllSystem.ServiceModel.Web.dll 的引用。
  7. 向解决方案中添加一个名为 PremiumCustomerService 的 WCF 服务应用程序。同时,在 IIS 7.0 中创建一个虚拟目录,其物理路径指向 PremiumCustomerService 文件夹。
  8. 将 SVC 文件重命名为 PremiumCustomerService.svc。它应该包含以下代码:
  9. <%@ ServiceHost Language="C#" Debug="true" 
        Service="PremiumCustomerService.PremiumCustomerService" 
        CodeBehind="PremiumCustomerService.svc.cs" %>
  10. 转到代码隐藏文件并添加以下代码:
  11. 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() 方法。

  12. 接下来,需要修改 web.config 文件。web.config 文件应包含以下内容:
  13. <?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

  14. 现在向解决方案中添加另一个名为 OrdinaryCustomerService 的 WCF 服务应用程序。
  15. .svc 文件重命名为 OrdinaryCustomerService.svc。该文件应包含以下内容:
  16. <%@ ServiceHost Language="C#" Debug="true" 
        Service="OrdinaryCustomerService.OrdinaryCustomerService" 
        CodeBehind="OrdinaryCustomerService.svc.cs" %>
  17. 修改代码隐藏文件以包含以下代码:
  18. 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() 方法。

  19. 接下来,相应的 web.config 文件应进行修改以包含以下内容:
  20. <?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

  21. 向解决方案中添加另一个名为 CustomerService 的 WCF 服务应用程序。现在,我们将在此定义路由服务、过滤器和 XPath 表达式。
  22. 添加对 System.ServiceModel.Routing.dll 的引用。
  23. CustomerService.svc 文件应包含以下内容:
  24. <%@ 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 程序集中。我们将再次编译并构建解决方案。

  25. 现在,我们介绍最重要的部分,即路由服务的 web.config 文件。在此,我们为服务行为定义了 filterTable,并在 routing 元素下添加了过滤器和 XPath 表达式。服务在 web.configclient 部分下定义。web.config 应包含以下内容:
  26. <?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 公共属性的内容用于确定消息将随后转发到的下游服务的终结点。

  27. 现在,我们可以在 IIS 管理器中测试该服务,看到 URL 为 https:///CustomerService/CustomerService.svc
  28. 接下来,我们向解决方案中添加一个名为 ProtocolBridgingClient 的 Windows Forms 应用程序,并为路由服务添加一个服务引用:https:///CustomerService/CustomerService.svc。我们还添加了对 System.ServiceModel.dll 的引用。
  29. 窗体的代码隐藏文件 ProtocolBridgingForm.cs 应包含以下代码:
  30. 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,它不仅具有路由功能,还具有转换能力。

© . All rights reserved.