<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_The power of ASP.NET</title><subtitle type="text">C#, ASP.NET(MVC),  ADO.NET, JavaScript(JQuery),WCF,SQL server, Windows server, Pattern, Open source...</subtitle><id>http://feed.cnblogs.com/blog/u/5910/rss</id><updated>2011-11-07T01:05:49Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><generator>CNBlogs BlogServer</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/5910/rss"/><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/11/03/2233756.html</id><title type="text">WCF 4.0 进阶系列 – 第十六章 使用回调合约发布和订阅事件（第一部分）</title><summary type="text">到目前为止，本书中你看到的练习与例子都集中于C/S模型。在该模型中，一个服务器提供一个服务，该服务等待客户端主动发出的请求，接收到客户端的请求后，处理请求，然后选择性地向客户端程序发送响应。客户端程序是活动的参与者，提交请求并有效地决定了服务何时开始执行工作。然而这只是大多数的情况，WCF还支持其他的处理体系，比如点对点网络和客户端回调。使用客户端回调，服务可以调用客户端程序中的一个方法，实际上转换了C/S关系中的客户端和服务器，回调时原先的服务端和客户端将发生对调，服务端成为客户端，客户端成为服务端。</summary><published>2011-11-03T01:29:00Z</published><updated>2011-11-03T01:29:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/11/03/2233756.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/11/03/2233756.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;div&gt;到目前为止，本书中你看到的练习与例子都集中于C/S模型。在该模型中，一个服务器提供一个服务，该服务等待客户端主动发出的请求，接收到客户端的请求后，处理请求，然后选择性地向客户端程序发送响应。客户端程序是活动的参与者，提交请求并有效地决定了服务何时开始执行工作。然而这只是大多数的情况，WCF还支持其他的处理体系，比如点对点网络和客户端回调。&lt;/div&gt;&#xD;
&lt;div&gt;在点对点场景中，没有被动的服务。所有参与者都是自主的客户端，因为参与者之间可以平等地通讯。此时，不存在客户端/服务器关系，因此参与者任何时候都应当准备处理向其发送的消息。&lt;/div&gt;&#xD;
&lt;div&gt;使用客户端回调，服务可以调用客户端程序中的一个方法，实际上转换了C/S管理中的客户端和服务器，回调时原先的服务端和客户端将发生对调，服务端成为客户端，客户端成为服务端。在本章，你将调查如何定义客户端回调，以及如何使用客户端回调实现一个简单的事件机制，该事件机制用于通知关注服务状态的客户端服务的状态发生了改变。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;实现和调用一个客户端的回调&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;在传统的C/S协议中，当宿主程序打开ServiceHost对象后，服务在一个或者多个由WCF服务架构建立的端点上侦听消息；然而客户端程序可能期望仅仅接收响应消息，而且该响应消息是客户端通过隐式请求发送到客户端的。一旦客户端打开了与服务之间的通道，WCF运行时将激活服务向客户端发送额外的消息（用于建立通讯），当客户端发出一个已经接收到额外消息的声明后，服务停止发送这类额外消息。WCF提供了两个特性来实现该功能：回调合约和双向通道。使用回调时非常重要的一点是：回调仅仅能在客户端向服务发送请求的处理过程中使用；而且还必须在由客户端初始化和用于发送请求的通道中调用回调。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;定义一个回调合约&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;一个回调合约定义了服务可以在客户端中回调的操作。一个回调合约与服务合约非常相似，因为它们都是一个接口或一个类，其包含的操作标记了OperationContract特性。从语义方面来讲接口和类是有区别的，区别在于是否使用ServcieContract特性。下面的例子定义了一个方法，服务可以调用该方法通知客户端产品的价格发生改变。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111022338036789.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;侦听回调的客户端实现了回调合约中的每个方法。服务识别客户端的回调有两个必要条件：服务实现了服务合约中定义的回调操作，客户端必须引用了该服务合约（实现回调合约的服务合约）。为了达到该目的，你可以使用ServiceContract特性类中的CallbackContract属性标记服务合约，如下面的代码所示：&lt;br /&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111022338039265.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该代码的目的是：客户端程序调用ChangePrice操作以更新某个特定产品的价格；当产品的价格修改后，服务在客户端调用OnPriceChanged操作，然后回传一个修改后的产品到服务。你应当注意IProductsServiceV3服务合约中的其他操作也可以调用OnPriceChanged操作，这是因为回调合约与服务合约捆绑在一起，而不是与服务合约中的讴歌操作捆绑在一起。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;在一个回调合约中实现一个操作&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;当你试图构建一个客户端代理去访问在服务合约中关联了回调合约的服务时，该代理类必须基于System.ServiceModel.DuplexClientBase类。该代理类的结构大致如下：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111022338032820.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上图中黄色方框内的内容显示了与普通代理之间的不同，因为普通的代理不需要定义回调合约。开发人员应创建一个客户端程序，在这个客户端程序中应包含一个类，此类实现了IProductsServiceV3Callback接口和接口的OnPriceChanged方法。&lt;/div&gt;&#xD;
&lt;div&gt;ProductsServiceV3Client代理类扩展了DuplexClientBase&amp;lt;IProductsServiceV3&amp;gt;类，而且它还包含许多构造函数，客户端程序可以使用这些构造函数实例化代理对象。上诉代码片段仅仅显示了其中的两个构造函数，所有构造函数的主要特点都是：第一个参数均为一个InstanceContext对象。这是服务可以调用客户端中操作关键特性。&lt;/div&gt;&#xD;
&lt;div&gt;你现在应该已经非常清楚一个服务中通过InstacneContextMode为该服务指定的"instance context"的含义。如果不清楚也没关系。让我们来回忆一下，服务的每个实例运行在自己的上下文中，并且这些上下文中保存实例的状态信息。服务的每个实例都有自己的上下文。当实例化服务实例时，WCF运行时创建并自动地初始化这些上下文。那么什么时候开始初始化这些上下文呢？下面列出了初始化上下文的三种情况：&lt;/div&gt;&#xD;
&lt;ul&gt;&#xD;
&lt;li&gt;客户端程序开始一个新 会话（如果服务指定PerSession实例上下文模式）&lt;/li&gt;&#xD;
&lt;li&gt;客户端调用服务的一个操作（如果服务指定PerCall实例上下文模式）&lt;/li&gt;&#xD;
&lt;li&gt;服务的宿主程序启动服务（如果服务指定Single实例上下文模式）&lt;/li&gt;&#xD;
&lt;/ul&gt;&#xD;
&lt;div&gt;当客户端连接到一个服务实例时，在客户端和服务之间传输消息的通讯通道中保存了某个特定服务实例的信息，因此WCF运行时可以引导消息到正确的服务实例。&lt;/div&gt;&#xD;
&lt;div&gt;当实现一个客户端回调时，你必须提供相同的功能以使服务端WCF运行时可以把消息路由回正确的客户端。为了实现该目标，你需要创建一个InstanceContext对象，该对象指向一个特定的客户端程序实例，当通过代理连接到服务时把InstaceContext对象传递到服务，客户端的WCF运行时自动地把客户端实例的信息包含在请求消息中，然后把请求消息发送之服务。如果服务需要调用回调合约中的操作，那么服务通过上下文对象直接调用适当的客户端实例。&lt;/div&gt;&#xD;
&lt;div&gt;下面的代码展示了如何在客户端实现客户端代理中IProductsServiceV3Callback接口：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111022338059590.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;InstanceContext的构造器中的this参数指向实现了IProductsServcieV3Callback合约的对象。DoSomeWork方法中创建proxy对象的语句引用了InstanceContext对象。如果服务通过该InstanceContext对象调用OnPriceChanged操作，那么服务端的WCF运行时将调用该客户端实例中的OnPriceChanged方法。&lt;/div&gt;&#xD;
&lt;div&gt;请注意，CallbackClient类还实现了IDisposable接口；Dispose方法用于关闭代理。一旦客户端连接到服务并且发送了一条初始化消息之后，服务可能在任意时刻回调客户端实例。如果客户端程序在DoSomeWork方法中，向服务发送完请求之后立即关闭代理；那么服务试图回调客户端实例时将失败，因为客户端实例对象不再有效。正如上面的代码所展示，如果客户端实例在DoSomwWork方法之后继续存在，那么在Dispose方法中关闭代理对象能确保服务可以在任意时刻回调客户端实例，除非客户端程序终止或者客户端对象被显示地销毁。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;在回调合约中调用一个操作&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;为了调用回调合约中的一个操作，服务必须获取向服务发送请求的客户端实例的引用。正如你刚刚看到的，服务端的WCF运行时通过该服务的操作上下文获取客户端实例信息。你可以通过惊呆书香OperationContext.Current属性访问操作上下文，该属性返回一个OperationContext对象。OperationContext类提供generic的GetCallbackChannel方法，该方法可以返回一个通道的引用；这个通道就是服务和调用服务的客户端之间进行通讯的那个通道。GetCallbackChannel方法的返回值回调合约类型的引用；你可以通过这个引用调用操作。正如下面的代码所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111022338072456.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;很有可能在客户端调用完服务的操作和服务正在回调客户端的时间内，尤其当客户端通过单向方式调用服务的操作后，客户端开始终止或关闭通讯通道。因此你在服务执行回调之前你应该检查通道回调通道是否已经被关闭，如下面黄色方块中的代码所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111022338073852.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;所有的WCF通道都实现了ICommunicationObject接口。该接口提供了State属性，你可以使用该属性确定通道是否仍然处于打开的状态。如果State属性的值不为CommunicationState.Openned，那么此时服务不应该试图去使用回调。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;回调操作与线程&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;如果服务调用回调合约中的一个操作，那么很可能客户端的代码也实现了该回调合约以使其他的操作能在服务中被回调。默认情况下，服务端的WCF运行时使用单线程处理回调的执行，因此回调至服务可能导致服务阻塞处理初始化请求的那个线程。在这种情况下，WCF运行时探测当前的情形，然后抛出一个InvalidOperationException异常，该异常的消息为"This operation would deadlock because the reply cannot be received until the current Message completes processing"。为了避免出现这种情形的发生，你可以在客户端程序中的回调实现类中设置并发模式：那么启动多线程（如果客户端程序的代码是线程安全的），要么启动重入（如果客户端程序的代码不是线程安全的，但是客户端使用的数据在多次调用之前是一致的）。为了实现这点，你需要在客户端程序中的回调实现类上应用CallbackBehavior特性，设置该特性类的ConcurrencyMode属性的值为ConcurrenyMode.Muitiple或者ConcurrencyMode.Reentrant。&lt;br /&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/20111102233807471.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;绑定和双向通道&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;并不是所有的绑定都支持客户端回调。为了支持客户端回调，你必须使用支持支持双向通讯的绑定；连接的一端必须能初始化通讯，而另一端必须能接受通讯。TCP传输协议和NamedPipes协议本身就是双向的，因此在实现客户端回调时，你可以使用NetTcpBinding绑定和NetNamePipeBinding绑定。HTTP协议实现的模型不支持双向操作，因此你不能使用BasicHttpBinding绑定，WSHttpBinding绑定，或者WS2007HttpBinding绑定。这看起来在基于HTTP传输协议的在内网系统中有很大的缺陷。然而，为此WCF提供了WSDualHttpBinding绑定以应对这种缺陷。该绑定建立两个HTTP通道（一个用于客户端向服务发送请求；另外一个用于服务向客户端发送那个请求），当然该绑定隐藏了实现的细节，因此不只需要把它当作一个普通的双向绑定即可。&lt;/div&gt;&#xD;
&lt;div&gt;在WSDualHttpBinding绑定和WSHttpBinding绑定与WS2007HttpBinding绑定之间有一些非常重要的差别。尤其是WSDualHttpBinding绑定不支持传输级安全，但是它还是实现了可靠的会话（而且你不能禁用该特性）&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2233756.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/11/03/2233756.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/11/01/2231219.html</id><title type="text">WCF 4.0 进阶系列 – 第十五章 构建REST服务（第三部分）</title><summary type="text">本小节介绍了WCF数据服务，它是一个非常灵活的框架，使用该框架你可以直接基于ADO.NET Entity Framework实体模型构建REST Web服务。Visual Studio提供了生成客户端库的工具，客户端程序使用客户端库连接到服务，然后对数据进行查询和更新。客户端库隐藏了构建，发送和接受相应HTTP消息的细节，因此开发人员可以将精力集中在业务逻辑上。WCF数据服务是一个非常广泛的话题，本小节的内容也只是谈及了它的一些浅显知识。其他深入的内容：比如你可以定义业务操作，你可以对外公布未在实体模型中定义的数据；你可以访问微软的WCF数据服务页（http://msdn.microsoft.com/en-us/data/odata.aspx）获取更详细的内容。</summary><published>2011-11-01T03:57:00Z</published><updated>2011-11-01T03:57:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/11/01/2231219.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/11/01/2231219.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;p&gt;&lt;strong&gt;使用WCF数据服务&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;到目前为止，本章中为你展示的例子都属于比较常见的情形。它们演示了如何使用WCF构建REST Web服务，这些服务使用WebGet和WebInvoke特性类手动定义一个对外公布数据的体系。如果你构建REST Web服务时采用ADO.NET实体框架读取数据，那么你可以使用WCF数据服务自动地完成大部分任务。使用由Visual Studio 2010提供的WCF Data服务模板和组件，你可以创建由WCF数据服务提供的抽象层，然后使用该抽象层构建REST Web服务。&lt;/div&gt;&#xD;
&lt;div&gt;你可以添加一个WCF数据服务到一个Web应用程序中以实现WCF数据服务。首先你需要定义一个实体框架，该实体框架描述对外公布的数据，然后添加WCF数据服务到该Web应用程序中。WCF数据服务模板基于System.Data.Service.DataService类型生成相关的数据服务类：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120265076.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该类（DataService）的类型参数是供实体模型使用的ObjectContext类，你应当使用这个类型来替代上面代码中的注释。当服务开始运行时，InitializeService方法自动执行。你可以添加服务公布的实体模型中实体和指定客户端程序对于这些实体数据的读取权限的代码到InitializeService方法中。比如，你可以指明一组实体中的数据是只读的，而在另外一组实体中的数据是可写的。下面的一组练习介绍了如何使用WCF数据服务模板创建和消耗REST Web服务。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：构建一个公布销售信息的WCF数据服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 启动Visual Studio，使用ASP.NE空网站模板创建一个新的Web站点。该网站保存在本地文件系统的Solutions\WCF\Step.by.Step\Chapter15文件夹下。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在解决方案浏览器窗口中，点击C:\...\SalesData工程。在属性窗口中，设置自动端口属性为false，然后设置端口的号码为48000。&lt;/div&gt;&#xD;
&lt;div&gt;3. 在解决方案浏览器窗口，在C:\...\SalesData工程上点击右键，然后点击添加新的项目。在添加新项目对话框中，选择ADO.NET实体数据模板。在名字字段处，输入SalesDataModel.edmx，然后点击添加按钮。当Visual Studio出现提示对话框时，允许Visual Studio添加该数据模型到工程的App_Code文件夹下。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在实体数据模型向导中，在选择模型内容页，点击从数据库生成，然后点击下一步。&lt;/div&gt;&#xD;
&lt;div&gt;5. 在选择数据连接页，点击创建新连接。在连接属性对话框，在服务名字段处，输入.\SQLExpress。在选择或者输入数据库名字字段处，输入AdventureWorks，然后点击确认按钮。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在选择数据连接页，确认保存实体连接设置到Web.config前的复选框被选中，如有必要更改连接字符串的名字为AdventureWorksEntities。然后点击下一步。&lt;/div&gt;&#xD;
&lt;div&gt;7. 在选择数据对象页，选择Contact(Person), SalesOrderDetail(Sales), 和SalesOrderHeader(Sales)表。对于其他选项请确认与下表中的值相同；然后点击完成按钮。&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0" cellpadding="5"&gt;&lt;colgroup&gt; &lt;col style="width: 260px;" /&gt; &lt;col style="width: 180px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr style="background: #666; color: white; font-weight: bold;"&gt;&#xD;
&lt;td&gt;选项&lt;/td&gt;&#xD;
&lt;td&gt;值&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;生成对象的名字使用单数或复数&lt;/td&gt;&#xD;
&lt;td&gt;选中&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;在模型中包括外键列&lt;/td&gt;&#xD;
&lt;td&gt;选中&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;模型命名空间&lt;/td&gt;&#xD;
&lt;td&gt;AdventureWorksModel&amp;lt;&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;最后的实体模型如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/20111101112029286.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当一个客户下订单时，其订单可能包含多个子项。SalesOrderHeader表包含了订单的信息（比如订单日期，客户的ID，等等），而SalesOrderDetail表则为订单的每个子项创建一行记录（比如产品ID，订购数量，等等）。&lt;/div&gt;&#xD;
&lt;div&gt;8. 生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;9. 在解决方案浏览器窗口，添加另外一个新的项目到C:\...\SalesData工程中。在添加新项目对话框中，选择WCF数据服务模板。在名字字段处，输入SalesDataService.svc，然后点击添加按钮。&lt;/div&gt;&#xD;
&lt;div&gt;Visual Studio模板将生成一个新的名为SalesDataService.cs文件，该文件包含了一个名为SalesDataService的类，如前所述，该类继承了DataService类。&lt;/div&gt;&#xD;
&lt;div&gt;10. 在文本编辑模式下，打开SalesDataService.cs文件中，然后添加下面的using声明到该文件的头部&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120309663.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;11. 在SalesDataService类的定义中删除注释。然后修改其为如下面的代码所示定义：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120308758.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;span style="color: red;"&gt;AdventureWorksEntites类是由ADO.NET实体模型向导自动生成的ObjectContext类型，该类型用以访问由该实体模型呈现的数据&lt;/span&gt;。&lt;/div&gt;&#xD;
&lt;div&gt;出于安全的原因，WCF数据服务模板并没有自动地对外公布任何资源，比如实体模型实现的实体集合。你必须在InitailizeService方法中制定允许或者禁止访问资源的策略。该方法接受一个DataServiceConfiguration对象为其参数，在该对象中你可以定义资源的访问策略。&lt;/div&gt;&#xD;
&lt;div&gt;12. 在InitializeService方法中，删除注释，然后添加下面的代码：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120324449.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;DataServiceConfiguration类的SetEntitySetAccessRule方法用以指定客户端程序对实体模型定义的每个实体的访问级别。在本练习中，WCF数据服务仅仅允许对数据进行只读操作。&lt;/div&gt;&#xD;
&lt;div&gt;SetEntitySetAccessRule方法接收两个参数&lt;/div&gt;&#xD;
&lt;ul&gt;&#xD;
&lt;li&gt;实体集名称。在实体模型中，这参数与一个实体集合的名字一样，但是参数使用复数的形式。该参数的值可以包含*通配符以标明所有实体集合，尽管这么做并不值得推荐。&lt;/li&gt;&#xD;
&lt;li&gt;对实体集授予的访问权限。该参数的值来自于System.Data.Service.EntitySetRights枚举类型。该枚举定义了各种读和写的访问权限。你可以使用或运算符合并实体集合权限。下表总结了EntitySetRights枚举的值：&lt;/li&gt;&#xD;
&lt;/ul&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0" cellpadding="5"&gt;&lt;colgroup&gt; &lt;col style="width: 140px;" /&gt; &lt;col style="width: 440px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr style="background: #666; color: white; font-weight: bold;"&gt;&#xD;
&lt;td&gt;选项&lt;/td&gt;&#xD;
&lt;td&gt;值&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;None&lt;/td&gt;&#xD;
&lt;td&gt;拒绝访问数据。所有实体的默认设置。&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;ReadSingle&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;授权读取实体集中单个项目&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;ReadMultiple&lt;/td&gt;&#xD;
&lt;td&gt;授权读取数据集&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;WriteAppend&lt;/td&gt;&#xD;
&lt;td&gt;授权在数据集中穿件一个新的数据项&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;WriteReplace&lt;/td&gt;&#xD;
&lt;td&gt;授权替换数据&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;WriteDelete&lt;/td&gt;&#xD;
&lt;td&gt;授权从数据集中删除数据项&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;WriteMerge&lt;/td&gt;&#xD;
&lt;td&gt;授权合并数据&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;AllRead&lt;/td&gt;&#xD;
&lt;td&gt;ReadSingle或ReadMultiple的缩写&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;AllWrite&lt;/td&gt;&#xD;
&lt;td&gt;WriteAppend或WriteReplace或WriteDelete或WriteMerge的缩写&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;All&amp;lt;&lt;/td&gt;&#xD;
&lt;td&gt;所有读和所有写操作的缩写&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;13. 请注意InitializeService方法的最后一行。每个实体集（Contacts，SalesOrderHeaders，SalesOrderDetails）可能包含数千行数据，客户端程序可能使用一个查询获取所有的数据。为了预防没有限制的查询耗费网络带宽，SetEntityPageSize方法限制查询返回的项目的数量。在本练习中该数值为25。SetEntityPageSize方法的第一个参数用于指定实体的名字，同样地该参数可以使用通配符，以表示适用于所有的实体。然而，与SetEntitySetAccessRule方法不同的是，SetEntityPageSize中使用通配符*是值得推荐的一种做法。&lt;/div&gt;&#xD;
&lt;div&gt;14. 生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;这就是使用WCF数据服务模板构建REST Web服务所需做的事情。当服务运行时，DataService类基于实体模型中实体的名字和实体之间的关系构成的体系自动地发布数据。你可以指定匹配实体模型架构的URI。WCF数据服务提供一系列的操作，你可以使用这些操作有选择地从单个列中获取数据，排序数据，或者基于数据做聚合计算。在接下来的练习中你将检查这些操作。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：测试WCF数据服务SalesData&lt;/div&gt;&#xD;
&lt;div&gt;1. 在解决方案浏览器窗口，在文本模式下打开SalesDataService.svc文件，该文件包含下面的代码：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120337905.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;WCF运行时使用该文件中的信息确定如何启动SalesDataService服务。Factory特性标明DataServiceHostFactory类型位于System.Data.Services命名空间下。该类型由WCF数据服务提供，并且它的目的是根据Service特性指定的类型创建一个WCF数据服务实例，并调用该类型（SalesDataService）的InitializeService方法设置安全策略，然后启动服务运行。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在SalesDataService.svc文件上点击右键，然后点击在浏览器中查看。服务将开始运行，IE启动并显示如下的内容：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120367117.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;3. 在IE的地址栏中输入http://localhost:48000/SalesData/SalesDataService.svc/Contacts。 IE将显示AdventureWorks数据库中前25个联系人。数据以Atom信息来源格式显示。其内容如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120397343.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;注意：由于IE配置的不同，可能你需要关闭feed-off视图以按照上图显示数据。关闭的方法是：IE&amp;mdash;工具&amp;mdash;IE选项&amp;mdash;内容标签&amp;mdash;信息来源（Feeds）--设置&amp;mdash;取消启动信息来源阅读视图。然后点击确认按钮关闭信息来源设置对话框；然后点击确认关闭IE选项对话框；然后重启IE，并输入地址http://localhost:48000/SalesData/SalesDataService.svc/Contacts，你将得到上图所示的结果。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在IE地址栏输入http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderDetials。SalesOrderDetails表中的前25条记录将显示在IE中。&lt;/div&gt;&#xD;
&lt;div&gt;如果你需要访问前25条数据之外的数据，WCF数据服务允许在查询中使用$skip和$top选项以块方式获取数据。这些查询选项与第一节中的skip和top参数类似。&lt;/div&gt;&#xD;
&lt;div&gt;5. 在IE地址栏输入http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderDetials?$skip=50。&lt;/div&gt;&#xD;
&lt;div&gt;你将从AdventureWorks数据的SalesOrderDetials表中51条记录开始获取25条数据并显示在IE中。请注意显示的数据以SalesOrderID列升序排列，该列在数据库中为SalesOrderDetails表的主键。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在IE地址栏输入http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderDetials?$orderby=UnitPrice。那么现在数据按照UnitPrice升序排列。你还可以切换为按照UnitPrice降序排列：http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderDetials?$orderby=UnitPrice desc&lt;/div&gt;&#xD;
&lt;div&gt;7. WCF Data服务提供了从实体中提取数据的操作。比如：http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderHeaders?$select=SalesOrderID,OrderDate,CustomerID,TotalDue。&lt;/div&gt;&#xD;
&lt;div&gt;该URI形成一个映射以限制返回的数据仅仅为SalesOrderID, OrderDate, CustomerID和TotalDue列。你还可以指定前缀以限制返回的数据项是根据通过$filter选项执行的值执行查询的。下面的URI显示客户编号为99的客户的所有订单的SalesOrderID和TotalDue：http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderHeaders?$select=SalesOrderID,TotalDue&amp;amp;$filter=CustomerID eq 99&lt;/div&gt;&#xD;
&lt;div&gt;8. WCF数据服务还非常方便的遍历实体与相关数据值之间的关系。在IE地址栏输入下面的URI：http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderHeaders(43682)。&lt;/div&gt;&#xD;
&lt;div&gt;该URI获取根据AdventureWorks数据库中主键列SalesOrderID的值，单个SalesOrderDetial项目。如果你想查找下此订单联系人的详细信息，那么你只需要在该URI后面添加"/Contact"：http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderHeaders(43682)/Contact。&lt;/div&gt;&#xD;
&lt;div&gt;为了查找该订单的详细信息，你只需要在该URI的后面添加/SalesOrderDetails: &lt;br /&gt;http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderHeaders(43682)/SalesOrderDetails。&lt;/div&gt;&#xD;
&lt;div&gt;9. 在IE地址栏输入URI: http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderHeaders?expand=Contact&lt;/div&gt;&#xD;
&lt;div&gt;该查询获取SalesOrderHeader的前25行，但是$expand选项会使WCF Data服务同时获取与每个订单关联的联系人的信息。&lt;/div&gt;&#xD;
&lt;div&gt;10. 关闭IE并返回到Visual Studio。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;你已经构建了一个WCF 数据服务，并且学习了如何通过一个Web浏览器访问该服务。该服务使用ASP.NET开发Web服务器寄宿WCF数据服务，当然现在你应该已经可以非常容易地将其部署到IIS上。此外，你还可以与本章第一节一样，使用WebServiceHost构建一个自定义的宿主程序寄宿此服务。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;使用客户端程序消耗WCF数据服务&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;SalesData服务非常简单，使用该服务，用户可以执行许多复杂的查询。尽管AtomPub协议已经被广泛接受，但是使用该协议返回的数据格式却不易理解和转换；你不能期望终端用户能理解这些数据格式。幸运地是，你可以通过构建客户端程序消耗WCF数据服务，这样服务端的数据以一种更容易理解的格式呈现给终端用户。为了实现该目标，你必须生成一个WCF数据服务客户端库。该客户端将做为WCF数据服务的代理，以读取WCF数据服务公布的数据。&lt;/div&gt;&#xD;
&lt;div&gt;你可以使用两种方式生成客户端库：在命令行中使用DataSvcUtil实用工具，或者在Visual Studio中通过添加服务引用向导。如果使用DataSvcUtil工具，在WCF数据服务在运行时，打开Visual Studio命令行工具然后输入下面命令（红色划线部分）：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120404703.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该命令将创建一个名为SalesClient.cs的代码文件，该文件包含了客户端程序用于向WCF数据服务发送请求的方法。客户端库通过与WCF数据服务下的实体模型有紧密关系的一系列的集合和属性公开数据。当客户端程序试图从这些集合和属性中获取数据时，客户端库生成对应的HTTP请求并向服务发送这些请求。当服务返回数据时，客户端库将返回的AtomPub格式的数据转换成.NET Framework集合和类型，这样客户端程序就可以非常方便的使用这些集合与类型。&lt;/div&gt;&#xD;
&lt;div&gt;在下面的练习中，你将构建一个客户端程序，该程序连接到SalesData服务，然后执行使用IE访问服务时同样的请求。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：为SalesData服务创建一个测试客户端的程序&lt;/div&gt;&#xD;
&lt;div&gt;1. 在Visual Studio中，添加一个新的控制台程序工程到SalesData解决方案。并将其命名为SalesDataClient，该工程保存在WCF\Step.by.Step\Solutions\Chapter15\SalesData文件夹下。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在解决方案浏览器窗口中，在SalesDataClient工程上点击右键，然后点击添加服务引用。在添加服务引用对话框中，点击自动探测。找到SalesData服务后，在命名空间字段处，输入SalesDataService，然后点击确认按钮。&lt;/div&gt;&#xD;
&lt;div&gt;粗略一看，添加数据Web服务引用对话框与添加SOAP Web服务引用一致。但是，Visual Studio能够识别数据Web服务，然后据此生成的客户端库匹配数据服务公开的数据，而不会匹配为SOAP Web服务操作对应的方法。&lt;/div&gt;&#xD;
&lt;div&gt;WCF数据服务的客户端库包含一个继承了DataServiceContext类型的类。该类有一个或多个DataServiceQuery类型的公共属性。该类的名字一般地与WCF数据服务依赖的实体模型ObjectContext对象的名字是一样的。比如，SalesDataService服务使用一个名为AdventureWorksEntities的ObjectContext对象连接到相关的实体模型，因此为客户端库生成的DataServiceContext类型的名字也是AdventureWorksEntities。&lt;/div&gt;&#xD;
&lt;div&gt;DataServiceContext类执行与实体框架中ObjectContext类相似的角色。客户端程序通过DataServiceContext对象连接到数据源，然后从数据服务所公布实体的DataServiceQuery属性中获取数据。每个DataServiceQuery属性都是一个generic集合，该集合呈现来自为WCF数据服务所提供的相关实体的数据。在SalesDataService服务中，实体模型提供了对AdventureWorks数据库中Contact，SalesOrderHeader和SalesOrderDetails数据表的读取。在客户端库中AdventureWorksEntities类中也包含了名为Contacts，SalesOrderHeader和SalesOrderDetails的DataServiceQuery类型的属性。&lt;/div&gt;&#xD;
&lt;div&gt;客户端库还提供每个DataServiceQuery集合所包含类型（Contact，SalesOrderHeader和SalesOrderDetail）的定义。客户端程序首先对DataServiceQuery集合执行LINQ查询，然后在客户端库中创建一个适当的HTTP请求从服务获取对应的数据。WCF数据服务获取匹配的数据后填充DataServiceQuery集合。客户端程序最后迭代该集合并从每个子项中获取数据。&lt;/div&gt;&#xD;
&lt;div&gt;3. 在文本编辑模式下打开SalesDataClient工程中的Program.cs文件，添加下面的声明到该文件的顶部：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120407702.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;4. 在Program类的main方法中，添加下面的代码：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120417669.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该声明连接到SalesData服务。正如先前描述，AdventureWorksEntities类型是一个DataServiceContext对象，它充当向SalesData发送请求的代理。在构造函数中指定的URI为SalesData服务的地址。&lt;/div&gt;&#xD;
&lt;div&gt;5. 添加下面的代码到Main方法中&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120422030.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码从SalesData服务获取订单的详细信息，并显示每个联系人的姓和名。请注意当你引用AdventureWorksEntities对象中的Contracts集合时，客户端库发送对应的HTTP GET请求（http://localhost:48000/SalesData/SalesDataService.svc/Contacts）至SalesData服务，然后填充Contracts集合。该请求与你使用IE具有同样的限制，该限制就是该请求也只仅仅返回前25个联系人的信息。&lt;/div&gt;&#xD;
&lt;div&gt;6. 添加下面的代码到Main方法中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/20111101112043752.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码与上一步的代码基本相似，但是它的作用是查询SalesOrderDetails集合。该行为导致AdventureWorksEntities对象发送一个HTTP GET请求（http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderDetails）至SalesData服务，同样地，该请求仅仅返回前25条数据。&lt;/div&gt;&#xD;
&lt;div&gt;7. 添加下面的生命到Main方法中&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120451666.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码在AdventureWorks数据库中从第51条记录开始获取25条SalesOrderDetails记录。&lt;/div&gt;&#xD;
&lt;div&gt;8. 添加下面代码到Main方法中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120473071.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码获取根据单价排序后的SalesOrderDetails记录。&lt;/div&gt;&#xD;
&lt;div&gt;9. 添加下面代码到Main方法中&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120481893.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码使用LINQ语法过滤和加工数据，列出客户编号为99的客户所有订单的SalesOrderID字段和TotalDue字段。&lt;/div&gt;&#xD;
&lt;div&gt;10. 添加下面代码到Main方法中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120506711.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码片段获取订单和每个订单对应的联系人信息。&lt;/div&gt;&#xD;
&lt;div&gt;11. 重新生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;现在你可以测试客户端程序。但是，在测试之前，为SalesData服务配置tracing是非常有用的。因为启用跟踪之后，你可以看到客户端程序向服务发送请求的详细信息。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：配置跟踪然后在客户端程序中测试SalesData服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 在C:\...\SalesData工程中，使用WCF服务配置编辑器打开Web.config。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在配置面板，点击诊断节点，在诊断面板中，点击启动跟踪链接。&lt;/div&gt;&#xD;
&lt;div&gt;3. 点击跟踪级别后的链接进入跟踪设置对话框，设置跟踪级别为Information，然后点击确认按钮。&lt;/div&gt;&#xD;
&lt;div&gt;4. 点击ServiceModelTraceListener链接。在侦听器设置对话框中，设置日志文件属性为web_tracelog.svclog，其路径为Solutions\WCF\Step.by.Step\Chapter15文件夹，然后点击确认按钮。&lt;/div&gt;&#xD;
&lt;div&gt;5. 保存配置文件并关闭WCF服务配置编辑器。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在解决方案窗口，打开SalesData解决方案的属性窗口，设置C:\...\SalesData和SalesDataClient工程均为解决方案SalesData的启动工程，然后点击确认按钮。&lt;/div&gt;&#xD;
&lt;div&gt;7. 在非调适模式下启动解决方案。当IE出现后最小化IE。在客户端程序控制台窗口中，确认显示了前25个客户：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120514038.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;8. 按下ENTER键，然后确认前25条SalesOrderDetails继续出现。这些记录的订单编号从43659到43661（每个订单都包含了多个子项）。&lt;/div&gt;&#xD;
&lt;div&gt;9. 按下ENTER键，然后确认一个不同的SalesOrderDetial数据记录集显示在窗口中。该批订单的编号从43662到43666.&lt;/div&gt;&#xD;
&lt;div&gt;10. 再次按下ENTER键，SalesOrderDetails继续将按照UnitPrice字段升序排列。&lt;/div&gt;&#xD;
&lt;div&gt;11. 再次按下ENTER键，SalesOrderDetails继续将按照UnitPrice字段降幅排列。&lt;/div&gt;&#xD;
&lt;div&gt;12. 再次按下ENTER键，你将看到SalesOrderID和TotalDue显示在窗口中，订单编号从43682到69485。&lt;/div&gt;&#xD;
&lt;div&gt;13. 继续按下ENTER键，你将看到订单编号和每个订单对应客户名字的列表。&lt;/div&gt;&#xD;
&lt;div&gt;14. 按下ENTER键结束程序并关闭客户端控制台窗口。&lt;/div&gt;&#xD;
&lt;div&gt;15. 在Windows任务栏，在ASP.NET开发服务器上点击右键，然后点击停止。&lt;/div&gt;&#xD;
&lt;div&gt;16. 启动服务跟踪查看工具，然后打开Solutions\WCF\Step.by.Step\Chapter15文件夹下的web_tracelog.svclog。&lt;/div&gt;&#xD;
&lt;div&gt;17. 在服务跟踪查看工具中，点击行为标签，然后点击第一个名为"Process action"的行为。在右边面板中，选择"Received a message over a channel"项目。然后在右下面板中，滚动格式化标签对应内容的滚动条，直到找到To字段。该字段对应的URI为http://localhost:48000/SalesData/SalesDataService.svc/Contacts。该URI为客户端程序中的第一个测试生成。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120532411.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;18. 在行为面板， 第二个Process Action行为。在右边面板，点击"Received a request over a channel"，然后在下面的面板中，确认To的值为&lt;a href="http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderDetails"&gt;http://localhost:48000/SalesData/SalesDataService.svc/SalesOrderDetails&lt;/a&gt;&lt;/div&gt;&#xD;
&lt;div&gt;19. 重复上述过程，分别验证另外5个地址。&lt;/div&gt;&#xD;
&lt;div&gt;20.关闭服务跟踪查看器，然后返回到Visual Studio。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;使用WCF数据服务修改数据&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;你可能期望构建客户端程序，通过WCF数据服务修改数据。客户端库实现了一个模型，使用该模型你可以在本地修改与服务对外公布的实体相关联 的数据，然后使用用于连接服务的DataServiceContext对象的SavaChanges方法成批地发送这些变动到服务。你需要注意，WCF数据服务必须显示地在InitializeService方法中指定合适的访问权限以允许对实体进行更改。下面的代码展示了如何对允许对Contacts实体进行更新。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120551133.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;接下来的代码为你展示了如何创建一个新的Contract实体对象。当你为WCF数据服务创建一个客户端库时，添加服务向导为每个实体类型生成一个静态的Create方法，该方法包含了使对象为非空的参数。向导还为每个实体生成了一个AddTo方法；你可以调用该方法以添加新创建的对象到DataServiceContext适当的集合对象中。最后调用SaveChanges方法提交更改到服务。&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120564688.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;SaveChanges方法生成一个HTTP POST请求，将newContact的数据作为消息的body传递至服务。&lt;/div&gt;&#xD;
&lt;div&gt;如果修改一个对象，那么首先发送一个查询到WCF数据服务，更改需要更改的字段；然后调用DataServiceContext对象的UpdateObject方法通知更改；最后调用SaveChanges发送更改至服务。DataServiceContext对象生成一个HTTP PUT请求或者为每个更改的对象生成一个HTTP MERGE消息，然后把对象的数据作为消息的body传送给服务。比如下面的代码展示了如何更改客户编码为99的客户的邮件地址：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120564655.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;如果需要删除一个对象，那么与更新一个对象的方法相似。首先获取一个对象，然后调用DataServiceContext对象的DeleteObject方法，最后调用SaveChanges方法。SaveChanges方法生成一个HTTP DELETE消息，然后把该消息发送给WCF数据服务。下面的代码展示了如何删除一个客户编码为101的客户&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201111/201111011120566574.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;请注意上面三个例子都调用了SaveChanges方法。如果你要在进行多次更改（插入，更新，删除，或者多个操作的联合），你仅仅需要在最后调用一次SaveChanges。DataServiceContext对象在其自身内部的一个可变集合中追踪所有对象的状态，然后针对每个修改生成对应的HTTP POST，PUT，MERGE和DELETE消息，然后再逐个发送这些消息到WCF数据服务。如果你要进一步优化，那么你可以在SaveChanges方法中指定SaveChangesOptions.ReplaceOnUpdate.Batch参数。该标记将会使DataServiceContext对象在一个单独的消息中发送所有的更改。这种方式减少了网络交通数量，并改善了客户端的性能。批处理还可以作为一个事务而运行，所有的操作要么成功要么失败。这对在应用程序中设计数据一致性非常有帮助。每个批请求对客户端产生一个单独的HTTP响应，该响应消息中包含了该批中所有操作的响应结果。如果任意一个操作失败，那么响应消息仅仅包含一个标明批操作失败的响应结果。&lt;/div&gt;&#xD;
&lt;div&gt;当使用任何多用户数据程序时，可能会导致并发异常，因此你需要在客户端中正确地处理并发。此外，你还需要确保在修改关联数据时保持数据的一致性。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;在客户端段中处理异常&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;当一个客户端程序向WCF数据服务发送一条请求时，请求可能会因为多种原因而失败。比如，客户端程序可能试图访问未授权数据，或者执行了一个服务不允许的查询。&lt;/div&gt;&#xD;
&lt;div&gt;如果失败是由于客户端程序与服务交互的方式（与此相反的失败是由于比如网络失败等引起的其他原因），那么服务将抛出一个DataServiceException异常。DataServiceException类型是一个可序列化的异常，之所以这么设计是为了客户端能与WCF数据服务失败的原因进行通信。当客户端接收到一个DataServiceException异常，客户端库把该异常反序列化为一个DataServiceClientException对象，然后把该对象传递给客户端程序。&lt;/div&gt;&#xD;
&lt;div&gt;如果你的程序执行一个查询时发生异常，DataServiceClientException异常将被封装在DataServiceQueryException对象中，DataServiceClientExcaption的异常消息将为"执行请求时错误发生"。你可以读取DataServiceClientException异常对象，通过检查DataServiceQueryException对象的InnerExcpetion属性获取异常的原因。&lt;/div&gt;&#xD;
&lt;div&gt;如果客户端程序发送一个请求而不是一个查询，WCF数据服务发生异常时，服务将响应一个DataServiceRequestException异常。&lt;/div&gt;&#xD;
&lt;div&gt;客户端程序应当在执行查询时捕获DataServiceQueryException异常，执行其他类型（比如修改数据）的操作时捕获DataServiceRequestExcaption异常。客户端程序还应该捕获DataServiceClientException异常以处理WCF数据服务在执行其他类型操作时抛出的其他异常。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2231219.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/11/01/2231219.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/10/26/2224600.html</id><title type="text">WCF 4.0 进阶系列 – 第十五章 构建REST服务（第二部分）</title><summary type="text">REST Web服务提供了查询数据的操作以响应HTTP GET请求。然而，HTTP协议还支持其他形式的请求，你可以利用这些消息类型提供可以修改REST Web服务中的数据的操作。最常见的情形是你使用HTTP POST请求创建新项目的操作，HTTP PUT请求更新现存数据的操作，使用HTTP DELETE请求定义删除项目的操作。请注意：POST请求是非对等的，而PUT请求与DELETE请求是对等的。这意味着PUT请求与DELETE请求可以多次重复地调用操作，这些操作都具有同样的效果。这个逻辑同样适用于DELETE请求；如果一个项目已经被删除，那么它还可以继续被删除。然而，使用POST请求重复地添加相同的信息可能会造成重复的数据。</summary><published>2011-10-26T05:17:00Z</published><updated>2011-10-26T05:17:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/10/26/2224600.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/10/26/2224600.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;p&gt;&lt;strong&gt;通过REST Web服务更新数据&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;REST Web服务提供了查询数据的操作以响应HTTP GET请求。但是，HTTP协议支持其他形式的请求，你可以利用这些消息类型提供操作以修改REST Web服务中的数据。最常见的情形是你使用HTTP POST请求创建新项目的操作，HTTP PUT请求更新现存数据的操作，使用HTTP DELETE请求定义删除项目的操作。&lt;/div&gt;&#xD;
&lt;div&gt;注意，上述常规并不是强制的，你也可以使用HTTP POST请求更新和删除数据，但这并不是推荐的做法。上述常规之所以这么定义的原因在于HTTP协议：POST请求是非对等的，而PUT请求与DELETE请求是对等的。这意味着PUT请求与DELETE请求可以多次重复地调用操作，这些操作都具有同样的效果：那就是它们看来仿佛只被执行了一次；而这对于POST却不一样。因此，如果你使用PUT请求实现一个更新操作，那么你可以对同一个数据重复地执行该更新操作，而且这些操作的结果始终都是一样。这个逻辑同样适用于DELETE请求；如果一个项目已经被删除，那么它还是继续被删除。但是，使用POST请求重复地添加相同的信息可能会造成重复的数据。&lt;/div&gt;&#xD;
&lt;div&gt;在一个WCF Web服务中，你通过WebGet特性类标记操作以响应HTTP GET请求；你还提供了一个模板以指定Web客户端可以访问的URI以调用服务的操作。为了支持HTTP的POST请求，PUT请求和DELETE请求，WCF提供WebInvoke特性类。同样地，你使用该特性类指明一个URI，此外你还可以指明操作响应请求消息的类型。当REST Web服务接收到一条对满足URI所定义类型的消息，那么该请求将调用该操作。这个体系使得多个操作可以响应同一个URI，只是这些操作期望不同的HTTP消息类型。这一点相当有用，而且让你避免考虑如何使用多个URI体系使相同的数据支持不同的操作。比如，IProductsSales服务合约中的GetCustomer操作可能定义为：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320442140.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;那么你可以使用相同的URI定义DeleteCustomer操作以删除一个客户，但是你必须指定该操作响应HTTP DELETE消息；&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320455204.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;在下面的一组练习中，你将扩展ProductsSales REST Web服务以使该服务具有能插入，更新，和删除客户数据的操作。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：扩展ProductsSales服务以支持数据更新&lt;/div&gt;&#xD;
&lt;div&gt;1. 在Visual Studio中，在文本编辑模式下打开ProductsSalesService项目下的IProductsSales.cs文件。&lt;/div&gt;&#xD;
&lt;div&gt;2. 添加下面的操作到IProductsSales接口&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320452729.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;你将要实现的CreateCutomer方法会添加一个新的客户到AdventureWorks数据库中。该操作标注了WebInvoke特性，因为是插入操作所以Method属性设置为POST。UriTemplate属性指定了创建新客户所需声明的详细信息（AdventureWorks数据库中的字段远不止这些，但是其他的列使用其默认值）。与上一节练习中GetAllOrders和GetAllCustomer操作中的skip和top参数一样，UriTemplate包含参数的顺序并不重要，只要你定义在CreateCustomer方法中参数与UriTempalte包含的参数名字相同即可。&lt;/div&gt;&#xD;
&lt;div&gt;一个Web程序能通过UriTemplate属性指定的URI提交POST请求以创建新的客户。比如，为John Sharp添加一个记录，那么应用程序将使用下面的URI：&lt;/div&gt;&#xD;
&lt;div&gt;Customers?FirstName=John&amp;amp;LastName=Sharp&amp;amp;EmailAddress=john@adventure-works.com&amp;amp;Phone=(123)456789&lt;/div&gt;&#xD;
&lt;div&gt;3. 添加下面的UpdateCustomer操作到IProductsSales接口中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320464813.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;UpdateCustomer方法将修改指定客户编号对应客户的电子邮件地址和电话号码为参数所声明的值。该操作将响应HTTP PUT请求&lt;/div&gt;&#xD;
&lt;div&gt;4. 添加DeleteCustomer操作到IProductsSales接口中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320479339.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该方法将响应HTTP DELETE请求，然后从数据库中移除指定的客户。&lt;/div&gt;&#xD;
&lt;div&gt;5. 在文本编辑模式下打开IProductsSalesService项目中的ProductsSales.cs文件，然后在ProductsSales类中实现CreateCustomer方法：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320497585.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述方法使用传入的参数创建一个新的Contact对象，然后使用实体框架（EF）保持新对象到数据库中。请注意某些列自动生成其默认值。AdventureWorks数据库自动为新客户生成客户编号，并且CreateCustomer方法返回自动生成的ID。如果错误发生，该方法将抛出一个HTTP状态编码为400的WebFaultException异常。&lt;/div&gt;&#xD;
&lt;div&gt;6. 添加下面的UpdateCustomer方法到ProductsSales类中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320497095.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;UpdateCustomer方法首先尝试找到需要更新的客户。当找到匹配的客户，该方法将把email参数和phone参数对应的值更新到客户对象并保存到数据库。请注意如果任何一个参数为null，那么该方法将不会更新该对象对应的字段；这就允许Web客户端程序在不要更新数据库中对应的字段事可以省略这些参数中的一个参数。如果没有从AdventureWorks数据库中发现匹配的客户，该方法将抛出一个HTTP状态编码为404的WebFaultException异常。如果在执行更新的过程中发生错误，那么该方法将抛出一个HTTP状态编码为400的WebFaultException异常。&lt;/div&gt;&#xD;
&lt;div&gt;7. 添加下面的DeleteCustomer方法到ProductsSales类中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320498764.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该方法与UpdateCustoomer方法类似，但该方法的目标是发现匹配的客户并从数据库中删除该客户。如果没有从AdventureWorks数据库中发现匹配的客户，该方法将抛出一个HTTP状态编码为404的WebFaultException异常。如果在执行更新的过程中发生错误，那么该方法将抛出一个HTTP状态编码为400的WebFaultException异常。&lt;/div&gt;&#xD;
&lt;div&gt;8.重新生成ProductsSalesService项目。请注意你仅仅是重新生成了ProductsSalesService项目而不是整个解决方案；因为此时，ProductsSales客户端程序不能成功地生成，这是因为CreateCustomer，UpdateCustomer和DeleteCustomer方法还没有包含在客户端带来中。你将在下面的练习中添加这些方法。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;你可以从一个Web程序中通过提交POST，PUT和DELETE请求调用CreateCustomer，UpdateCustomer和DeleteCustomer操作。下面的代码片段是从一个ASP.NET Web程序中提取出来的，它演示了如何提交一个HTTP Delete请求以尝试从AdventureWorks数据库中删除客户编码为101的客户。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320512800.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;你不能简单地在Web浏览器比如IE的地址栏输入一个URI然后调用上面的操作。这是因为大多数浏览器都是通过发送HTTP GET请求工作；这是因为浏览器本身就是为了查询数据而不是为了修改数据。为了测试这些新的方法，你可以创建一个ASP.NET Web程序或者通过一个过程性的客户端程序调用这些操作。你下面的练习中，你将创建一个过程性客户端程序调用新添加的操作。&lt;/div&gt;&#xD;
&lt;div&gt;为了在客户端能调用新的操作，你首先必须更新ProductsSalesProxy类。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：更新客户端以测试ProductsSales REST Web服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 在文本编辑模式下打开ProductsSalesClient项目的productsSalesProxy.cs文件。添加CreateCustomer，UpdateCustomer，和DeleteCustomer方法到ProductsSalesProxy类中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320522965.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;这些方法与前一节的练习中定义的方法使用相同的模式；他们简单地实现了通过ClientBase&amp;lt;IProductsSales&amp;gt;类的Channel属性路由请求到服务。请注意，UpdateCustomer方法为参数email和phone设置了默认值，因为客户端在有需要时可以省略这两个参数中的任意一个。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在文本编辑模式下打开ProductsSalesClient项目下的Program.cs文件，然后在Main方法中的proxy.Close()之前添加下面的代码：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/20111025232057952.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码通过依次调用新创建的方法从而实现了测试ProductsSales REST服务。第一个测试创建一个新的客户，然后调用GetCustomer方法时使用新创建客户的客户编号以验证CreateCustomer操作是否成功。第二个测试更改该客户的电子邮件地址，然后再次调用GetCustomer以获取更新后的客户的详细信息。最后一个测试方法删除新创建的客户，在删除操作结束后，如果DeleteCustomer方法成功那么GetCustomer方法将返回一个null对象。&lt;/div&gt;&#xD;
&lt;div&gt;3. 重新生成解决方案&lt;/div&gt;&#xD;
&lt;div&gt;4. 在非调适模式下运行解决方案。在ProductsSalesClient控制台窗口中，按下ENTER键。所有的新测试都将成功地执行：一个新客户端被添加，然后更新，随后被删除：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110252320596690.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 在ProductsSalesClient控制台窗口中按ENTER键以关闭客户程序；然后在ProductsSalesHost控制台中按ENTER键以关闭寄宿程序。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2224600.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/10/26/2224600.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/10/25/2224118.html</id><title type="text">WCF 4.0 进阶系列 – 第十五章 构建REST服务（第一部分）</title><summary type="text">通常企业使用两种架构实现Web服务：基于SOAP的服务和基于REST模型的服务。这两个架构依赖HTTP协议和因特网实现的寻址体系，但采取不同的方式使用它们。到目前为止，本书所有的练习都集中在SOAP模型上，这种架构使得Web服务的设计者关注服务的业务过程，并把这些业务过程对外公开为服务的操作。相比之下，REST模型则关注企业对外公布的数据，并实现一个允许客户端程序使用自身的逻辑访问并操作这些数据的体系。REST模型变得越来越普遍，你可以使用WCF提供的特性、方法、和类型快捷地构建和访问REST Web服务。此外，实体框架为WCF提供了数据服务模板，你可以使用这些模板把来自实体框架模型的数据和实体作为REST资源对客户端程序公开。本小节介绍REST的基本概念，并创建和测试一个基本的REST服务。</summary><published>2011-10-25T09:27:00Z</published><updated>2011-10-25T09:27:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/10/25/2224118.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/10/25/2224118.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;div&gt;通常企业使用两种架构实现Web服务：基于SOAP的服务和基于REST模型的服务。这两个架构依赖HTTP协议和因特网实现的寻址体系，但采取不同的方式使用它们。到目前为止，本书所有的练习都集中在SOAP模型上，这种架构使得Web服务的设计者关注服务的业务过程，并把这些业务过程对外公开为服务的操作。相比之下，REST模型则关注企业对外公布的数据，并实现一个允许客户端程序使用自身的逻辑访问并操作这些数据的体系。REST模型变得越来越普遍，你可以使用WCF提供的特性、方法、和类型快捷地构建和访问REST Web服务。此外，实体框架为WCF提供了数据服务模板，你可以使用这些模板把来自实体框架模型的数据和实体作为REST资源对客户端程序公开。&lt;/div&gt;&#xD;
&lt;div&gt;本章的学习目的是介绍REST Web服务，并展示如何使用WCF创建和访问REST Web服务。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;理解REST模型&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;REST模型最早在2000年由Roy Fielding在他的博士论文中提到，"结构化样式和基于网络软件的架构设计"。正如论文的名字那样，REST是一个架构风格而不是一个预先定义的构建Web服务的方式，你可以使用任何合适的技术实现REST。REST的关键点在于它描述了一个无状态的，遵循与WWW相似的架构。举例来说，AdventureWorks提供客户和销售信息数据的访问，把每个客户或订单的详细信息作为单个的资源。从AdventureWorks网站获取所有客户列表，那么应有如下URL的网站应用程序：http://www.adventure-works.com/sales/customers。数据可以按照多种格式返回，但是最常见的模式包括XML和JSON。如果AdventureWorks选择XML，那么通过上述URL查询的返回结果可能如下所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636172262.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;遵循REST模型的Web服务一般允许Web应用程序通过额外的路径参数进一步提取数据。比如，查询单个客户的详细信息。如果客户的编号为99，那么AdventureWorks允许Web应用程序指定如下的URL地址：http://www.adventure-works.com/sales/customers/99。&amp;nbsp;此外，允许Web应用程序查找某个指定客户的所有订单也是非常有用的，那么对于的URL应该为：http://www.adventure-works.com/sales/customers/99/orders。&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;设计基于REST方案的关键是要理解如何划分业务模型为资源，以及如何将这些资源关联到一起。在某些情况下，比如客户和订单，这种关系相对简单明了，但是在其他的一些方案中要划分和关联资源无疑是一项艰巨的任务。&lt;/div&gt;&#xD;
&lt;div&gt;REST模型依赖于应用程序，这些程序发送合适的HTTP动作，这些动作构成请求的一部分以访问服务的数据。举例来说，上面所述的请求发送了一个HTTP GET请求至Web服务。HTTP还支持其他动作，比如POST，PUT和DELETE，这些动作对应REST服务的创建，修改和移除资源。使用REST模型，你可以利用这些动作创建插入、更新和删除数据。&lt;/div&gt;&#xD;
&lt;div&gt;与SOAP相反，REST模型发送和接收的消息更简洁。这主要是因为REST WS-*的描述不需要提供路由，策略，或安全等功能；你仅仅需要依赖Web服务器实现的，用以保护REST Web服务的基础传输结构。你还应该记住REST模型是无状态的；在客户端与服务的交互对话之间不存在会话或者事务的概念。因而采取简单实现方式的REST Web服务意味着在传输和接受消息方面通常比SOAP Web服务更有效率。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;实现一个查询数据的REST Web服务&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;使用WCF实现一个REST Web服务是一个简单的过程，而且WCF在System.ServiceModel.Model组件中提供了许多类型供你使用。该过程最重要的部分是设计服务对外提供资源读取的数据体系。在大多数情况下，资源根据自然状态分组到各个集合中，并且与其他资源或者集合之间相互关联。本章的练习将使用下表定义的数据体系。&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 280px;" /&gt; &lt;col style="width: 320px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="height: 19px; background: gray;"&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;URI&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;描述&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;Customers&lt;/td&gt;&#xD;
&lt;td&gt;AdventureWorks数据库中所有的客户&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;Customers/{customerID}&lt;/td&gt;&#xD;
&lt;td&gt;某一个指定的客户&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;Orders&lt;/td&gt;&#xD;
&lt;td&gt;AdventureWorks数据库中所有的订单&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;Orders/{orderID}&lt;/td&gt;&#xD;
&lt;td&gt;某一个指定的订单&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;Orders/{orderID}/Customer&lt;/td&gt;&#xD;
&lt;td&gt;某一个订单对应的客户&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;Customers/{customerID}/Orders&lt;/td&gt;&#xD;
&lt;td&gt;某一个客户的所有订单&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;根据数据库中数据的数量，Customers和Orders URIs可能返回大量数据项。因次，根据用户额外指定的查询参数以限制返回的数据项是非常有意义的。在本节的练习中，你将实现两个可选的查询参数，它们分别为skip和top，用户可以按照下面的方式使用这两个参数：&lt;/div&gt;&#xD;
&lt;div&gt;Orders?top=10&lt;/div&gt;&#xD;
&lt;div&gt;Orders?skip=500&lt;/div&gt;&#xD;
&lt;div&gt;Orders?skip=9&amp;amp;top=20&lt;/div&gt;&#xD;
&lt;div&gt;Top参数的目的是仅仅获取前n条数据记录，n为参数top的数值。上面的第一个例子从数据库中获取前10个订单。Skip参数的目的是忽略前n条数据记录然后从第n+1条记录开始获取数据记录。上面的第二个例子是从数据库中获取第501条开始的所有订单。第三个例子就是从第10条开始提取数据并提取20条，也就是提取从10到30的数据记录。使用这种方式合并参数允许客户端实现分页机制--以一种数据块大小可管理的方式获取数据。&lt;/div&gt;&#xD;
&lt;div&gt;与SOAP Web服务一样，实现REST Web服务的第一个任务是定义服务合约。合约指定对外公开的操作，并在操作与URIs之间建立关联以区分不同的资源。然后Web客户端程序就可以通过查询URIs调用这些操作。&lt;/div&gt;&#xD;
&lt;div &gt;练习：定义ProductsSales REST Web服务合约&lt;/div&gt;&#xD;
&lt;div&gt;1. 启动Visual Studio，并根据下表的内容创建一个新的解决方案&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 141px;" /&gt; &lt;col style="width: 356px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="height: 19px; background: gray;"&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;URI&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;描述&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;模板&lt;/td&gt;&#xD;
&lt;td&gt;空白解决方案&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;名字&lt;/td&gt;&#xD;
&lt;td&gt;ProductsSales&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;位置&lt;/td&gt;&#xD;
&lt;td&gt;Solutions\WCF\Step.by.Step\Chapter15&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;2. 添加一个新的类库项目到ProductsSales解决方案。将其命名为ProductsSalesService，该项目的保存位置为Solutions\WCF\Step.by.Step\Chapter15\ProductsSales&lt;/div&gt;&#xD;
&lt;div&gt;3. 在解决方案浏览器窗口中，添加ProductsSalesModel.edmx文件和App.config文件至ProductsSalesService项目。这两个文件可以从原书作者的源代码中的Chapter15文件夹中找到。&lt;/div&gt;&#xD;
&lt;div&gt;ProductsSalesModel.edmx文件是一个实体框架模型。它定义了两个实体，分别为Contact和SalesOrderHeader。在AdventureWorks数据库中，Contacts表包含了所有联系人和客户的详细信息，SalesOrderHeader表包含了客户开出的订单信息。App.config文件包含了供实体模型用以连接至AdventureWorks数据库的连接字符串。&lt;/div&gt;&#xD;
&lt;div&gt;4. 重新生成解决方案。该行为将从实体模型中生成Contact类和SalesOrderHeader类对应的代码。&lt;/div&gt;&#xD;
&lt;div&gt;5. 引用System.ServiceModel组件和System.ServiceModel.Web组件到ProductsSalesService项目。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在解决方案浏览器窗口，更改Class1.cs文件的名字为IProductsSales.cs。并允许Visual Studio更新所有对与Class1.cs的引用。&lt;/div&gt;&#xD;
&lt;div&gt;7. 在文本编辑模式下打开IProductsSales.cs文件，添加下面的using声明到文件的顶部。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636187344.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;8. 更改IProductsSales类为一个公共的接口，为添加ServiceContract特性。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636189014.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;9. 在IProductsSales接口中，添加GetAllOrders方法，并为该方法添加特性：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636207019.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该操作将返回SalesOrderHeader对象集合，其包含了每个订单的数据。OperationContract特性标明该方法为一个Web服务操作。WebGet特性标明这是一个REST操作，该操作响应HTTP GET请求并同过UriTemplate属性指定了客户端程序用以调用该操作的URI。该URI包含了可选的skip查询参数和top查询参数。在花括号中的ship和top可以被替换成客户端程序运行时所提供的值。GetAllOrders方法的定义中所包含的参数与上述两个参数有相同的名字；WCF运行时将把客户端程序传递的值对应地填充到GetAllOrders方法的参数上。请注意，虽然服务操作参数的顺序并不重要，但是这些参数的名字必须与WebGet特性类中UriTemplate属性值中花括号内的名字一样。&lt;/div&gt;&#xD;
&lt;div&gt;使用Description特性，你可以指定一些简单的描述性文字，这些文字会显示在服务的帮助页面中。&lt;/div&gt;&#xD;
&lt;div&gt;10. 添加GetOrder方法到IProductsSales接口中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636211753.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当客户端程序通过制定的URI格式Orders/OrderId访问服务时该操作运行，它将获取与订单对应的SalesOrderHeader对象。与上一步一样，注意该方法接收一个与UriTemplate花括号中展位符相匹配的参数。另一个值得关注地方是该参数的类型是字符串类型，尽管AdventureWorks数据中SalesOrderHeader表中对应列的数据类型为整数类型。这是因为URI中的导航元素必须为字符串类型。该限制并不适用于查询参数（GetAllOrders方法中skip和top参数就是整数类型）&lt;/div&gt;&#xD;
&lt;div&gt;11. 添加GetCustomerForOrder方法到IPorductsSales接口中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636222757.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当客户端程序通过制定的URI格式Orders/{OrderId}/Customer访问服务时该操作运行。&lt;/div&gt;&#xD;
&lt;div&gt;12. 添加GetAllCustomers，GetCustomer，和GetOrdersForCustomer方法到IProductsSales接口：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636231461.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上面三个方法定义的操作允许客户端程序分别获取所有的客户，某个特性客户的详细信息，以及某个特定客户的所有订单。&lt;/div&gt;&#xD;
&lt;div&gt;13. 重新生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：实现ProductsSales REST Web服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 添加一个新的类文件到项目ProductsSalesService项目中，并将其命名为ProductsSales.cs。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在文本编辑模式下打开ProductsSales.cs文件。然后在该文件的头部添加下面的using声明&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636232366.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;3. 修改ProductsSales类的定义，使其实现IProductsSales接口&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636242399.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;4. 添加下面的GetAllOrders方法到ProductsSales类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636255563.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该方法从AdventureWorks数据库中获取订单列表，最后返回SalesOrderHeader对象集合。Web应用程序通过寄宿该服务的URI来访问订单时调用该方法。Web应用程序可能还可以可选性地通过skip和top这两个参数来查询订单。这两个参数的默认值为0。由于数据据可能包含成百上千的数据订单记录。因此服务仅仅从数据库中提取前100条订单数据。当然用户可以显示地指定所需订单的确切数量。如果应用程序需要数据库中所有订单，那么它可以不停的调用GetAllOrders操作，并在每次调用GetAllOrders时为skip参数和top参数指定适当的值。&lt;/div&gt;&#xD;
&lt;div&gt;该方法并没有验证参数和检查错误；比如，它并没有验证skip和top参数是否为负数。如果异常发生，该方法抛出WebFaultException异常。该异常类型是一个特定版本FaultException类，唯一的区别是该异常生成一个HTTP错误而不是SOA错误。 WebFaultException异常构造函数的参数指定HTTP错误包含的HTTP状态代码。 Http状态代码是位于System.Net命名空间下的一个枚举类型，它定义了一个代码列表，你可以将其中的代码返回给一个Web应用程序。HttpStatusCode.BadRequest对应的代码为400错误消息，该针对所有异常都返回400错误消息，不包含详细的错误信息。由于安全原因，你应当避免在错误发生时返回太多的信息（尽管常见的最佳实践是记录错误的详细信息到本地服务器上，一般都是记录在系统事件日志的程序日志中）。&lt;/div&gt;&#xD;
&lt;div&gt;5. 添加GetOrder方法到ProductsSales类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636264266.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该方法根据传入参数（订单编号的值）找到订单并返回SalesOrderHeader对象。注意Web程序提供的订单编号是字符类型，因此在方法中我们需要将其转化为整数类型。如果没有找到订单，则返回一个null值。和前一个方法一样，如果发生异常，则返回错误请求错误。&lt;/div&gt;&#xD;
&lt;div&gt;6. 添加GetCustomerForOrder方法到ProductsSales类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636273319.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述方法获取开出指定订单的客户。为了完成该操作，LINQ查询通过ContractID列和CustomerID列连接AdventureWorks数据库中的Contact表与SalesOrderHeader表。查询完成后，返回一个Contact对象。&lt;/div&gt;&#xD;
&lt;div&gt;7. 添加GetAllCustomer，GetCustomer，和GetOrdersForCustomer方法到ProductsSales类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636305252.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上面三个方法与前面的步骤类似，不同的仅仅是聚焦于客户而不是订单罢了。&lt;/div&gt;&#xD;
&lt;div&gt;8. 重新生成解决方案&lt;/div&gt;&#xD;
&lt;div&gt;与SOAP Web服务一样，你可以使用IIS，或者创建自己的程序寄宿REST Web服务。 WCF在System.ServiceModel.Web命名空间下提供了WebServiceHost类，该类是一个特定版本的ServiceHost类，其为REST Web服务提供了寄宿环境。WebServiceHost类为其寄宿的服务增加了一个名为WebHttpBehavior的端点行为。该行为使服务能使用HTTP请求而不是SOAP请求接受和发送消息。你将在下面的练习中使用该类。&lt;/div&gt;&#xD;
&lt;div &gt;练习：寄宿ProductsSales REST Web服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 添加一个新的控制台程序项目到ProductsSales解决方案，并将其重命名为ProductsSalesHost，该项目的保存位置为Solutions\WCF\Step.by.Step\Chapter15\ProductsSales文件夹中。&lt;/div&gt;&#xD;
&lt;div&gt;2. 打开ProductsSalesHost项目的属性页。然后点击程序标签，设置Traget Framework属性为.NET Framework 4，并允许Visual Studio关闭和重新打开该项目。&lt;/div&gt;&#xD;
&lt;div&gt;3. 引用ProductsSalesService项目到ProductsSalesHost。然后引用System.ServiceModel组件和System.ServiceModel.Web。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在文本编辑模式下打开Program.cs文件，然后添加下面的using声明：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636317793.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 在Programm类中，添加下面的声明：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636323224.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码基于ProductsSalesService类创建一个WebServiceHost对象，然后启动该服务并侦听请求。上述代码与之前章节中寄宿SOAP服务的代码是相似的。&lt;/div&gt;&#xD;
&lt;div&gt;6. 删除ProductsSalesHost的app.config文件，并从原书所附代码中的Chapter15文件夹下拷贝App.config到ProductsSalesHost项目中。&lt;/div&gt;&#xD;
&lt;div&gt;7. 使用WCF服务配置管理器打开ProductsSalesHost下的App.config文件。&lt;/div&gt;&#xD;
&lt;div&gt;8. 在配置面板，在服务文件夹上点击右键，然后点击创建新的服务。然后在右边面板中，在名字字段处，输入ProductsSalesService.ProductsSales。&lt;/div&gt;&#xD;
&lt;div&gt;9. 在配置面板，展开ProductsSalesService.ProductsSales服务文件夹，然后在文件夹下的端点处击右键选择创建新的服务端点，并按照下表的值设置新创建服务端点的对应属性。&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 141px;" /&gt; &lt;col style="width: 356px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="height: 19px; background: gray;"&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;值&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;地址&lt;/td&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;绑定&lt;/td&gt;&#xD;
&lt;td&gt;webHttpBinding&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;合约&lt;/td&gt;&#xD;
&lt;td&gt;ProductsSalesService.IProductsSales&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;IProductsService服务中的操作上的WebGet特性类中的uriTemplate属性指定的URIs将应用到服务的相对地址上。比如，GetCustomerForOrder操作UriTemplate属性的值为Orders/{orderID}/Customer。那么客户端程序就可以通过 http://localhost:8000/Sales/Orders/54545/Customer 来调用GetCustomerForOrder操作，其中54545是订单编号。&lt;/div&gt;&#xD;
&lt;div&gt;webHttpBinding绑定配置为REST Web服务配置了一个端点，该端点对外公接收HTTP请求而不是SOAP消息。&lt;/div&gt;&#xD;
&lt;div&gt;10. 保存配置文件并推出WCF服务配置管理器&lt;/div&gt;&#xD;
&lt;div&gt;11. 重新生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;当你从Web引用程序中提交一个HTTP GET请求后，ProductsSales服务将响应该请求，你可以使用Web浏览器比如IE来向服务发送请求，在下面的练习中我们将使用IE想ProductsSales服务发送请求。请注意WebServiceHost类禁用了对外公布WSDL元数据。但是，当REST Web服务启动了帮助后，你仍然可以查询该REST服务。因此，在下面的练习中，你将配置ProductsSalesService服务提供帮助信息，列出服务所支持的URLs，当你使用每个URI查询时构造返回数据。&lt;/div&gt;&#xD;
&lt;div &gt;练习：使用Web浏览器测试ProductsSales REST Web服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 在解决方案浏览器窗口中，设置ProductsSalesHost项目为解决方案的启动项目，然后在非调试模式下启动解决方案。确认ProductsSalesHost程序的控制台窗口显示消息"Service Running"。&lt;/div&gt;&#xD;
&lt;div&gt;2. 启动IE，然后输入地址栏http://localhost:8000/sales/orders，然后按ENTER键。浏览器访问该URI将调用GetOrders操作， 该操作返回一个SalesOrderHeader对象集合，该集合被序列化为XML。 IE显示序列化后的XML数据，如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636349618.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;3. 指定另外一个URL：http://localhost:8000/Slaes/Orders?skip=100&amp;amp;top=5，然后检查返回结果。这次你会发现第一个销售订单编号为43759。&lt;/div&gt;&#xD;
&lt;div&gt;4. 然后你还可以分别测试其他的一写URL，检查各种正确和错误的结果&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 319px;" /&gt; &lt;col style="width: 271px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="height: 19px; background: gray;"&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;URL&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;结果&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales/Orders?top=-10&lt;/td&gt;&#xD;
&lt;td&gt;返回错误也页面&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales/Orders/43700&lt;/td&gt;&#xD;
&lt;td&gt;返回定边编号为43700的订单详细内容&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales/Orders/30000&lt;/td&gt;&#xD;
&lt;td&gt;返回空白页面，因为订单编码30000不能找到&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales/Orders/43700/Customer&lt;/td&gt;&#xD;
&lt;td&gt;范围订单号码为43700的客户信息&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales/Customers&lt;/td&gt;&#xD;
&lt;td&gt;返回前100个客户信息&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales/Customers/100&lt;/td&gt;&#xD;
&lt;td&gt;客户编号为100的客户信息&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales/Customers/100/Orders&lt;/td&gt;&#xD;
&lt;td&gt;客户编号为100的客户的所有订单&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;5. 返回到ProductsSales服务的控制台程序窗口，按ENTER键停止服务，然后关闭控制台窗口，但是保持IE处于运行状态。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在Visual Studio中，使用WCF服务配置编辑器打开App.config&lt;/div&gt;&#xD;
&lt;div&gt;7. 在配置面板，展开高级文件夹，在端点行为上点击右键，然后点击创建一个新的端点行为。在右边面板中，清楚名字的值，然后在右下角，点击添加按钮以添加一个webHttp行为扩展元素到当前端点的行为中。&lt;/div&gt;&#xD;
&lt;div&gt;8. 在配置面板，点击未命名端点下的webHttp节点。在WebHttp面板，设置helpEnabled属性为true。该属性允许REST Web服务公布一个帮助页面，该页面描述服务的所有操作，服务响应消息的XML结构，以及响应消息的JSON结构。&lt;/div&gt;&#xD;
&lt;div&gt;9. 保存配置文件，然后关闭WCF服务配置编辑器。&lt;/div&gt;&#xD;
&lt;div&gt;10. 在Visual Studio 中，在非调试模式下运行解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;11. 返回到IE中，然后在地址栏输入http://localhost:8000/Sales/help。IE将转向ProductsService服务的帮助页面。其内容如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/20111025163637405.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;12. 点击Customers/{customerID} URI的GET链接。另外一个描述GetCustomer操作响应消息的页面将出现：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636465300.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;13. 检查其他操作对应的响应消息的格式，然后关闭IE。&lt;/div&gt;&#xD;
&lt;div&gt;14. 返回到ProductsSales服务的控制台程序窗口，按ENTER键停止服务，然后关闭控制台窗口。&lt;/div&gt;&#xD;
&lt;div&gt;使用Web程序通过URI发送请求与使用浏览器发送请求是一样地，此外你还可以过程性的客户端应用调用REST Web服务的操作。构建这样一个客户端程序与构建程序访问SOAP Web服务是相似的；你可以创建一个代理类然后使用常规方式调用服务公开的方法。不幸的是，Visual Studio当前并没有为REST Web服务提供生产代理类的功能；但是，通过扩展System.ServiceModel.ClientBase类手动实现一个代理类（请参考第十一章）并不困难。下面的联系我们将使用这种方式创建客户端程序访问REST Web服务。&lt;/div&gt;&#xD;
&lt;div &gt;练习：创建过程化客户端程序访问ProductsSales REST Web服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 在Visual Studio中，添加一个新的控制台程序，并将其命名为ProductsSalesClient，该项目的保存位置为Solutions\WCF\Step.by.Step\Chapter15\ProductsSales文件夹中。&lt;/div&gt;&#xD;
&lt;div&gt;2. 打开ProductsSalesClient项目的属性页，设置TargetFramework属性为.NET Framework 4。并允许Visual Studio关闭然后重新打开该项目。&lt;/div&gt;&#xD;
&lt;div&gt;3. 引用项目ProductsSalesService到ProductsSalesClient；然后引用System.Data.Entity，System.ServiceModel组件到ProductsSalesClient项目。&lt;/div&gt;&#xD;
&lt;div&gt;4. 添加一个新的类文件到ProductsSalesClient项目，并命名该文件为ProductsSalesProxy.cs。&lt;/div&gt;&#xD;
&lt;div&gt;5. 然文本编辑模式下打开ProductsSalesProxy.cs文件。然后添加下面的using声明到该文件的头部：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636493612.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;6. 修改ProductsSalesProxy类的定义以使其派生ClientBase&amp;lt;IProductsSales&amp;gt;类，并实现IProductsSales接口：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636494517.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;7. 实现IProductsSales接口的GetAllOrders方法：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636496186.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;正如第十一章的描述，该方法简单地路由调用经过与服务相连接的通道，然后调用服务中相同名字的操作。该方法指定了skip参数和top参数为可以选参数，它们的默认值均为0。&lt;/div&gt;&#xD;
&lt;div&gt;8. 添加GetOrder，GetCustomerForOrder，GetAllCustomers，GetCustomer和GetOrdersForCustomer方法到ProductsSalesProxy类。这些方法与GetAllOrders采用同样的模式实现；他们都通过与服务连接的通道调用服务中对应的方法。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636505762.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;9. 在文本编辑模式下打开ProductsSalesClient项目下的Program.cs文件，然后添加下面的using声明到该文件的头部：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110251636512763.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;10. 添加下面的语句到Main方法中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/20111025163655651.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上面的代码创建ProductsSalesProxy类的一个实例，然后功过带代理调用每个可能的方法以执行下面的测试：&lt;/div&gt;&#xD;
&lt;ul&gt;&#xD;
&lt;li&gt;使用GetAllOrders获取所有订单，并为skip参数和top参数指定值。&lt;/li&gt;&#xD;
&lt;li&gt;通过GetOrder方法获取订单编号为43687的详细信息&lt;/li&gt;&#xD;
&lt;li&gt;通过GetCustomerForOrder方法获取订单编号为43867的客户信息&lt;/li&gt;&#xD;
&lt;li&gt;通过GetAllCustomers方法获取订单编码在75和90之间的客户信息&lt;/li&gt;&#xD;
&lt;li&gt;通过GetCustomer方法获取客户编号为99的客户信息&lt;/li&gt;&#xD;
&lt;li&gt;通过GetOrdersForCustomer方法获取客户编号为99的所有订单信息&lt;/li&gt;&#xD;
&lt;/ul&gt;&#xD;
&lt;div&gt;11. 使用WCF配置编辑器打开ProductsSalesClient项目下的App.config文件。&lt;/div&gt;&#xD;
&lt;div&gt;12. 在配置面板，展开客户端文件夹，然后在端点上点击右键，然后点击创建新的客户端端点。&lt;/div&gt;&#xD;
&lt;div&gt;13. 在客户端端点面板，按照下表的值设置新创建端点的对应属性。&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 141px;" /&gt; &lt;col style="width: 356px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="height: 19px; background: gray;"&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;td&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;值&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;地址&lt;/td&gt;&#xD;
&lt;td&gt;http://localhost:8000/Sales&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;绑定&lt;/td&gt;&#xD;
&lt;td&gt;webHttpBinding&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;合约&lt;/td&gt;&#xD;
&lt;td&gt;ProductsSalesService.IProductsSales&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;14. 在配置面板，展开高级文件夹，然后在端点行为上电价右键，然后点击创建新的端点行为配置。在右边面板，清楚名字的值。然后在面板的右下方，点击添加按钮，然后添加一个webHttp行为扩展元素到端点行为中。&lt;/div&gt;&#xD;
&lt;div&gt;注意：与使用WebServiceHost类寄宿服务不同，访问REST服务的客户端程序的端点不会自动地被配置为发送HTTP请求，因为你必须总是添加WebHttpBehavior行为到客户端端点中。&lt;/div&gt;&#xD;
&lt;div&gt;15. 保存配置文件，并退出WCF服务配置编辑器。&lt;/div&gt;&#xD;
&lt;div&gt;16. 在Visual Studio中，只是ProductsSalesClient项目和ProdudtsSalesHost项目均为解决方案的启动项目。然后在非调试模式下启动解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;17. 在ProductsSalesClient控制台窗口中，按下ENTER键。各个测试都将成功地运行，并逐步的产生如下图所示的结果：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/2011102516365710.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;18. 在ProductsSalesClient控制台窗口中按ENTER键以关闭客户程序；然后在ProductsSalesHost控制台中按ENTER键以关闭寄宿程序。&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2224118.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/10/25/2224118.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/10/22/2221191.html</id><title type="text">WCF 4.0 进阶系列 – 第十四章 检测服务和路由消息（第四部分）</title><summary type="text">实现手动路由无疑是非常强大的技术，但是常见的场景下你所希望的仅仅是，基于请求的某些特性而不是编写一些动态的算法以实现路由消息。为了处理这样的场景，WCF提供了RoutingService类，该类位于System.ServiceModel.Route命名空间下。本小节不再使用负载均衡算法路由消息；而是利用RoutingService类实现基于消息头信息中包含的请求的类型路由消息到对应的服务实例。</summary><published>2011-10-22T07:37:00Z</published><updated>2011-10-22T07:37:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/10/22/2221191.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/10/22/2221191.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;p&gt;&lt;strong&gt;使用路由服务类&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;实现手动路由无疑是非常强大的技术，但是常见的场景下你所希望的仅仅是，基于请求的某些特性而不是编写一些动态的算法以实现路由消息。为了处理这样的场景，WCF提供了RoutingService类，该类位于System.ServiceModel.Route命名空间下。给命名空间下的类图如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110221524159999.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;RoutingService类的目的在于：当消息达到时，基于内容实现路由。你可以配置RoutingService对象，使其检查消息头部的信息然后路由消息；甚至还可以使其转化消息体的内容然后基于转化后的内容路由请求。你可以通过包含一个或多个路由过滤器（过滤器指定了与消息的匹配条件以及和当匹配成功后消息推送目的地信息）的过滤表配置RoutingService类。一个路由过滤器实际上是MessageFilter类的实例。&lt;/div&gt;&#xD;
&lt;div&gt;你可以动态地创建MessageFilter对象，并将其与RoutingService对象关联。但大多数完成方式是静态地为每个路由过滤器添加过滤信息到寄宿RoutingService对象的应用程序的配置文件中；并使WCF运行时在运行服务时根据配置文件创建必要的MessageFilter对象。在下面练习中我们也将使用该方式。&lt;/div&gt;&#xD;
&lt;div&gt;在下面的练习中，你将更改ShoppingCartService服务的路由策略。不再使用负载均衡算法交替地发送请求至在不同端口侦听请求的ShoppingCartService实例；你将基于请求的类型路由消息：AddItemToCart消息和RemoveItemFromCart消息将被路由到ShoppingCartService服务的一个实例，而GetShoppingCart和CheckOut消息将路由到另外一个实例。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：寄宿和配置RoutingService服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 使用Visual Studio打开WCF\Step.by.Step\Solutions\Chapter14\ShoppingCartServiceWithRouter文件夹下的ShoppingCart.sln解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;该方案包含持续性ShoppingCartService服务，ShoppingCartServiceHost和ShoppingCartGUIClient项目，它从第七章复制过来。当前，ShoppingCartService服务配置了两个HTTP端点，这两个端点在9010和9020侦听请求，它们使用BasicHttpContextBinding绑定。&lt;/div&gt;&#xD;
&lt;div&gt;2. 添加一个新的控制台应用程序项目到ShoppingCart解决方案中。该控制台项目的名字为StaicRouter，并保存在WCF\Step.by.Step\Solutions\Chapter14\ShoppingCartServiceWithRouter文件夹下。&lt;/div&gt;&#xD;
&lt;div&gt;3. 引用System.ServiceModel, System.ServiceModel.Routing组件到StaticRouter项目，然后再引用项目ShopingCartService到StaticRouter项目。&lt;/div&gt;&#xD;
&lt;div&gt;4. 打开StaticRouter项目的Programm.cs文件；然后在文件的头部添加下面的using语句&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110221524169542.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 添加下面的代码（高亮部分）到programcs文件main方法中：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110221524164624.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述的代码你现在应该非常熟悉。该代码创建RoutingService类的一个实例，然后将其寄宿在ServiceHost对象中。你将在后续的步骤中为该服务定义配置文件。你还将制定ShoppingCartService服务的地址以使客户端的请求能路由到该地址。&lt;/div&gt;&#xD;
&lt;div&gt;6. 为StaticRouter项目添加配置文件，然后使用WCF服务配置管理器打开该配置文件。&lt;/div&gt;&#xD;
&lt;div&gt;7. 在配置面板，在服务文件夹上点击右键，然后点击创建新的服务。在右边的面板中，在名字字段处输入System.ServiceModel.Routing.RoutingService。该名字为RoutingService类的完整名。&lt;/div&gt;&#xD;
&lt;div&gt;8. 在配置面板，选择新创建服务，然后在其文件夹下的端点处点击右键，然后选择创建新的服务端点。在服务端点面板，按照下表的内容设置对应的属性的值。&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 94px;" /&gt; &lt;col style="width: 496px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="background: gray;"&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td style="padding-left: 7px; padding-right: 7px; border-top: solid 0.5pt; border-left: none; border-bottom: solid 0.5pt; border-right: solid 0.5pt;"&gt;&#xD;
&lt;div&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;值&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;地址&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;http://localhost:9000/ShoppingCartService/ShoppingCartService.svc&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;绑定&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;basicHttpContextBinding&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;合约&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;System.ServiveModel.Routing.IRequestReplyRouter&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;合约定义了RoutingService对象实现的消息传输模式。IRequestReply接口基于一个通道处理基本的请求/回复双向模式，推送客户端的请求消息至服务并路由服务的响应消息到对应的客户端。还可以使用其他的合约。比如IDuplexSessionRouter，该接口允许RoutingService对象路由服务器的回调消息到客户端；或者ISimplexSessionRouter，该接口单向地路由消息到实现Session的服务；或者ISimplexDatagramRouter，该接口支持没有提供Session的服务。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;span style="color: gray;"&gt;&lt;em&gt;注意：在第16章，我们将详细介绍如何创建并使用双向通道。 &lt;/em&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;div&gt;IRequestReplyRouter接口定义在System.ServiceModel.Routing命名空间下，其内容如下所示：&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110221524189249.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;IRequestReplyRouter接口允许RoutingService类异步地推送请求和响应消息；该接口还支持事务。请注意Action和ReplyAction属性均被标注为*.&lt;/div&gt;&#xD;
&lt;div&gt;9. 在配置面板，展开高级文件夹，然后点击服务行为文件夹。在服务行为面板中，点击创建新的服务行为配置链接。&lt;/div&gt;&#xD;
&lt;div&gt;10. 在右边面板中，清除行为的名字，然后点击添加按钮，然后选择添加一个路由行为元素。该元素包含了你可以用来配置RoutingService服务的行为或者用以指定路由表名字的属性。&lt;/div&gt;&#xD;
&lt;div&gt;11. 在配置面板，展开未命名行为，然后点击路由元素。在右边的面板中，在FilterTableName字段处输入ShoppingCartServiceRoutingTable，请确认RouteOnHeadersOnly属性的值为True，并设置SoapProcessingEnabled属性为False。&lt;/div&gt;&#xD;
&lt;div&gt;你将在后续的步骤中为ShoppingCartServiceRoutingTable过滤表指定消息过滤元素。RouterOnHeadersOnly属性表明：过滤表中过滤元素在路由经过RoutingService服务的消息时，是仅仅定义过滤消息头的规则，还是过滤消息内容的规则。在本练习中，将根据消息头包含的请求动作信息过滤消息，因此RouterOnHeadersOnly属性设置为True。&lt;/div&gt;&#xD;
&lt;div&gt;当RoutingService服务接收到一条请求消息，路由服务基于不同的绑定路由该请求到一个目标服务，这可能与消息传输需求的实现有所不同。SoapProcessingEnabled属性指定了RoutingService服务是否在按照绑定的要求转换消息。如果该属性设置为false，那么路由服务不对消息做任何更改就直接推送消息至目标服务；否则，RoutingService服务将检查消息并将消息的格式转换为目标服务需求的格式。在本练习中，ShoppingCartService服务使用BasicHttpContextBinding绑定在服务和客户端程序之间传输包含了服务实例ID上下文信息。由于你需要将这些上下文信息没有任何干扰的经过RoutingService，因为该属性设置为false。&lt;/div&gt;&#xD;
&lt;div&gt;12. 在配置面板，展开客户端文件夹，然后再端点文件夹上点击右键，然后在添加创建新的客户端端点。在客户端端点面板，按照下表的内容设置对应的属性的值：&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 94px;" /&gt; &lt;col style="width: 496px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="background: gray;"&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;值&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;名字&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;ShoppingCartServieHttpEndpoint1&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;地址&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;http://localhost:9010/ShoppingCartService/ShoppingCartService.svc&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;绑定&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;basicHttpBinding&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;合约&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;*&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;地址与ShoppingCartHost程序寄宿的ShoppingCartService服务的第一个端点地址相匹配。合约中*号字符表明服务接收任何消息而不是某些指定的服务合约的消息。&lt;/div&gt;&#xD;
&lt;div&gt;请注意Binding属性设置为basicHttpBinding而不是basicHttpContextBinding。客户端程序使用BasicHttpContextBinding绑定连接到RoutingService服务，其使得在消息头部中包含了诸如服务实例的ID这样的上下文信息。如果路由服务也使用上下文绑定连接到ShoppingCartService服务，那么RoutingService服务将会处理从ShoppingCartService服务接收到消息中的上下文信息，把这些上下文信息从消息头部中删除，因此这些上下文信息不会传递给客户端程序。而制定BasicHttpBindg绑定后则会阻止RoutingService服务再 把从ShoppingCartService服务响应的消息回传给客户端之前，从响应消息中查询上下文信息并删除这些上下文信息。此外，在前面步骤中我们已经提到，设置SoapPrecessingEnabled属性为false也阻止了RoutingService服务移除客户端程序发送消息中的上下文信息，因此RoutingService服务直接推送这些消息只ShoppingCartService--不做任何更改。&lt;/div&gt;&#xD;
&lt;div&gt;13. 按照下表内容添加第二个客户端端点&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 94px;" /&gt; &lt;col style="width: 496px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="background: gray;"&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;&lt;span style="color: #f2f2f2;"&gt;&lt;strong&gt;值&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;名字&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;ShoppingCartServieHttpEndpoint1&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;地址&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;http://localhost:9020/ShoppingCartService/ShoppingCartService.svc&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;绑定&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;basicHttpBinding&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;合约&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td&gt;&#xD;
&lt;div&gt;*&lt;/div&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;14. 保存配置文件，然后退出WCF服务配置管理器&lt;/div&gt;&#xD;
&lt;div&gt;15. 打开StaticRouter项目下的App.config文件，你将会看到上述的端点已经添加到配置文件中。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/20111022152420527.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;lt;fliters&amp;gt;元素中的过滤器列表定义了路由消息的规则。每个过滤器都有一个唯一的名字并且指定了一个filterType，过滤类型指定消息过滤器对象创建的过滤消息的类型。Action类型会导致WCF运行时创建一个ActionMessengerFilter对象，该对象可以基于消息头中的Action元素过滤请求。filterData属性指定了与Action对应的值。如果一个Action与filterData的值匹配，WCF运行时检查&amp;lt;filterTable&amp;gt;片段内的与filter名字像对应的每个实体；然后把消息路由到匹配实体指定的端点。比如，如果RoutingService服务接收到一个消息，该消息的头部的action值为http://&lt;/div&gt;&#xD;
&lt;div&gt;adventure-works.com/2010/06/04/ShoppingCartService/AddItemToCart，那么WCF运行时将路由消息至过滤表中ShoppingCart1实体定义的端点ShoppingCartServiceHttpEndpoint1。过滤表中的EndpointName属性引用&amp;lt;client&amp;gt;片段内定义的端点。&lt;/div&gt;&#xD;
&lt;div&gt;使用WCF，你还可以基于其他条件过滤消息。比如，你可以指定EndpointAddress以定义一个EndpointAddressMessageFilter。如果你希望基于消息体的内容执行过滤，你可以通过XPath创建一个XPathMessageFilter对象，此时flterData属性用于定义消息中数据的路径并且该属性的值对应一个Xpath表达式。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;span style="color: gray;"&gt;&lt;em&gt;更过关于其他过滤类型的详细信息，请参考http://msdn.microsoft.com/en-us/library/system.servicemodel.routing.configuration.filtertype.aspx). &lt;/em&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;div&gt;16. 重新生成项目。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;现在你可以测试RoutingService宿主程序和配置。但是，为了演示消息被正确的路由到两个ShoppingCartService服务端点，你将配置ShoppingCartHost程序，并添加一个服务行为已显示每条接收和发送消息的动作和地址。&lt;/div&gt;&#xD;
&lt;div &gt;练习：测试RoutingService服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 在ShoppingCartHost项目中，引用MessageInspector组件。&lt;/div&gt;&#xD;
&lt;div&gt;2. 使用WCF服务配置管理器打开ShoppingCartHost项目的App.config文件。在配置面板，展开高级文件夹，展开扩展，然后点击行为元素扩展。&lt;/div&gt;&#xD;
&lt;div&gt;3. 在右下面板，点击创建新的行为元素扩展。在扩展配置元素编辑器中，在名字文本框处，输入messageInspector&lt;/div&gt;&#xD;
&lt;div&gt;添加类型字段，然后该字段后的省略号按钮，在行为扩展类型浏览对话框中，选择Chpater14文件夹，然后点击MessageInspector组件，然后点击打开按钮。在行为扩展类型类型对话框中，点击MessageInspector.ShoppingCartBehaviroExtensionElement，然后点击打开按钮。&lt;/div&gt;&#xD;
&lt;div&gt;在扩展配置元素编辑对话框中，点击确认按钮。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在配置面板，展开高级文件夹下的服务行为，然后点击DurabelServiceBehavior行为。在右边面板中，点击添加按钮，然后添加messageInspector行为元素至DurableServiceBehavior行为。&lt;/div&gt;&#xD;
&lt;div&gt;5. 保存配置文件并退出WCF服务配置管理器&lt;/div&gt;&#xD;
&lt;div&gt;6. 在解决方案浏览器窗口，在ShoppingCart方案上点击右键，然后设置方案的启动项目。把StaticRouter项目添加到启动项目组中。然后点击确认按钮&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110221524213135.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;7. 在非调适模式下启动解决方案。Shopping Cart GUI客户端窗口将出现，同事还会出现一个ShoppingCartHost控制台程序和StaticRouter控制台程序。&lt;/div&gt;&#xD;
&lt;div&gt;8. 在Shopping Cart GUI窗口中的产品编码处输入SA-M198，然后点击添加按钮。接着继续添加另外一个产品WH-H098到购物车中。最后点击结算按钮。&lt;/div&gt;&#xD;
&lt;div&gt;客户端程序将得到与我们之前练习一样的结果。&lt;/div&gt;&#xD;
&lt;div&gt;9. 切换到ShoppingCartHost项目的控制台窗口。你将看到消息探测器所显示的消息。确认所有AddItemToCart请求都发送至在9010端口侦听的服务；而Chekcout消息发送至在9020端口侦听的服务。&lt;/div&gt;&#xD;
&lt;div&gt;10. 关闭Shopping Cart GUI窗口，分别在控制台窗口中按ENTER键关闭服务寄宿窗口和StaticRouter窗口。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;在本章，你已经了解到如何通过配置服务发现以从服务的实现中分离服务的地址；此外你还学习到WCF所支持的三种常见的服务发现模式：ad hoc, 声明和托管。&lt;/div&gt;&#xD;
&lt;div&gt;你还仔细地研究了如何实现路由WCF服务。你已经了解到WCF服务的运行时如何确定怎么处理请求消息。接收到消息的通道分发器逐个查询其端点分发器对象。一个端点分发器对外公开AddressFilter和ContractFilter属性，ChannelDispatcher对象使用这两个属性以发现该端点分发器是否可接收消息。被通道分发器选中的端点分发器开始处理消息并调用服务对应的方法。你可以通过提供自己的AddressFilter和ContractFilter对象，以及实现IDispatchOperationSelector接口；以实现自定义端点分发器接收和处理消息的方式。&lt;/div&gt;&#xD;
&lt;div&gt;此外，你还看到如何定义一个通用的WCF服务，该服务可以作为其他服务的路由器，该路由服务实现了一个方法，该方法可以接收任何消息并推送消息到可以处理这些消息的服务。&lt;/div&gt;&#xD;
&lt;div&gt;最后，你了解到如何使用和配置RoutingService类以实现基于在配置文件中的定义的信息路由消息。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2221191.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/10/22/2221191.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/10/09/2203926.html</id><title type="text">WCF 4.0 进阶系列 – 第十四章 检测服务和路由消息（第三部分）</title><summary type="text">有时候需要从一个服务推送消息至另一个完全不同的服务以处理该消息。比如，当客户端程序发送请求至企业内部不同的WCF服务，但是所有这些请求实际上都首先通过前端的服务，该服务相当于WCF服务的防火墙。前端服务可以实现一个路由机制，通过检查消息的行为或地址以推送请求到真正的服务；该项技术就是基于地址的路由。前端服务还可以过滤消息，如果消息是非法请求那么阻塞该消息，消息过滤的效果取决于前端服务上所实施的智能检查的程度。另外一套类似的机制就是基于消息的内容路由消息；这项技术就是基于内容的路由。前端服务还可以提供其他特性，比如负载均衡。客户端的请求达到单个前端服务，前端服务使用负载均衡算法分发请求到真正运行WCF服务的服务器。</summary><published>2011-10-09T10:01:00Z</published><updated>2011-10-09T10:01:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/10/09/2203926.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/10/09/2203926.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;p&gt;&lt;strong&gt;实现路由&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;路由适用于处理与监测服务稍微不同的场景。&lt;/div&gt;&#xD;
&lt;div&gt;有时候需要从一个服务推送消息至另一个完全不同的服务以处理该消息。比如，当客户端程序发送请求至企业内部不同的WCF服务，但是所有这些请求实际上都首先通过前端的服务，该服务相当于WCF服务的防火墙。前端服务可以运行在企业外围网络的计算机上，而实际上处理请求的WCF服务可以寄宿在位于企业内部受保护的网络中。前端服务可以实现一个路由机制，通过检查消息的行为或地址以推送请求到真正的服务；该项技术就是基于&lt;span &gt;地址的路由&lt;/span&gt;。前端服务还可以过滤消息，如果消息是非法请求那么阻塞该消息，消息过滤的效果取决于前端服务上所实施的智能检查的程度。&lt;/div&gt;&#xD;
&lt;div&gt;另外一套类似的机制就是基于消息的内容路由消息；这项技术就是基于&lt;span &gt;内容的路由&lt;/span&gt;。比如，如果你寄宿一个商业服务，你可能根据用户支付的费用为用户提供不同的服务。高级用户可以向高性能的服务发送请求以快速地获取响应，而普通用户只能从较低的性能的服务得到响应。所有的用户均使用相同客户端程序向相同的前端服务发送请求，但前端服务检查消息的某个方面，比如发送请求的用户身份，然后根据请求身份消息推送消息到对应的目的地。&lt;/div&gt;&#xD;
&lt;div&gt;前端服务还可以提供其他特性，比如负载均衡。客户端的请求达到单个前端服务，前端服务使用负载均衡算法分发请求到真正运行WCF服务的服务器。&lt;/div&gt;&#xD;
&lt;div&gt;WCF提供两个主要机制实现路由，选择哪一个机制取决于需求的复杂程度。System.ServiceModel.Routing命名空间下的RoutingService类通过检查消息内容和消息地址，为路由消息到其他的服务提供了配置信息。相似地，如果需要实现更动态的或低级别的方式（如同负载均衡路由器的需求），你可以基于条件（比如当前服务的负载）手动地路由消息。&lt;/div&gt;&#xD;
&lt;div&gt;在研究如何使用RoutingService之前，有必要解释一下当WCF服务从客户端接收到消息后到底发生了什么，以及如何使用这些信息实现自定义的路由服务。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;手动路由消息&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;一个服务可以对外提供多个端点，每个端点都配置了相同的或者不同的服务合约。当WCF服务接收到一条消息，它必须检查该消息以决定哪个服务端的应当处理该消息。你可以自定义WCF选择端点的方式，这就为你提供了在服务内部更改WCF路由消息的方式。&lt;/div&gt;&#xD;
&lt;div&gt;再次回顾ChannelDispatcer和EndpointDispatcher对象&lt;/div&gt;&#xD;
&lt;div&gt;在第&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/07/21/2112526.html"&gt;十一章"编写代码控制配置和通信"&lt;/a&gt;中，你已经了解到服务端的WCF运行时为每个不同的地址和绑定的组合创建一个通道堆栈，然后使用该堆栈与服务进行通讯。每个通道堆栈都有一个ChannelDispatcher对象，并且该对象对应一个或者多个EndpointDispatcher对象。&lt;span &gt;ChannelDispatcher对象的目标是决定哪一个EndpointDispatcher对象应该处理消息。EndpointDispatcher对象的角色是转化消息请求为方法的调用并调用服务对应的方法。&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;div&gt;服务对外暴露的每个地址和绑定的组合可被多个端点共享。比如第六章ProductsServiceLibrary解决方案中的ProductsServiceHost项目中，为服务合约Products.IProductsService和Products.IProductsServcieV2定义了下列的服务和端点：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;span style="font-family: Consolas; font-size: 10pt;"&gt;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515;"&gt;services&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515;"&gt;service&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;name&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;Products.ProductsService&lt;/span&gt;"&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515;"&gt;endpoint&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;address&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;http://localhost:8010/ProductsService/Service.svc&lt;/span&gt;"&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;binding&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;ws2007HttpBinding&lt;/span&gt;"&lt;span style="color: blue;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;bindingConfiguration&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;""&lt;span style="color: blue;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;name&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;WS2007HttpBinding_IProductsService&lt;/span&gt;"&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;contract&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;Products.IProductsService&lt;/span&gt;"&lt;span style="color: blue;"&gt;&amp;nbsp;/&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515;"&gt;endpoint&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;address&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;http://localhost:8010/ProductsService/Service.svc&lt;/span&gt;"&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;binding&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;ws2007HttpBinding&lt;/span&gt;"&lt;span style="color: blue;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;bindingConfiguration&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;""&lt;span style="color: blue;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;name&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;WS2007HttpBinding_IProductsService&lt;/span&gt;"&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: red;"&gt;contract&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;"&lt;span style="color: blue;"&gt;Products.IProductsServiceV2&lt;/span&gt;"&lt;span style="color: blue;"&gt;&amp;nbsp;/&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515;"&gt;service&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515;"&gt;services&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt; &lt;/span&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;注意上述配置定义了两个端点，但是它们共享相同的地址和绑定组合；唯一的区别是这两个端点的服务合约不一样。上述配置将导致WCF运行时创建单个通道堆栈和ChannelDispatcher对象。但是通道堆栈可能关联两个的端点；每个服务合约使用一个服务端点。因此，WCF运行时为通道堆栈创建两个EndpointDispatcher对象，并将这两个对象添加到与ChannelDispatcher对象关联的 EndpointDispatcher对象集合中。如果ProductsServiceHost项目还为ProductsService提供TCP端点，那么WCF运行时将创建两个通道堆栈（一个用于HTTP端点，另外一个用于TCP端点），每个通道都有用自己的ChannelDispatcher对象。TCP端点拥有自己的EndpointDispatcher对象。&lt;/div&gt;&#xD;
&lt;div&gt;下图展示了端点，通道堆栈和分发对象之间的关系&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/20111009170457745.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当服务从通道堆栈接收到一条消息，通道堆栈顶部的ChannelDispatcher对象查询与之关联的EndpointDispatcher对象以决定哪一个端点可以处理该条消息。如果没有发现对应的端点，WCF运行时将触发一个UnknownMessageReceived事件。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;EndpointDispatcher对象与过滤器&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;EndpointDispatcher对象如何指明其是否可以处理一条消息？ 端点分发器对外提供可供ChannelDispatcher查询的两个属性AddressFilter和ContractFilter。AddressFilter属性是EndpointAddresMessageFilter类的一个实例变量。EndpointAddressMessageFilter类提供一个名为Match的方法，该方法接收一个消息对象为输入参数并返回一个Boolean值以指明EndpointDispatcher对象能否识别消息头部中包含的地址。ContractFilter属性是ActionMessageFilter类的一个实例变量。该类同样提供一个Match方法，该方法接受一个消息对象为输入参数，并返回一个boolean值以指明EndpointDispatcher对象能否处理消息头部中包含的行为。请记住消息头部包含的行为用于识别EndpointDispatcher所调用服务实例中接受请求的方法。在内部，ActionMessageFilter对象包含一个动作表，该表用于确认一个方法是否匹配，进行匹配时把该方法与表中所保存的行为逐个进行比较。其结果是要么发现一个匹配的行为，要么检查完所有行为但没有匹配的行为。&lt;/div&gt;&#xD;
&lt;div&gt;上述两个过滤器的Match方法都必须对向其发送消息的ChannelDispatcher对象返回True。同样还有可能多于一个EndpointDispatcher对象可以处理消息。在这种情况下，EndpointDispatcher类提供FilterPriority属性。该属性返回一个整数类型。一个EndpointDispatcher对象可以与另外一个EndpointDispatcher对象进行比较然后返回一个较高或者较低的值。如果两个都匹配的端点返回同样的优先级别值，那么WCF运行时抛出MuitipleFilterMatchesException异常。&lt;/div&gt;&#xD;
&lt;div&gt;基于服务配置文件中端点的定义，WCF运行时为每个ChannelDispatcher对象创建EndpointAddressFilterMessage和ActionMessageFilter对象。但是你可以重写这些过滤器--这需要使用自定义的地址和行为表创建对象实例，并在服务运行时之前插入到WCF运行时中。这种实现方式需要你创建自定义的行为，关于如果创建自定义行为请查考第十一章。&lt;/div&gt;&#xD;
&lt;div&gt;默认情况下，EndpointDispatcher调用服务合约中行为对应的方法。但是，你可以修改EndpointDispatcher处理一个请求操作的方式：首先创建一个实现IDispatchOperationSelector接口的类；然后将该类赋值给DispatcherRuntime对象的OperationSelector属性。因为EndpointDispatcher对象的DispatcherRumtime属性引用DispatcherRuntime对象。接口IDispatchOperationSelector包含了方法SelectionOperation。你可以使用SelectionOperation方法来检查消息并返回EndpointDispatcher对象应调用的方法。如果你希望手动控制哪一个分发机制进行工作那么上述方式非常有用。&lt;/div&gt;&#xD;
&lt;div&gt;总而言之，分发机制提供了高度定制化的机制以确定哪一个端点应该处理消息。你可以使用它来构建服务以透明地路由消息至其他的服务。&lt;/div&gt;&#xD;
&lt;div&gt;下图展示了上述的关系：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091704591137.png" /&gt;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;路由消息至其他服务&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;在WCF中，构建一个接受特定的消息服务并发送消息到另外一个服务处理进行处理，是相当简单的。你所需要做的是使用目标服务的镜像服务合约定义一个前端服务。前端服务中定义的方法能执行所需的预处理，比如检查发送请求的用户身份或者检查发送的数据，然后将这些请求推送至合适的目标服务。&lt;/div&gt;&#xD;
&lt;div&gt;但是，创建一个通用的、可接受任何消息的服务；而且该服务还可以路由消息到运行在其他计算机上的其他服务。那么要构建该服务则需要考虑更多。下面三点就是你需要处理的问题：&lt;/div&gt;&#xD;
&lt;ul&gt;&#xD;
&lt;li&gt;服务合约。WCF服务描述了可由服务合约执行的操作。如果一个服务接受消息，那么这些消息必须能够被一个或多个EndpointDispatcher对象的ContractFilter识别；然后将这些消息推送至实现了与目标服务相同的服务合约的服务。当路由消息到单个服务时是非常容易完成地，当你为多个不同的服务创建一个前端服务时，路由消息就会迅速地变得难以掌控，这是因为前端服务必须实现所有服务的服务合约。&lt;/li&gt;&#xD;
&lt;li&gt;消息内容。这个问题与上述服务合约相关。当一个前端服务必须实现大量服务的服务合约时，它还必须实现这些服务所使用的数据合约，这些数据合约用以描述数据结构如何序列化成消息体部分。同样地，这也会迅速地变成一件耗费精力的任务。&lt;/li&gt;&#xD;
&lt;li&gt;消息头的内容。除了消息体之外，一条消息还包含消息头。这些消息头包含的信息包括：加密密钥，事务标识，以及其他一些控制数据流和管理消息一致性的项目。前端服务必须小心地管理这些信息以使这些信息对发送请求的客户端和接受服务处理请求的服务是透明的。&lt;/li&gt;&#xD;
&lt;/ul&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;幸运的是，已经有很多好的解决方案来解决上述的一些问题，你将在后续的练习中研究这些解决方案。在这些练习中，你将看到如何为ShoppingCartService服务创建一个简单的负载均衡路由器。你将运行两个ShoppingCartService服务实例，负载均衡路由器将分发来自客户端的请求到两个服务实例。负债均衡路由将实现一个简单的算法，发送交替的请求到ShoppingCartService服务。尽管练习中的三个服务运行在同一台电脑上，安排它们运行在不同的机器上同样也很简单，并且这样并允许使用不同的处理器扩展负载。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;你将重新熟悉ShoppingCartService服务并修改该服务运行在传统的因特网环境中。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：回顾持续性ShoppingCartService服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 使用Visual Studio，打开\WCF\Step by step\Chapter14\LoadBalancingRouter文件夹下的ShoppingCart.sln文件。&lt;/div&gt;&#xD;
&lt;div&gt;该方案包含修改后的第七章的持续性服务ShoppingCartService项目，ShoppingCartServiceHost项目，和ShoppingCartGUIClient项目。&lt;/div&gt;&#xD;
&lt;div&gt;2. 打开ShoppingCartService项目下的IShoppingCartService.cs文件，并检查该文件。回顾第七章，ShoppingCartService服务实现了AddItemToCart，RemoveItemFromCart，GetShoppingCart和Checkout操作。&lt;/div&gt;&#xD;
&lt;div&gt;3. 打开ShoppingCartService.cs文件并查看该文件的代码。请注意服务使用了PerSession实例模式并标注了DurableService特性。会话状态在两次调用之间保存到WCFPersistence SQL Server数据库。&lt;/div&gt;&#xD;
&lt;div&gt;4. 打开ShoppingCartHost项目的Programm.cs文件。这是服务寄宿程序。它所做的就是使用ServiceHost对象启动服务运行，然后等待用户按下ENTER键关闭宿主程序。&lt;/div&gt;&#xD;
&lt;div&gt;5. 打开ShoppingCartHost项目的app.config文件。请注意服务的宿主创建一个HTTP端点使用http://localhost:90000/ShoppingCartService/ShoppingCartService.svc地址。在这个版本的应用程序中，该端点使用basicHttpContextBinding绑定，该绑定使用默认的配置。在因特网环境中，你很可能实现了传输级别的安全以保护消息在客户端和服务之间的传输。使用WCF，你可以使用基于HTTPS的basicHttpBinding绑定配置传输级别安全。basicHttpContextBinding绑定是basicHttpBinding的意见简单扩展，它把客户端程序希望与之通讯的会话实例的ID作为Web请求头的Cookie在网络中传输。&lt;/div&gt;&#xD;
&lt;div&gt;6. 打开ShoppingCartGUIClient项目的app.config文件。验证客户端程序使用的端点与服务有相同的URI和绑定配置。&lt;/div&gt;&#xD;
&lt;div&gt;7. 在非调适模式下运行解决方案，以使你熟悉客户都程序。&lt;/div&gt;&#xD;
&lt;div&gt;在Shopping Cart GUI客户端窗口，在产品编号处输入WB-H098，然后点击添加商品。经过短暂的延时后，一个水瓶将添加到购物车并显示在客户端窗口中。输入BK-M38S-46，再次点击添加商品按钮，你将在购物车中发现新添加的银色山地自行车。&lt;/div&gt;&#xD;
&lt;div&gt;8. 关闭Shopping Cart GUI客户端窗口，并且关闭服务宿主控制台窗口。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;到目前，你已经拥有一个版本的可供客户端程序直接连接的ShoppingCartService。下一步就是运行服务的多个实例并创建另外一个服务路由消息，根据路由服务实现的负载均衡算法，把客户端路由消息到这些服务实例的其中一个实例。在下面的练习中，你将发送逐个发送请求到两个服务实例。&lt;/div&gt;&#xD;
&lt;div &gt;练习：创建ShoppingCartRouter服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 使用WCF服务类库模板，添加一个新的项目到ShoppingCart解决方案。该项目名为ShoppingCartServiceRouter，存放位置为\WCF\Step by step\Chapter14\LoadBalancingRouter文件夹。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在ShoppingCartServiceRouter项目中，重命名Service1.cs为Router.cs，并重命名IService1.cs为IRouter.cs。并允许Visual Studio更新与Service1有关的引用。&lt;/div&gt;&#xD;
&lt;div&gt;3. 打开IRouter.cs文件，然后添加下面的代码到文件的顶部。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091704596087.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;4. 删除IRouter接口前的注释，并添加如下的特性。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705008529.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 在IRouter接口内，删除GetData和GetDataUsingDataContract操作的定义。然后添加下面的操作：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705006860.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;理解上面的代码而不是查看服务合约是理解路由是如何工作的关键。&lt;/div&gt;&#xD;
&lt;div&gt;在前面的讨论中，你已经了解到设计一个通用的前端服务推送消息到其他服务时，需要关注服务合约和消息内容等问题。服务合约定义了服务可以处理的操作。在常规环境下，一个操作的WSDL描述由Namespace和Name属性组成，这两个属性来自ServiceContract特性的Namespace和Name属性，它们和操作的名字一起生成一个唯一标识符，这个标识为就是我们所说的行为（action），它定义了客户端程序为调用操作应发送请求消息；同样定义了服务回传的响应消息中的回复行为。比如，ShoppingCartService服务中的AddItemToCart操作将生产如下的标识：&lt;/div&gt;&#xD;
&lt;div&gt;http://adventure-works.com/2010/06/04/ShoppingCartService/AddItemToCart&lt;/div&gt;&#xD;
&lt;div&gt;当WCF运行时为服务创建EndpointDispatcher对象时，它添加对应端点可以接收的行为到ContractFilter属性引用的动作表中。&lt;/div&gt;&#xD;
&lt;div&gt;如果你在定义操作时，在操作的特性中显示地为Action属性的指定了一个值，那么WCF运行时将使用你定义的值替代操作的名字。如果你指定Action属性的值为"*",WCF运行时将自动地路由发送至该操作的所有消息，无论客户端发送消息的头部中指定行为的值。在内部，服务的WCF运行时替换ContractFilter属性引用的ActionMessageFilter对象为MatchAllMessageFilter对象。MatchAllMessageFilter对象的Match方法将对传送至该方法的所有非空消息返回true，因此EndpointDispatcher将自动地识别它是否可以接收向其发送的所有的请求。在本练习中，当ShoppingCartClient程序发送AddItemToCart，RemoveItemFromCart，GetShoppingCart，和Checkout消息至ShoppingCartServiceRouter服务，该服务将接受所有这些消息并调用ProcessMessage方法。&lt;/div&gt;&#xD;
&lt;div&gt;你还应当注意ProcessMessage方法的签名。客户端的WCF运行时打包传递至操作的参数到SOAP消息的消息体中。在常规环境下，服务端的WCF时把SOAP消息的消息体转换回可以传递到实现服务操作的方法中去的一组参数。如果方法有返回值，服务端的WCF运行时打包返回值到消息中然后回传该消息到客户端的WCF运行时，WCF客户端的运行时把响应消息的消息体转化成客户端程序期望的类型。&lt;/div&gt;&#xD;
&lt;div&gt;ProcessMessage方法则有所不同，因为该方法接收一个消息对象为其输入参数。在第十一章，你已经了解到Message类提供了传输和接受原始的SOAP消息。当服务端的WCF运行时接收到来自客户端程序的一条消息，它不拆包消息获取参数而是传递整个SOAP消息至ProcessMessage方法。现在由ProcessMessage方法自己转化和翻译消息对象的内容。&lt;/div&gt;&#xD;
&lt;div&gt;相似地，ProcessMessage方法的返回值同样也是一个Message对象。ProcessMessage方法必须创建一个包含客户端期望的数据格式的完整SOAP消息并返回该消息对象。该响应消息的消息头必须包含一个ReplyAction，其对应于客户端WCF运行时期望的ReplyAction。通常，服务端的WCF运行时基于服务和操作的名字添加一个ReplyAction。比如，ShoppingCartService服务返回值客户端程序的AddItemToCart响应消息将生成如下的标识：&lt;/div&gt;&#xD;
&lt;div&gt;http://adventure-works.com/2010/06/04/ShoppingCartService/AddItemToCartResponse&lt;/div&gt;&#xD;
&lt;div&gt;如果你设置了OperationContract特性的ReplyAction属性为"*"，那么服务端的WCF运行时期望你在代码中提供适当的ReplyAction，然后WCF运行时在你创建响应消息时把该ReplyAction添加到消息的头部。在这种情况下，你将从ShoppingCartService服务返回的ReplyAction不做任何更改然后回传到客户端程序。&lt;/div&gt;&#xD;
&lt;div&gt;6. 移除CompositeType类，包括IRouter.cs文件中的DataContract特性。&lt;/div&gt;&#xD;
&lt;div&gt;7. 打开Router.cs文件，添加如下的声明到文件的头部。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705016478.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;8. 删除Router类的注释，GetData方法和GetDataUsingDataContract方法。&lt;/div&gt;&#xD;
&lt;div&gt;9. 添加ServiceBehavior特性到Router类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705016412.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;Router类将提供ProcessMessage的实现。如果你熟悉SOAP协议，你将会注意到你可以在消息头包含接收服务可以识别并处理的信息。在本练习中，ShoppingCartServiceRouter服务自己不会处理消息，它只是简单的推送消息到ShoppingCartServcie服务的一个实例。因此它不需要检查或者理解消息头部，并且在不做任何改动的情况下推送它们至服务实例。设置ServiceBehavior特性类的ValidateMustUnderstand属性为false以关闭该服务对消息头信息进行任何的识别或验证。&lt;/div&gt;&#xD;
&lt;div&gt;10. 添加下面的私有变量到Router类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705026379.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;ShoppingCartServiceRouter服务将扮演一个客户端程序访问两个ShoppingCartServcie服务实例，向每个实例发送消息并等待响应。ProcessMessage方法的各种特性要求使用在第十一章中描述的底层技术而不是通过代理对象连接到ShoppingCartService服务。你将使用IChannelFactory对象创建爱你一个通道工厂，该工厂基于IRequestChannel以打开与ShoppingCartService服务每个实例的通道。&lt;/div&gt;&#xD;
&lt;div&gt;EndpointAddress对象指定了每个ShoppingCartService服务实例的URI。在后续的步骤中你将配置ShoppingCartServiceHost程序使用这两个地址运行两个ShoppingCartService服务实例。&lt;/div&gt;&#xD;
&lt;div&gt;ProcessMessage方法将使用routeBalancer变量确定向ShoppingCartService的哪个实例发送消息。&lt;/div&gt;&#xD;
&lt;div&gt;11. 添加如下代码到Router类的构造函数中&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705028789.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;ProcessMessage方法将使用一个ChannelFactory对象打开与适当的ShoppingCartService服务实例的通道。ChannelFactory对象的创建和销毁非常耗费资源，而我们练习中的场景是单个服务，即所有的请求都会重用相同的ChannelFactory对象；所以在静态方法构造器中创建ChannelFactory对象保证了该对象仅仅创建一次。&lt;/div&gt;&#xD;
&lt;div&gt;此外，请注意ChannelFactory对象在创建时使用BasicHttpContextBinding对象。该绑定匹配http地址，以及两个ShoppingCartService服务实例的需求（两个服务都是持续性服务，并且通过SOAP消息的头部信息传递上下文信息）。&lt;/div&gt;&#xD;
&lt;div&gt;12. 添加ProcessMessage方法到Router类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705039005.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该方法包含多个Console.WriteLine语句，这些语句可以让你在服务端的控制台窗口追踪服务的运行。&lt;/div&gt;&#xD;
&lt;div&gt;Try代码片段中If语句实现了负载均衡算法；如果routeBalencer变量的值为偶数，那么上述方法将创建一个通道并将推送请求至address1（https://localhost:9010/ShoppingCartService/ShoppingCartService.svc）；否则，上述方法将为address2创建一个通道。上述方法然后增加变量routeBalancer的值（+1）。使用这种方式，ProcessMessage方法交替地发送所有的请求至ShoppingCartService服务的两个实例。&lt;/div&gt;&#xD;
&lt;div&gt;上述方法中有一个细微之处值得注意，那就是绑定所实现的上下文协议。请记住当你使用WSHttpContextBinding，BasicHttpContextBinding，或NetTcpContextBinding绑定时，消息可以包含上下文信息。持续性服务使用这些上下文信息关联客户端的请求并将它们直接传送至合适的会话中。在默认情况下，接受消息的通道在内部缓存这些上下文信息，当调用其他服务时同样遵循相同的上下文信息。然而接收到的消息同样包含相同的上下文消息，因此，当消息被推送至服务实例时，相同的上下文消息将被发送两次，当路由发送消息时将在服务内部产生错误。解决方法是在推送消息之前移除入栈消息的上下文信息，这可以通过message.Properties.Remove("ContextMessageProperty")语句来完成。&lt;/div&gt;&#xD;
&lt;div&gt;IRequestChannel类的Request方法通过通道发送Message对象至目的服务。返回值是另外一个Message对象，其包含服务的响应。然后ProcessMessage方法传递该消息至客户都程序（不对该消息做任何更改）。&lt;/div&gt;&#xD;
&lt;div&gt;请记住客户端发送消息对象可能包含安全或者其他信息。与上下文数据不一样，ProcessMessage方法并不检查或更改这些信息，因为目的服务也不关心消息是否由ShoppingCartServiceRouter服务推送。相似地，ProcessMessage方法也不会更改响应消息，路由直接传输响应消息至客户端程序。但是，这并不能阻止你在传递消息之前添加代码以修改请求消息或者响应消息的内容。这就带来了一些安全方面的考虑，因此你应该确保在部署ShoppingCartServiceRouter服务时应当将其放置在一个安全的环境中。&lt;/div&gt;&#xD;
&lt;div&gt;在生产环境中，一般地你使用IIS寄宿路由服务，因为IIS便于外界访问。为了简化，在下面的练习中，我们将使用寄宿ShoppingCartService服务的宿主程序寄宿ShoppingCartServiceRouterService服务 。你将修改宿主的配置文件以使其为ShoppingCartService服务提供两个端点，而这两个端点正式ShoppingCartServiceRouterService服务所期望的端点。&lt;/div&gt;&#xD;
&lt;div &gt;练习：配置ShoppingCartHost程序以寄宿ShoppingCartRouterServcie服务。&lt;/div&gt;&#xD;
&lt;div&gt;1. 使用WCF服务配置管理工具编辑ShoppingCartHost项目下的app.config文件&lt;/div&gt;&#xD;
&lt;div&gt;2. 在配置面板，在服务文件夹上点击右键，然后选择创建新的服务。在右边面板中，在命名文本框中，输入ShoppingCartServiceRouter.Router&lt;/div&gt;&#xD;
&lt;div&gt;3. 在配置面板，在ShoppingCartServiceRouter.Router服务的端点文件夹点击右键，然后选择创建新的服务端点。在服务端点面板，按照下表的值指定端点相应的属性&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 75px;" /&gt; &lt;col style="width: 515px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="background: #a6a6a6;"&gt;&#xD;
&lt;td&gt;&lt;span style="color: white;"&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;td&gt;&lt;span style="color: white;"&gt;&lt;strong&gt;属性值&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;地址&lt;/td&gt;&#xD;
&lt;td&gt;http://localhost:9000/ShoppingCartService/ShoppingCartService.svc&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;绑定&lt;/td&gt;&#xD;
&lt;td&gt;basicHttpContextBinding&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;合约&lt;/td&gt;&#xD;
&lt;td&gt;ShoppingCartServiceRouter.Router&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;请注意路由服务的地址与ShoppingCartService服务的地址，这样现有的客户端将直接连接到路由服务而不需要更新配置。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在配置面板，选中服务文件夹，展开ShoppingCartService.ShoppingCartServiceImpl服务，展开端点文件夹，然后点击未命名端点。在服务端点面板，设置该端点的名字为ShoppingCartServiceHttpEndpoint1，然后更改地址为&lt;a href="http://localhost:9010/ShoppingCartService/ShoppingCartService.svc"&gt;http://localhost:9010/ShoppingCartService/ShoppingCartService.svc&lt;/a&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 在配置面板，在ShoppingCartServcie.ShoppingCartServiceImpl服务的端点上点击右键，然后选择创建新的服务端点以添加第二个服务端点。按照下表的值指定端点相应的属性&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;table style="border-collapse: collapse;" border="0"&gt;&lt;colgroup&gt; &lt;col style="width: 75px;" /&gt; &lt;col style="width: 515px;" /&gt;&lt;/colgroup&gt;&#xD;
&lt;tbody valign="top"&gt;&#xD;
&lt;tr style="background: #a6a6a6;"&gt;&#xD;
&lt;td&gt;&lt;span style="color: white;"&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;td&gt;&lt;span style="color: white;"&gt;&lt;strong&gt;属性值&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;名字&lt;/td&gt;&#xD;
&lt;td&gt;ShoppingCartServiceHttpEndpoint2&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;地址&lt;/td&gt;&#xD;
&lt;td&gt;http://localhost:9020/ShoppingCartService/ShoppingCartService.svc&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;绑定&lt;/td&gt;&#xD;
&lt;td&gt;basicHttpContextBinding&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;合约&lt;/td&gt;&#xD;
&lt;td&gt;ShoppingCartServcie.IShoppingCartService&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;6. 保存配置文件，然后退出WCF服务配置管理工具。&lt;/div&gt;&#xD;
&lt;div&gt;7. 在解决方案窗口，为项目ShoppingCartHost项目添加ShoppingCartServiceRouter引用。&lt;/div&gt;&#xD;
&lt;div&gt;8. 打开ShoppingCartHost项目的Programm.cs文件，然后添加如下代码（红色方框内）&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705049463.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上面语句为ShoppingCartServiceRouter服务创建并打开一个新的ServiceHost对象。&lt;/div&gt;&#xD;
&lt;div&gt;9. ShoppingCartHost程序使用9010和9020端口为ShoppingCartService服务的端点。你不许保留这两个端口以使ShoppingCartHost程序可以访问这两个端口。使用管理员身份运行Visual 命令提示符窗口，然后输入下面的命令：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705054969.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;10. 关闭Visual Studio命令提示符窗口，然后返回到Visual Studio&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：测试ShoppingCartRouter服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 在非调适模式下运行解决方案。在ShoppingCartGUI客户端窗口的产品编码处输入PU-M044，然后点击添加按钮。这将添加一个水瓶至购物车。然后再添加另外一个产品WB-H098；然后点击结算按钮。&lt;/div&gt;&#xD;
&lt;div&gt;客户端程序应该与之前的练习一样地工作。但是，如果你检查服务控制台窗口，你将看到路由服务按照顺序推送请求消息至ShoppingCartService服务的两个实例；服务端点的地址在9020和9010之间替换。下图展示了两天请求消息经由路由服务后分别推送至两个不同的服务实例。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201110/201110091705063442.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;2. 关闭Shopping CartGUI窗口，然后在服务控制台窗口中按ENTER键盘停止服务。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;参考&lt;/div&gt;&#xD;
&lt;div&gt;MSDN 路由服务&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;a href="http://msdn.microsoft.com/zh-cn/library/ee517421.aspx"&gt;http://msdn.microsoft.com/zh-cn/library/ee517421.aspx&lt;/a&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2203926.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/10/09/2203926.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/09/15/2177726.html</id><title type="text">WCF 4.0 进阶系列 – 第十四章 检测服务和路由消息（第二部分）</title><summary type="text">使用服务声明可以减少由Probe消息和ProbeMatch消息带来的网络流量，但是如果服务启动时客户端程序没有运行那么会发生什么？答案是客户端将失去通知消息因此不能发现服务，因为客户端不能连接到服务。解决方法是使用一个发现消息和声明的结合体；创建一个中间服务，该服务使用一个已知的地址侦听来自服务的声明消息并缓存这些声明消息。在客户端程序中，发送Probe消息至中间服务。中间服务可以接受这些Probe请求，在缓存中查找服务的地址，如果匹配，想客户端返回一条ProbeResponse消息，该消息包含匹配服务的详细信息。由于中间服务在一个固定的、已知的地址上，客户端不需要向网络广播大量的Probe消息；相反，它们直接向中间服务发送单播Probe消息。在本小节，我们将学习如何创建这样一个中间服务，并测试其工作过程。</summary><published>2011-09-15T08:40:00Z</published><updated>2011-09-15T08:40:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/09/15/2177726.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/09/15/2177726.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;p&gt;&lt;strong&gt;使用发现代理&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;使用服务声明可以减少由Probe消息和ProbeMatch消息带来的网络流量，但是如果服务启动时客户端程序没有运行那么会发生什么？答案是客户端将失去通知消息因此不能发现服务，因为客户端不能连接到服务。解决方法是使用一个发现消息和声明的结合体；创建一个中间服务，该服务使用一个已知的地址侦听来自服务的声明消息并缓存这些声明消息。在客户端程序中，发送Probe消息至中间服务。中间服务可以接受这些Probe请求，在缓存中查找服务的地址，如果匹配，想客户端返回一条ProbeResponse消息，该消息包含匹配服务的详细信息。由于中间服务在一个固定的、已知的地址上，客户端不需要向网络广播大量的Probe消息；相反，它们直接向中间服务发送单播Probe消息。&lt;/div&gt;&#xD;
&lt;div&gt;在WCF中，中间服务也称之为发现代理。更进一步讲，System.ServiceModel.Discovery命名空间包含了一个抽象类DiscoveryProxy，你可以使用该类来实现一个代理服务。&lt;/div&gt;&#xD;
&lt;div&gt;为了构建代理服务，你首先继承DiscoveryProxy类，然后重载下列抽象方法。这些抽象方法实现异步操作，并且你必须确保它们是线程安全的。&lt;/div&gt;&#xD;
&lt;ul&gt;&#xD;
&lt;li&gt;&#xD;
&lt;div&gt;OnBeginOnlineAnnouncement和OnEndOnlineAnnouncement，DiscoveryProxy包含了内建的声明服务，该服务用以侦听声明消息。在上一个练习中当使用AnnouncementService类时，你为事件OnlineAnnouncementReceived和OfflineAnnouncementReceived添加了事件处理器。DiscoveryProxy类没有采用相同的方式对外公开这些事件，但你需要为这些事件提供逻辑已使它们发生时可以调用。&lt;/div&gt;&#xD;
&lt;div&gt;当服务表明自己开始运行时OnBeginOnlineAnnounce方法开始运行。该方法的目的是存储服务的元数据，无论哪种存储方式（存储在内存中、写入到文件中或者保存在数据库中）都是适合发现服务的。这可能需要耗费一定的时间，因为该方法使用异步编程设计模式实现。该方法的参数包括：EndpointDiscoveryMetadata描述服务的元数据；一个用于当OnEndOnlineAnnouncement方法结束时回调DiscoveryProxy对象AsyncCallback对象。该方法返回一个IAsyncResult对象。DiscoveryProxy对象调用OnEndOnlineAnnouncement方法以结束操作。在IAsyncResult对象表明服务声明已经被处理而且服务的详细信息已经保存前该方法将一直被阻塞。&lt;/div&gt;&#xD;
&lt;/li&gt;&#xD;
&lt;li&gt;OnBeginOfflineAnnouncement和OnEndOfflineAnnouncement，这两个方法提供处理逻辑处理当服务关闭时发送的离线声明消息。这两个方法的目的是从存储中移除服务的详细消息。这两个方法同样使用异步编程设计模式实现。&lt;/li&gt;&#xD;
&lt;li&gt;OnBeginFind和OnEndFind，当客户端发送Probe消息时这两个方法运行。客户端寻找的服务的相信信息通过FindRequestContext对象传入。OnBeginFind方法指明搜索一个服务，在搜索完成之前OnEndFind方法将一直被阻塞。&lt;/li&gt;&#xD;
&lt;li&gt;OnBeginResolve和OnEndResolve，WS-Discovery协议同样支持作为Probe消息而引申的Resolve消息；一条Probe消息指定客户端搜索的服务合约，与之相关联的ProbeMatch消息包含服务的地址。一条Resolve请求指定服务的地址，与之对应的ResolveMatch消息包含描述服务合约的元数据。当客户端程序发送一条Resolve消息时OnBeginResolve和OnEndResolve方法开始运行。OnBeginResolve方法异步地执行搜索操作，在搜索完成前OnEndResolve方法将一直被阻塞。&lt;/li&gt;&#xD;
&lt;/ul&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;在下面的一组练习中，你将通过扩展DiscoveryProxy类实现发现代理，然后你修改客户端程序发送Probe请求至发现代理而不是试图捕获服务声明。&lt;/div&gt;&#xD;
&lt;div &gt;练习：实现发现代理&lt;/div&gt;&#xD;
&lt;div&gt;1. 在解决方案窗口，添加一个新的控制台应用程序项目ProductsServiceProxy到解决方案ProductsService，然后保存该项目到*\WCF\Step.by.Step\Solutions\Chapter14\ProductsService文件夹下。&lt;/div&gt;&#xD;
&lt;div&gt;2. 添加System.ServiceModel和System.ServiceModel.Discovery组件至ProductsServiceProxy项目。&lt;/div&gt;&#xD;
&lt;div&gt;3. 添加本书源代码中附带的AsyncResult.cs文件；该文件与第十二章的AsyncResult.cs是一样的。&lt;/div&gt;&#xD;
&lt;div&gt;4. 添加一个新的类文件ProductsServiceProxy，其保存在ProductsServiceProxy项目中。&lt;/div&gt;&#xD;
&lt;div&gt;5. 在ProductsServiceProxy.cs文件中，添加下面的using语句之文件头部。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625071884.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;6. 为ProductsServiceProxy类添加下面的特性&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625106708.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;简单地将，ProductsServiceProxy将在内存中使用ConcurrentDictionary对象缓存服务信息。因此，ProductsServiceProxy服务将使用线程安全的单个服务实例来实现。&lt;/div&gt;&#xD;
&lt;div&gt;7. 修改ProductsServiceProxy类，使其继承DiscoveryProxy类。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625113775.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;8. 在ProductsServiceProxy类中，添加ConcurrentDictionary集合，该集合用于保存服务的信息；在ProductsServiceProxy类的构造函数中对这个集合进行初始化。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625122794.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;9. 为ProductsServiceProxy类添加添加私有的AddService和RemoveService方法。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625181993.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;10. 添加下面的私有方法FindService到ProductsServiceProxy类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625206095.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;11. 添加私有方法ResolveService到ProductsServiceProxy类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625215944.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;12. 添加私有方法WaitForAsyncResult方法只ProductsServiceProcy类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625234539.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;13. 重载DIscoveryProxy类的OnBeginOnlineAnnouncement和OnEndOnlineAnnouncement方法&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625274379.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;OnBeginOnlineAnnouncement方法创建Task对象以异步地调用AddServcie方法，该方法添加在参数中指定的服务元数据到services字典中。该方法返回IAsyncResult接口，其包含等待操作结束所需的状态信息。&lt;/div&gt;&#xD;
&lt;div&gt;OnEndOnlineAnnouncement方法接收IAsyncResult对象的参数，然后调用WaitForAsyncResult方法，该方法将阻塞当前线程直到IAsyncResult对象指明操作已经完成。&lt;/div&gt;&#xD;
&lt;div&gt;14. 重载OnBeginOfflineAnnouncement方法和OnEndOfflineAnnouncement方法&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625351890.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;15. 重载OnBeginFind方法和OnEndFind方法&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625434602.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;16. 重载OnBeginResolve和OnEndResolve方法&lt;/div&gt;&#xD;
&lt;div&gt;17. 重新生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;你现在已经定义了一个DiscoveryProxy服务，该服务在内存中使用字典中缓存声明请求。下一步就是为该服务提供一个宿主。为了节省时间，并且与使用IIS寄宿有所不同，我们直接使用原书代码中附带的host.cs，你将在下面的联系中检查该宿主程序是否正常工作。&lt;/div&gt;&#xD;
&lt;div &gt;练习：检查ProductsServiceProxy服务是否被寄宿&lt;/div&gt;&#xD;
&lt;div&gt;1. 从ProductsServiceProxy项目中删除Programm.cs文件，然后添加Host.cs文件。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在文本编辑模式下打开Host.cs文件。&lt;/div&gt;&#xD;
&lt;div&gt;Program类包含两个字符串常量probeAddress和announcementAddress。它们均使用tcp地址&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625433032.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;客户端程序将发送Probe消息至一个在probeAddress地址上侦听的端点，服务将发送声明消息至一个在announcemAddress地址上侦听的端点。&lt;/div&gt;&#xD;
&lt;div&gt;Main方法创建一个用于寄宿ProductsServiceProxy服务的ServiceHost对象&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625445573.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;Main方法随后创建一个可发现的端点&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625443654.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;然后，Main方法使用类似的方式创建一个声明端点&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625445323.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;接着，Main方法添加上述的两个端点至ServiceHost对象，并开始寄宿她们。此时，可发现带来可以接收来自服务的声明消息和处理来自客户端程序的Probe和Resolve请求。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151625549727.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;3. 重新生病解决方案&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;在开始运行ProductsService服务之前，你需要配置其向发现代理的TCP端点发送声明消息，而不是在网络中广播声明消息。&lt;/div&gt;&#xD;
&lt;div &gt;练习：配置ProductsService服务发送声明消息至发现代理&lt;/div&gt;&#xD;
&lt;div&gt;1. 使用WCF服务配置管理工具打开C:\...\ProductsService\项目下的web.config文件。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在配置面板，展开高级文件夹，展开服务行为文件夹，展开未命名行为，展开serviceDiscovery行为元素，展开Announcement端点文件夹，然后添加未命名声明端点。&lt;/div&gt;&#xD;
&lt;div&gt;服务当前被配置为通过UDP广播声明消息。&lt;/div&gt;&#xD;
&lt;div&gt;3. 在客户端端点面板，更改Kind属性为announcementEndpoint。&lt;/div&gt;&#xD;
&lt;div&gt;announcementEndpoint端点是一个标准端点，其用于发送多播声明消息。你可以更改Address属性的值以指定声明消息的发送的目标地址。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在地址属性处，输入net.tcp://localhost:8002/Announcement。前面我们提到过，该地址用于侦听来自服务的声明消息。设置绑定属性的值为netTcpBinding，这是因为发现代理使用TCP地址。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151630425852.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 保存配置文件，并关闭WCF服务配置管理工具。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;同样，你还需要修改客户都应用程序。上一个联系中，该客户端包括了侦听声明消息的功能，但是现在发现代理已经有了该功能。因为现在客户端只需要直接向发现代理发送probe消息，并且不再使用广播的方式。&lt;/div&gt;&#xD;
&lt;div &gt;练习：修改客户端程序发送Probe请求至发现代理&lt;/div&gt;&#xD;
&lt;div&gt;1. 打开ProductsClient项目下的Programm.cs文件&lt;/div&gt;&#xD;
&lt;div&gt;2. 在Program类中，删除ConcurrentDictionary集合的定义services，然后声明probe消息的发送地址。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151630439539.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;3. 在Main方法中，移除announcementService变量并删除实现OnlineAnnouncementReceived和OfflineAnnouncementReceived事件。同样移除announcementHost变量以及添加UDP声明端点和侦听的代码。 现在Main方法看起来如同下图所示.&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151630439049.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;6. 更改Console.WriteLine声明，更改其消息为"Press ENTER when the Discovery Proxy has started"&lt;/div&gt;&#xD;
&lt;div&gt;5. 在Console.ReadLine语句后，添加下面的代码&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151630449671.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码用于创建一个用于连接至发现代理TCP端点；然后实例化DiscoveryClient对象以连接至刚创建的端点。&lt;/div&gt;&#xD;
&lt;div&gt;6. 接下来，我们需要基于ProductsService实现的服务合约创建FindCriteria对象。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151630457545.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;7. 剩余的代码不需要更改。重新生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;现在你可以测试发现代理，但是首先你需要部署更新的后的ProductsService服务并配置项目启动发现服务项目和客户端程序。&lt;/div&gt;&#xD;
&lt;div &gt;练习：测试发现代理&lt;/div&gt;&#xD;
&lt;div&gt;1. 在解决方案窗口，在C:\...\ProductsService\项目上点击右键，然后点击发布站点。发布当前的web站点到http://localhost/DiscoverableService并且允许Visual Studio覆盖现有的文件。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在ProductsService解决方案中，设置ProductsServiceProxy和ProductsClient项目为启动项目。&lt;/div&gt;&#xD;
&lt;div&gt;3. 在非调适模式下运行解决方案，但是不要在客户端控制台窗口中按ENTER键。&lt;/div&gt;&#xD;
&lt;div&gt;4. 返回到IIS管理器中，通过浏览ProductsService.svc文件启动DiscoverableProductsService Web应用程序。然后关闭IE浏览器，但保持IIS管理器处于打开状态。&lt;/div&gt;&#xD;
&lt;div&gt;5. 切换到发现代理控制台窗口。你将看到追踪消息OnBeginOnlineAnnouncement和OnEndOnlineAnnouncement方法由ProductsService服务启动而触发，如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151630461265.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;6. 切换到客户端程序控制台窗口，然后按下ENTER键。客户端程序将向发现大力发送Probe消息，然后返回ProductsService服务的地址。客户端程序随后使用该地址与ProductsService服务建立连接并获取和更新产品信息。&lt;/div&gt;&#xD;
&lt;div&gt;7. 返回发现大力控制台窗口，现在你可以看到由客户端发送Probe消息而产出的OnBeginFind和OnEndFind方法的追踪信息：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151630476970.png" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;8. 返回到IIS管理器中。在控制面板，在DiscoverableProductsService服务所使用的应用程序池上点击Recycle。该行为将关闭使用该应用程序池的所有Web应用程序。&lt;/div&gt;&#xD;
&lt;div&gt;9. 切换回发现代理控制台窗口，我们可以看到窗口将会显示OnBeignOfflineAnnouncement和OnEndOfflineAnnouncement方法；ProductsServcie服务声明自己将因为应用程序池关闭而离线。&lt;/div&gt;&#xD;
&lt;div&gt;10. 在发现代理控制台窗口按下ENTER键，然后返回到Visual Studio中。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img alt="" src="http://images.cnblogs.com/cnblogs_com/yang_sy/201109/201109151630485956.png" /&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2177726.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/09/15/2177726.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/08/30/2160202.html</id><title type="text">WCF 4.0 进阶系列 – 第十四章 检测服务和路由消息（第一部分）</title><summary type="text">如果客户端程序向WCF服务发送一条请求消息，那么客户端通过端点发送该请求。如果你回想一下，端点由三部分组成：地址，绑定和合约。地址指明消息发送的目的地；绑定指明传输、格式、以及于与服务进行通讯所使用的协议；合约决定客户端可以发现的消息以及客户端期望的响应消息。多个服务可以实现同一个服务合约，或一个服务也有可能更改地址。如果客户端将服务特定的地址硬编码在客户端的配置文件中，那么如果服务发生变动、或者临时不可访问、或者服务由于太忙而不能处理请求，那么客户端将不再能继续与该服务通讯。WCF提供了检测服务和路由消息来解决上述问题。在本章的第一部分，你将看到如何在一个通过Ad Hoc模式和服务声明方式在WCF解决方案中实现检测服务。</summary><published>2011-08-30T15:48:00Z</published><updated>2011-08-30T15:48:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/08/30/2160202.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/08/30/2160202.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;p&gt;&lt;strong&gt;前言&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;如果客户端程序向WCF服务发送一条请求消息，那么客户端通过端点发送该请求。如果你回想一下，端点由三部分组成：地址，绑定和合约。地址指明消息发送的目的地；绑定指明传输、格式、以及于与服务进行通讯所使用的协议；合约决定客户端可以发现的消息以及客户端期望的响应消息。多个服务可以实现同一个服务合约，或一个服务也有可能更改地址。如果客户端将服务特定的地址硬编码在客户端的配置文件中，那么如果服务发生变动、或者临时不可访问、或者服务由于太忙而不能处理请求，那么客户端将不再能继续与该服务通讯。WCF提供了检测服务和路由消息来解决上述问题。&lt;/div&gt;&#xD;
&lt;div&gt;在本章的第一部分，你将看到如何在一个通过Ad Hoc模式和服务声明方式在WCF解决方案中实现检测服务。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;实现检测服务&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;WCF检测服务实现了OASIS WS-Discovery 协议。该特性允许客户端程序基于查询条件（比如服务实现的合约）动态地查找一个服务。服务的位置可能会改变，但只要服务是可检测的，那么客户端程序就可以发现服务并连接至该服务。&lt;/div&gt;&#xD;
&lt;div&gt;在WS-Discovery协议中最简单的模式是：如果一个客户端程序希望连接至一个服务，那么该客户端在网络上发送包含特定条件（如服务协定类型、关键字和范围）的 Probe 请求。如果服务支持可检测性，那么该服务的端点使用由WS-Discovery规范定义的既定地址，并在该地址侦听Probe请求。服务接收到客户端发送的Probe 请求，然后确定它们是否匹配该条件。如果某一服务匹配该条件，那么该服务做出响应，向客户端响应一条包含与该服务联系所需信息的 ProbeMatch 消息。这种模式就是ad hoc模式（MSDN翻译为临时模式）&lt;/div&gt;&#xD;
&lt;div&gt;使用WCF，在服务端你只需要激活检测行为并添加一个预先配置的可检测端点至该服务，那么该服务就可以支持检测性。预先配置的端点是udpDiscoveryEndpoint，该端点是WCF实现的多个标准端点之一。标准端点实现一组预先定义好的功能并包含一些内建的配置信息；你需要做的仅仅是把服务所使用端点的名字指向标准端点。udpDiscoveryEndpoint端点使用固定的合约，固定的HTTP绑定，以及一个由WS-Discovery规范指定的默认地址。&lt;/div&gt;&#xD;
&lt;div&gt;让我们来看一下使用Ad hoc模式实现发现服务所需的一些类以及它们之间的关系图：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302347564542.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;我们来分析一下的UdpDiscoveryEndpoint构造函数的执行过程：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302347594839.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;1. new UdpDiscoveryEndpoint()，访问UdpDiscoveryEndpoint默认的不带参数的构造函数。&lt;/div&gt;&#xD;
&lt;div&gt;2. this.UdpDiscoveryEndpoint(UdpDiscoveryEndpoint.DefaultIPv4MulticastAddress) ，访问UdpDiscoveryEndpoint的参数为Url的构造函数，参数的值为new&amp;nbsp;Uri("soap.udp://239.255.255.250:3702")。&lt;/div&gt;&#xD;
&lt;div&gt;3. this.UpdDiscovery(DiscoveryVersion.DefaultDiscoveryVersion, multicastAddress)，访问UdpDiscoveryEndpoint的参数为DiscoveryVersion和Uri的构造函数，参数的值分别为WSDiscovery11和new&amp;nbsp;Uri("soap.udp://239.255.255.250:3702")。&lt;/div&gt;&#xD;
&lt;div&gt;DiscoveryVersion.DefaultDiscoveryVerion访问DiscoveryVersion类的静态属性DefaultDiscoveryVersion，该属性通过方法FromName("WSDiscovery11")返回一个DiscoveryVersion对象。在FromName方法中，调用了DiscoveryVersion的静态属性WSDiscovery11；该属性通过DiscoveryVersion的构造函数new&amp;nbsp;DiscoveryVersion("WSDiscovery11",&amp;nbsp;"http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01",&amp;nbsp;new&amp;nbsp;DiscoveryVersion11Implementation())返回DiscoveryVersion对象。请注意最后一个参数new DiscoveryVersion11Implementation()。该对象包含一个方法GetDiscoveryContract，它将根据ServiceDiscoveryMode返回一个对于的ContractDescription对象。&lt;/div&gt;&#xD;
&lt;div&gt;4. base(discoveryVersion, ServiceDiscoveryModel.Adhoc)，访问UdpDiscoveryEndpoint的基类DiscoveryEndpoint的构造函数。&lt;/div&gt;&#xD;
&lt;div&gt;5. 该函数为访问自身的另外一个构造函数DiscoveryEndpoint(DiscoveryVersion discoveryVersion, ServiceDiscoveryMode discoveryMode, Binding binding, EndpointAddress endpointAddress)；前面的两个参数由子类传入，另外两个参数的值为null。该函数将访其父类Endpoint的构造函数。但是先需要通过自身的GetDiscoveryContract方法获取ContractDescription实例：&lt;/div&gt;&#xD;
&lt;div&gt;5.1 GetDiscoveryContract方法的代码如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302347594905.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;请注意discovery的类包含一个接口IDiscoveryVersionImplementtation；其属性DefaultDiscoveryVersion为该接口提供了一个具体的实现，该实现就是DiscoveryVersion11Implementation类。上诉方法通过访问该实现类的GetDiscoveryContract获取服务合约。&lt;/div&gt;&#xD;
&lt;div&gt;6. 现在，访问Endpoint类的的构造函数ServiceEndpoint(ContractDescription contract)，该构造函数的参数由DiscoveryEndpoint的静态方法GetDiscoveryContract方法传入&lt;/div&gt;&#xD;
&lt;div&gt;7. 执行完ServiceEndpoint类的的ServiceEndpoint(ContractDescription contract)后；回到DiscoveryEndpoint类的DiscoveryEndpoint(DiscoveryVersion discoveryVersion, ServiceDiscoveryMode discoveryMode, Binding binding, EndpointAddress endpointAddress)方法。该方法为服务添加可检测特性行为；最后设置端点的地址和绑定。&lt;/div&gt;&#xD;
&lt;div&gt;8. 然后，回到UdpDiscoveryEndpoint类的UpdDiscovery(DiscoveryVersion.DefaultDiscoveryVersion, multicastAddress)方法。该方法调用自身的Initializer方法。&lt;/div&gt;&#xD;
&lt;div&gt;9. 最后，我们来看UdpDiscoveryEndpoint的Initializer方法。该方法指定编码模式，消息版本，并添加自定义的绑定，最后为绑定添加一些行为。其方法的代码如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348002147.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;配置WCF服务支持Ad Hoc模式的可检测服务&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;在下面的练习中，你将配置ProductsService服务支持Ad Hoc模式的可检测服务；并为客户端程序添加代码以发现ProductsService服务然后与其建立连接。&lt;/div&gt;&#xD;
&lt;div &gt;练习：配置ProductsService服务支持Ad Hoc模式的可检测服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 使用管理员身份启动Visual Studio，然后打开*\WCF\Step.by.Step\Solutions\Chapter14\ProductsService文件夹下的ProductsService.sln解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;该解决方案包含ProductsService服务，它寄宿在ASP.NET开发Web服务器上，该服务在8090端口侦听请求。该方案还包含一个客户端程序，其使用端点WS2007HttpBinding_IProductsService连接至服务，并调用该服务的各种操作。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在非调适模式下运行解决方案。ASP.NET开发Web服务器将启动，并在8090端口侦听请求。在客户端控制台窗口中，确认显示了http://localhost:8090/ProductService/ProductsService.svc。然后按ENTER键。客户端程序将执行整套任务；显示AdventureWorks数据库中所有产品的产品编码，显示产品WB-H098的产品信息和库存信息，最后并为当前库存增加100.&lt;/div&gt;&#xD;
&lt;div&gt;当客户端程序完成后在客户端控制台窗口中按ENTER键以关闭客户端，然后回到Visual Studio。&lt;/div&gt;&#xD;
&lt;div&gt;目前，客户端程序没有使用检测服务的方式查找ProductsService服务，而且服务本身也不支持可检测性。在下面的步骤中，你将首先配置服务为可检测服务，然后将注意力转移到客户端程序。&lt;/div&gt;&#xD;
&lt;div&gt;3. 使用WCF服务配置管理器打开C\...\ProductsService项目下的web.config文件。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在配置面板中，展开高级文件夹，然后展开服务行为文件夹，最后点击未命名行为。&lt;/div&gt;&#xD;
&lt;div&gt;5. 在行为面板，点击添加按钮，在添加服务元素扩展对话框中，选择serviceDiscovery行为元素，然后点击添加按钮。&lt;/div&gt;&#xD;
&lt;div&gt;serviceDiscovery行为元素使可检测服务对外暴露所有端点。但是，你仍然需要添加一个可检测的端点以侦听来自客户端的Probe请求。通过添加udpDiscoveryEndpoint标准端点可以完成对Probe请求的侦听任务。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在配置面板，展开服务文件夹，然后展开ProductsService.ProductsServiceImpl服务，在端点文件夹上点击右键，然后选择创建新的服务端点。&lt;/div&gt;&#xD;
&lt;div&gt;7. 在服务端点面板，选择端点Kind属性的值为udpDiscoveryEndpoint。其他的属性均使用其默认值。&lt;/div&gt;&#xD;
&lt;div&gt;8. 在配置面板，点击标准端点文件夹。在标准端点面板，点击创建新的标准端点配置。在创建标准端点对话框中，点击udpDiscoveryEndpoint端点类型，然后点击确认按钮。&lt;/div&gt;&#xD;
&lt;div&gt;9. 在右边面板，更改新创建的标准端点名字为AdHocDiscoveryEndpoint。在常见区域中，设置DiscoveryMode属性为Adhoc，DiscoveryVersion属性为WSDiscovery11。&lt;/div&gt;&#xD;
&lt;div&gt;实际上，有两个版本的常规WS-Discovery规范；较早的版本诞生于2005年4月，最新的版本为1.1。udpDiscoveryEndpoint标准端点支持两个规范。但是默认情况下使用更新的1.1版本。如果你希望启动对老版本的支持，更改DiscoveryVersion属性为WSDiscoveryApril2005。&lt;/div&gt;&#xD;
&lt;div&gt;MaxResponseDelay属性的目的是阻止服务试图在同一时刻发送大量ProbeMatch消息时引起网络"风暴"。如果该属性设置一个不为零的值，那么服务在发送每条ProbeMatch响应之前都会等待一段时间。&lt;/div&gt;&#xD;
&lt;div&gt;请注意，你可以修改端点侦听的多播地址（默认情况值来自WS-Discovery规范设定的值）。但是，你不应该修改它除非在你的客户端程序中Probe请求的地址也做了同样的修改。&lt;/div&gt;&#xD;
&lt;div&gt;10. 在配置面板，点击服务文件夹下的端点文件夹，然后选择最后一个未命名端点，这将选中updDiscoveryEndpoint端点。设置该端点的EndpointConfiguration属性为AdHocDiscoveryEndpoint。&lt;/div&gt;&#xD;
&lt;div&gt;尽管你没有更改该端点的默认属性，上述配置将允许你方便地更改端点的值，特别是当你需要更改端点的值的时候。&lt;/div&gt;&#xD;
&lt;div&gt;11. 保存配置文件，然后退出WCF服务配置编辑器。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;在进行配置客户端联系前，我们先开一下配置客户端见车Ad hoc模式的服务所需要使用的类&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348013642.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;而最重要的对象是DiscoveryClient，其初始化的过程大致如下：该过程创建一个innerClient，其类型为IDiscoveryClient接口，该接口提供了ProbeOperation(FindCriteria criteria)方法以处理客户端的Probe请求；该方法的具体实现是在System.ServiceModel.Discovery.Version11.DiscoveryInnerCLientAdhoc11中实现。而IDiscoveryInnerClientResponse提供了ProbeMatchOperation(*)方法处理匹配的Probe；该方法在DiscoveryClient提供了具体的实现。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/20110830234803252.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;在下面的练习中，你将修改客户端程序以启用检测服务。客户端程序将创建一个DiscoveryClient对象以发送一条Probe消息以识别实现ProductService服务的合约。ProductService服务的udpDiscoveryEndpoint端点将接收到该请求，然后检测服务是否实现了请求的合约，并返回一条包含服务端点信息的ProbeMatch消息。客户端程序从ProbeMatch消息中获取服务的地址，然后使用该地址连接至ProductsService服务。&lt;/div&gt;&#xD;
&lt;div &gt;练习：修改客户端程序以实现检测ProductService服务&lt;/div&gt;&#xD;
&lt;div&gt;1. 在解决方案浏览器中，引用System.ServiceModel.Discovery组件至PorductsClient项目。该组件包含了客户端程序执行检测服务所需的类型。&lt;/div&gt;&#xD;
&lt;div&gt;2. 开发ProductsClient项目下的Programm.cs文件，在文件头部添加声明using System.ServiceModel.Discovery；&lt;/div&gt;&#xD;
&lt;div&gt;3. 完成Main方法如下：&lt;br /&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348038366.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;第一行代码创建一个DiscoveryClinet实例，该实例打开一个本地的UdpDiscoveryEndpoint实例端点以发送和接受检测消息。DiscoveryClient类位于System.ServiceModel.Discovery命名空间下。它提供发布Probe请求并等待ProbeMatch响应。&lt;/div&gt;&#xD;
&lt;div&gt;第二回声明创建一个System.ServiceModel.Discovery.FindCriteria对象。该对象的信息作为Probe请求将发送。其构造函数指定了在Probe请求中发送的服务合约。&lt;/div&gt;&#xD;
&lt;div&gt;第三行代码中，DiscoveryClient的Find方法使用本地UdpDiscoveryEndpoint端点广播一条Proble消息，该方法的参数执行了查找的服务。Find方法返回一个FindResponse对象，其EndpointAddress集合包含了每个响应服务的地址。&lt;/div&gt;&#xD;
&lt;div&gt;Find方法的运行可能会耗费一段时间。在WS-Discovery协议中，当客户端发送一条Probe消息，零个或多个服务将响应该消息。如果没有指定的时间内接收到任何ProbeMatch消息，客户端可以选择超时。同样地，可能没有任何一个响应的服务是客户端可接受服务。因此，客户端可能会继续等待一段时间，然后检查接收到的所有ProbeMatch响应以确定哪一个服务最适合客户端的需求（比如，客户端可能检查每个响应的URLs然后决定连接到一个地理上与客户端最接近的服务）。&lt;/div&gt;&#xD;
&lt;div&gt;DiscoveryClient类的Find方法隐藏了其实现的复杂性。当创建FindCriteria对象，你可以设置该对象的持续Duration和MaxResults属性。正如它们的名字所指示的那样，Duration属性设定Find方法等待结果的时间是多长（默认值是20秒），MaxResults属性指定结果所能接收的最大值（默认值为Int32.MaxInt）。Find方法的等待要么发生超时要么结果超出最大数。然后，Find方法然后从各个ProbeMatch消息中收集数据然后将这些信息存贮在FindResponse对象的Endpoints集合中，最后返回FindResponse对象。如果你希望客户端程序不因为执行检测服务而被阻塞，你可以调用FindAysnc方法；该方法立即返回，但每次客户端接收到一条响应消息都会触发DIscoveryClient对象的FindProgressChanged事件。当DiscoveryClient对象确定话费了足够的时间等待结果后会触发FindCompleted事件。&lt;/div&gt;&#xD;
&lt;div&gt;当DiscoveryClient对象结束检测服务，调用Close方法关闭UdpDiscoveryEndpoint端点。&lt;/div&gt;&#xD;
&lt;div&gt;创建一个新的端点地址实例，然后将FindResponse对象的属性Endpoints集合中第一个匹配服务的地址赋值给该实例。然后，创建一个新的ProductsServcieClient代理对象实例，并将从端点实例的值赋值给代理对象实例。后面的代码与之前的练习的代码一样。&lt;/div&gt;&#xD;
&lt;div&gt;4. 重新生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;在你开始测试客户端程序之前，你必须部署服务至一个支持检测的宿主环境。不幸的是，ASP.NET开发Web服务器不吃之该特性，因此你必须将服务发布在IIS上。该练习还可以证明：即使你移动了服务，而且你并未做客户端做任何修改，客户端还是可以连接至服务。&lt;/div&gt;&#xD;
&lt;div &gt;练习：部署ProductsService至IIS并测试客户端程序&lt;/div&gt;&#xD;
&lt;div&gt;1. 在解决方案浏览器窗口，在C:\...\ProductsServcie\projec项目上点击右键，然后选择发布Web站点&lt;/div&gt;&#xD;
&lt;div&gt;2. 在发布站点对话框窗口中，在目标位置框内输入http://localhost/DiscoverableProductsService，然后点击确认按钮。等待"发布成功"出现在Visual Studi的状态栏中。&lt;/div&gt;&#xD;
&lt;div&gt;3. 发开IIS，并确认DiscoverableProductsService站点已经建立。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在该站点上点击右键，选择管理程序，然后点击高级设置。在高级设置对话框中，设置应用程序缓冲池为ASP.NET V4.0；然后点击确认按钮&lt;/div&gt;&#xD;
&lt;div&gt;5. 在中间面板点击查看内容标签。如果ProductsService已经成功部署，那么你应看看如下入所示的文件：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348047560.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;6. 在ProductsService.svc上点击右键，然后选择"在浏览器中浏览"。确认IE自动启动并显示ProductsServiceImpl服务页面。这步操作确认了你的服务已正确部署。&lt;/div&gt;&#xD;
&lt;div&gt;7. 返回到Visual Studio，在非调适模式下运行解决方案。大约20秒（传递至DiscoveryClient实例的FindCriteria对象的属性Duration的值为20秒）之后，你将看到客户端程序已经发现了ProductsService服务的地址，并显示在客户端控制台窗口中，而且该地址与上一个练习的服务的地址是不一样的。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348045433.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;8.   按下ENTER键，客户端程序将连接至上述地址，然后得到和上一个练习一样的结果。&lt;/div&gt;&#xD;
&lt;div&gt;9. 当客户端程序结束，按下ENTER键以关闭客户端程序窗口，然后回到Visual Studio。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;处理服务声明&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;检测服务无疑是一项非常强大技术，但是使用Ad Hoc模式查找服务有点费时而且可能使客户端程序不能成功地发现服务；基本上，每次当你想连接至一个服务时，你都必须等待直到服务端点被发现。你可以调整用于检测服务的FindCriteria对象的Duration属性的值，但是如果你把该值修改的太小，那么你将会因为在既定的时间内没有接收到Probe请求的响应而面临服务不能被发现的风险。Ad Hoc模式对网络也是不友好的，特别是当你的组织拥有大量客户端的情况下；每次当客户端连接至一个服务，它必须在你的网络中广播一条Probe请求。为了应付这些问题，一种可行的解决方法是处理服务声明。&lt;/div&gt;&#xD;
&lt;div&gt;一个支持可检测的服务在启动时向已知的地址发送一条多播消息而"声明"自己的存在。客户端程序能侦听这些多播消息并在一个本地集合也称之为缓存（服务的地址和服务的元数据一起通过服务声明传输至客户端）中保存服务的详细信息。当客户端希望向服务发送消息时，客户端从本地缓存中查询服务的地址，然后使用该地址连接至服务并调用服务的操作。&lt;/div&gt;&#xD;
&lt;div&gt;相似地，当一个服务关闭时，该服务同样向网络中广播一条关闭消息。客户端程序能够捕获这些消息，并使用这些信息将服务的详细信息从本地的缓存中移除。&lt;/div&gt;&#xD;
&lt;div&gt;在这种情形下，检测服务的功能就从客户端转移到服务端。客户端不再向服务发送Probe消息，相应地，服务也不需要实现用于识别服务的端点。但是，你需要修改服务使之在启动和关闭时发送声明消息。WCF为该特性提供了便利；所有你需要做的仅仅是添加一个udpAnnouncementEndpoint标准端点到servcieDiscovery行为元素。WCF运行时将为你做其余的一切。&lt;/div&gt;&#xD;
&lt;div&gt;客户端程序可以使用AnnouncementServcie对象侦听声明。该类位于System.ServiceModel.Discovery命名空间下。在客户端程序中，你创建该类的一个实例，然后为它提供一个侦听的端点。当一条新的服务声明到达，将触发AnnouncementServcie对象的OnlineAnnouncementReceived事件，服务的详细信息传递至该事件。同样地，当服务关闭时OfflineAnnouncement事件也将发生。&lt;/div&gt;&#xD;
&lt;div&gt;在进行练习之前，我们来分析一下与服务声明有关的类及其执行过程。下图非常明显的显示了UdpAnnouncementEndpoint，AnnouncementEndpoint与ServiceEndpoint之间的关系。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348058597.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;我们再来看AnnounceEndpoint构造函数的执行过程：&lt;/div&gt;&#xD;
&lt;div&gt;1. AnnouncementEndpoint():this(DiscoveryVersion.DefaultDiscoveryVersion). 可见AnnouncementEndpoint方法调用了自身的另外一个接收参数DiscoveryVersion的构造器方法。而该方法为其子类UdpAnnouncementEndpoint所拥有。注意DefaultDiscoveryVersion的值为WSDiscovery11.&lt;/div&gt;&#xD;
&lt;div&gt;2. 而上面的方法在UdpAnnouncementEndpoint中是这样的定义的：UdpAnnouncementEndpoint(DiscoveryVersion discoveryVersion) : this(discoveryVersion, UdpAnnouncementEndpoint.DefaultIPv4MulticastAddress)；可见其访问了另外一个构造方法。&lt;/div&gt;&#xD;
&lt;div&gt;3. 上述方法访问下面的方法&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/20110830234806615.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;首先，因为该方法调用了基类的base(discoveryVersion)方法，因此我们先看基类的该方法。在基类中该方法是这样定义的：AnnouncementEndpoint(DiscoveryVersion discoveryVersion) : this(discoveryVersion, null, null)。它访问了另外一个构造方法：AnnouncementEndpoint(DiscoveryVersion discoveryVersion, Binding binding, EndpointAddress address) : base(AnnouncementEndpoint.GetAnnouncementContract(discoveryVersion))。&lt;/div&gt;&#xD;
&lt;div&gt;4. 因为DisCoveryVersion的值为WSDiscovery11，让我们继续查看DiscoveryVersion类，该类有一个共有名为WSDicovery11的属性，其类型为DiscoveryVersion。该属性是这样定义的：DiscoveryVersion.wsDiscovery11 = new DiscoveryVersion("WSDiscovery11", "http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01", new DiscoveryVersion11Implementation());我们可以看到由DiscoveryVersion11Implementation类真正实现WSDiscovery11，它包含了上面所需的方法GetAnnounceContract方法&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348078173.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 获取完服务合约之后，继续访问ServiceEndpoint的构造方法ServiceEndpoint(ContractDescription contract)。&lt;/div&gt;&#xD;
&lt;div&gt;6. 随后，回到AnnouncementEndpoint的构造方法AnnouncementEndpoint(DiscoveryVersion discoveryVersion, Binding binding, EndpointAddress address)。该方法为服务添加可检测版本；并设置端点的地址和绑定。&lt;/div&gt;&#xD;
&lt;div&gt;7. 最后，我们再回到UdpAnnouncementEndpoint的Initialize方法。该方法指定编码模式，消息版本，并添加自定义的绑定，最后为绑定添加一些行为。其方法的内容如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348072383.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;在接下来的一组练习中，你将修改ProductsService服务发送声明消息。然后你将更新客户端程序以捕获这些消息并在服务启动或者关闭时保存该服务的详细信息。&lt;/div&gt;&#xD;
&lt;div &gt;练习：配置ProductsService服务以发送声明&lt;/div&gt;&#xD;
&lt;div&gt;1. 在Visual Studio中，使用WCF服务配置编辑器打开C:\...\ProductsService项目下的web.config文件&lt;/div&gt;&#xD;
&lt;div&gt;2. 在配置面板，展开服务文件夹下的Products.ProductServiceImpl服务，展开端点文件夹，然后点击第二个未命名端点。确认该端点是udpDiscovery端点，在该端点上点击右键，然后选择删除端点以移除该端点。在显示"This item will be deleted"消息对话框中点击确认按钮。&lt;/div&gt;&#xD;
&lt;div&gt;服务现在需要发送声明而不是发送对Probe请求的响应消息，因此不再需要该端点。但是，服务可以同时实现这两种机制（发送声明消息和发送Probe的响应消息），因次你应该根据实际情况选择保留哪个端点。&lt;/div&gt;&#xD;
&lt;div&gt;3. 在配置面板，展开高级文件夹，展开服务行为文件夹，展开未命名行为，然后展开serviceDiscovery行为元素。&lt;/div&gt;&#xD;
&lt;div&gt;serviceDiscovery行为元素包含一个声明端点。你可以为服务添加该端点以发送声明消息。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在声明端点上点击右键，然后点击创建新的客户端的端点。在端点面板中，设置Kind属性为udpAnnouncementEndpoint。&lt;/div&gt;&#xD;
&lt;div&gt;udpAnnouncementEndpoint是另外一个标准端点，它被事先配置并使用WS-Discovery规范指定的协议发送声明消息。如果你需要更改声明地址或者声明端点的配置，你需要为udpAnnouncementEndpoint类型创建一个标准的端点配置，然后通过该配置做相应的更改。&lt;/div&gt;&#xD;
&lt;div&gt;5. 保存配置文件并关闭WCF服务配置编辑器。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;在修改客户端之前，我们浏览一下客户端捕获声明消息所需的类。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/20110830234808879.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;实际上，捕获服务声明时，在客户端建立了一个AnnouncementService服务，该服务负责与ProductsService服务通信，并获取ProductsService服务发送的Online和Offline消息，并将ProductsService服务的详细信息保存在本地的缓冲中。然后ProductsClient才访问本地缓存，并从缓存中或缺ProductsServcie服务的地址，然后与ProductsService服务进行通讯。整个流程的详细过程我不在赘述。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：修改客户端程序以捕获声明消息&lt;/div&gt;&#xD;
&lt;div&gt;1. 打开ProductsClient项目下的Program.cs文件&lt;/div&gt;&#xD;
&lt;div&gt;2. 在Program类中，添加下面的using语句&lt;/div&gt;&#xD;
&lt;div&gt;Using System.Collections.Concurrent&lt;/div&gt;&#xD;
&lt;div&gt;3. 在Programm类中，在main方法前添加如下的ConcurrencyDictionary集合&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348093453.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;定义在System.Collection.Concurrent命名空间下的ConcurrentDictionary类是一个线程安全的字典集合。当客户端程序接收到声明消息，将会把发送声明服务的详细信息存储在该集合中。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在main方法中，删除注释"Test the operations in the service"之前的所有代码和注释。&lt;/div&gt;&#xD;
&lt;div&gt;5. 在main方法中的开始处，添加下面的声明：&lt;/div&gt;&#xD;
&lt;div&gt;AnnouncementService announceService = new AnnouncementService();&lt;/div&gt;&#xD;
&lt;div&gt;该语句创建一个AnnounceService对象以侦听声明消息&lt;/div&gt;&#xD;
&lt;div&gt;6. 添加下面的代码到Main方法中&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/20110830234810696.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码订阅OnlineAnnouncementReceived事件，该事件在服务启动并发送声明消息时被触发。该事件的eventArgs参数是AnnouncementEventArgs类型的一个实例。该类型最应关注的属性是EndpointDiscoveryMetadata，它包含了服务的详细信息，包括声明服务的地址和合约。事件处理器添加服务元数据到service字典集合中，并用服务的地址作为字典的key（当另外一个同样服务使用字典中已经存在的地址，那么事件处理器不能将该服务添加至字典，并将抛出异常。）。最后，事件处理器迭代服务实现的合约并将服务合约和服务的地址显示在客户端控制台窗口中。这样你可以清楚地看到当服务声明发生；但在产品环境中你不要这么做。&lt;/div&gt;&#xD;
&lt;div&gt;7. 添加下面的代码之Main方法中&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348104665.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码订阅OfflineAnnouncementReceived事件，该事件在服务指明其正在关闭时发生。和第六步一样，AnnouncementEventArgs参数包含了服务的详细信息。事件处理器从service集合中移除服务元数据，捕获并报告发生的异常。&lt;/div&gt;&#xD;
&lt;div&gt;8. 在OfflineAnnouncementReceived事件后，添加如下代码：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348112223.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码为AnnouncementService对象创建一个宿主，该宿主程序用于侦听UdpAnnouncementEndpoint标准端点对上的声明。该端点使用默认配置的并且与服务广播放松声明的相同地址上进行侦听。当宿主启动后，客户端程序等待用户按ENTER键以继续。&lt;/div&gt;&#xD;
&lt;div&gt;9. 添加如下代码至Main方法&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348111733.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;这些代码与之前向服务发送Probe消息类似，但上述代码从services集合中获取服务的详细信息，而为采用WS-Discovery协议。FindCriteria类的IsMatch方法比较服务的元数据和一个指定的服务合约然后返回匹配的结果。在上述代码中，LINQ查询在services集合搜索实现了IProductsService服务合约的服务最后返回第一个匹配服务的地址。&lt;/div&gt;&#xD;
&lt;div&gt;10. 添加下面的代码之main方法&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348128418.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码创建ProductsServiceClient代理的实例并把从services集合中搜索的服务的地址复制给ProductsServiceClient实例。&lt;/div&gt;&#xD;
&lt;div&gt;客户端程序中剩下的代码与上一个练习时一样的。&lt;/div&gt;&#xD;
&lt;div&gt;11. 重新生成解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;最后一步是部署更新后的服务至IIS。部署完之后，你就可以运行客户端程序验证它是否可以正确地接收到服务声明。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;练习：测试ProductsService和客户端程序&lt;/div&gt;&#xD;
&lt;div&gt;1. 在解决方案浏览器窗口，在C:\...\ProductsService项目上点击右键，然后点击发布Web站点。发布更新后的Web站点至http://localhost/DiscoverableProductsService。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在非调适模式下运行解决方案。在客户端程序控制台窗口中，先不要按ENTER键因为ProductsService服务还没有发送任何声明消息，因为客户端程序还不能发现ProductsService服务。&lt;/div&gt;&#xD;
&lt;div&gt;3. 返回IIS。在连接面板，点击DiscoverableProductsService站点，然后点击中间面板的查看内容标签，然后在ProductsService.svc文件上点击右键，选择在浏览器中查看。该行为将会启动ProductsService服务，导致服务发送声明消息。关闭IE浏览器然是不关闭IIS。&lt;/div&gt;&#xD;
&lt;div&gt;4. 返回到客户端控制台窗口。你会发现它现在显示了一条消息，该消息指明客户端已经接收到一条来自实现IProductsService接口服务的声明消息：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348122388.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 在客户端程序中，按下ENTER键。客户端程序将像之前的练习一样的运行。当客户端程序结束，不要按下ENTER键关闭客户端程序。&lt;/div&gt;&#xD;
&lt;div&gt;6. 返回IIS中。在连接面板，点击应用程序池，然后选择DiscoverableProductsService站点所使用的应用程序池，然后关闭该应用程序池。&lt;/div&gt;&#xD;
&lt;div&gt;7. 返回到客户端控制台窗口，你将看到离线声明事件被触发。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108302348135486.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;8. 在客户端控制台窗口中按ENTER键盘关闭客户端程序，并回到Visual Studio。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div &gt;参考&lt;/div&gt;&#xD;
&lt;div&gt;&lt;a href="http://msdn.microsoft.com/zh-cn/library/dd456782.aspx"&gt;http://msdn.microsoft.com/zh-cn/library/dd456782.aspx&lt;/a&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2160202.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/08/30/2160202.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/08/25/2153521.html</id><title type="text">WCF 4.0 进阶系列 – 第十三章以更好的性能实现WCF服务（下）</title><summary type="text">在传输数据时，使用合适的编码机制对提高性能亦有显著的影响。你已经了解到，WCF支持使用文本和二进制对消息进行编码。两者相比较，二进制经常更具有影响力并且占用较少的网络流量，但是二进制编码格式是与平台相关的，因此运行在非Windows平台上的服务和客户端程序不能轻易地使用它。此外，WCF还支持消息传输优化机制（MTOM），该机制为传输大块的二进制数据提供了一个标准的，互操作的，并且有效的格式。如果你知道服务传输数据的大小，那么此时启用MTOM是非常有用的；然而有些服务发送未知大小的数据块，这种场景下最好启用流模式传输数据，WCF同样支持从服务输出流。在本小节中，你将学习到如何使用MTOM编码数据以减少大二进制数据对象的系统开销，以及何如启用流模式以最大化地利用网络带宽。</summary><published>2011-08-25T09:11:00Z</published><updated>2011-08-25T09:11:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/08/25/2153521.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/08/25/2153521.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;p&gt;&lt;strong&gt;启用MTOM传输数据&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;MTOM 是一种机制，用来以原始字节形式传输包含 SOAP 消息的较大二进制附件，从而使所传输的消息较小。一条SOAP消息经常由消息头和消息体构成；消息头提供地址，路由信息和安全信息；消息体提供数据，以及消息的负载。消息体由XML构成，包含在传输至服务的请求或者返回客户端程序的响应信息中。消息体信息的真正结构是由WSDL对操作的描述而指定，这些操作由你在服务中指定的操作合约衍生而来。举例来说，你在本书第一章创建的ProductService服务，在IProductsService服务合约中定义了ChangeStockLevel操作：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251658382390.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当客户端程序调用ChangeStockLevel操作，WCF运行时创建一条如下的消息：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251658399467.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;你可以看到消息体包含了操作所需的参数，这些参数被编码为XML信息集合。这种数据结构使得参数以一种非常直观的方式呈现。但是，请记住，当XML消息在网络中传输时，它会被转换成一系列的文本字符，而非文本数据。比如上面例子中的&amp;lt;newStockLevel&amp;gt;和&amp;lt;bin&amp;gt;元素，在客户端发送时必须转化为文本，在被服务接收到时必须转化为数字。这种转化在两个层面增加了系统开销：&lt;/div&gt;&#xD;
&lt;div&gt;1. 把二进制格式的整数转化文本和从文本转化回整数会消耗时间，内存和计算能力。&lt;/div&gt;&#xD;
&lt;div&gt;2. 当数据被转换为文本后在网络中传输时可能没有原始的数据类型精简；原始数据的值越大，那么转换为文本后需要更多的文本字符。&lt;/div&gt;&#xD;
&lt;div&gt;在上面的例子中，增加的系统开销是微弱的。但是，你应该如何处理二进制数据，比如一个图片类型的数据。一种可能的解决方案是转化二进制格式为仅包含对应的0和1这两个字符的文本格式。但是考虑到这种方式增加的系统开销。转化一兆的二进制数据将包含一百万个字符的字符串，这要求大量的内存和时间。如果WCF转化二进制数据为一个Base64位编码的字符串而非0和1这两个字符构成的字符串文本，那么会发生什么？结果是转化为Base64编码的字符串后，文本数据更精简。但是，平均起来，Base64编码机制的字符串比原始的字符串长度增加40%。此外，接收方接收到数据后还必须转会数据原始的类型。很明显，当需要传输大的二进制数据消息时，我们需要寻找另外的解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;MTOM规范就是另外一种解决方案。当你使用MTOM传输包含二进制数据的消息时，二进制数据不会编码为文本，而是其作为一个附件，并以双方均知晓的MIME（多功能Internet邮件扩充规范）规定的格式传输至接收方。原始消息中的所有文本信息都被编码为XML信息集合，但二进制信息被作为MIME附件的引用。下图展示了MTOM如何编码二进制数据：&lt;br /&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251658406271.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;像往常一样，是要MTOM时也必须考虑安全因素。当签名MTOM编码的消息，WCF计算的签名包含了消息的所有MIME附件。如果消息的任何一部分，包括MIME附件，在发送和接收的时候改变了，那么消息的签名就会失效。更多关于签名的详细信息，请回顾第四章"&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/05/24/2054834.html"&gt;保护企业内部的WCF服务&lt;/a&gt;"。&lt;/div&gt;&#xD;
&lt;div&gt;在WCF中，MTOM由一个特定的编码通道处理。如果你使用标准的HTTP绑定（basicHttpBinding, wsDualHttpBinding, wsFederationHttpBinding, 或ws2007HttpBinding），那么你可以在消息编码配置中更改消息编码属性为MTOM。而其他的传输协议，比如TCP，MSMQ和命名管道，默认情况下，它们使用自己特有的二进制编码；其对应的标准绑定没有MessageEncoding属性，但如果你希望在这些绑定上启用MTOM编码消息，那么你必须创建自定义的绑定。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;向客户端程序发送二进制数据对象&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;考虑下面的场景：AdventureWorks希望扩展WCF服务ShoppingCartService，以使用户可以查看公司产品图片。产品图片以二进制方式存贮在数据库中。开发人员已经创建了ShoppingCartPhotoService的服务原型，该服务提供了GetPhoto的操作，该操作从数据库获取图像然后返回给客户端程序。在下面的练习中，你将检查该项目，并学习如何配置该服务使其使用MTOM编码消息。&lt;/div&gt;&#xD;
&lt;div &gt;练习：检查ShoppingCartPhotoService&lt;/div&gt;&#xD;
&lt;div&gt;1. 启动Visual Studio，打开*\WCF\Step.by.Step\Solutions\Chapter13\MTOM文件夹下的MTOMService解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;该方案包含一个名为ShoppingCartPhotoService的服务原型，该服务实现了GetPhoto操作；并且该服务由ASP.NET 开发Web服务器提供宿主。该解决方案还包含一个简单的WPF客户端程序。&lt;/div&gt;&#xD;
&lt;div&gt;2. 打开C:\...\MTOMService项目中App_Code文件夹下的IShoppingCartPhotoService.cs文件。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251658414220.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;检查定义服务合约的IShoppingCartPhotoService接口。该接口包含GetPhoto操作，其允许客户端程序发送一个产品编码从而请求一个产品图片。该服务获取图片后将该图片作为返回参数返回至客户端程序。因为图片以二进制格式存贮在数据库中，所以返回参数的类型为byte[]。最后，该操作的返回值为bool值，以指定操作成功还是失败。&lt;/div&gt;&#xD;
&lt;div&gt;3. 继续检查App_Code文件夹下的ShoppingCartPhotoService.cs文件，通过下图你可以看到服务实现类是如何实现GetPhoto方法的。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251658445318.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;该方法并未包含任何与WCF相关的东西；它通过ProductsPhotoModel实体执行一个LINQ查存；然后从数据库的ProductPhoto表中根据指定的产品编码获取对应产品的图片；最后该查询返回一个ProductPhoto对象。&lt;/div&gt;&#xD;
&lt;div&gt;图片信息保存在ProductPhoto表中的LargePhoto列中，该列的数据类型为varbinary。实体框架获取该varbinary数据并填充至LINQ查询的返回对象PruductPhoto的LargePhoto属性上（该属性的数据类型为byte[]）。该对象的LargePhoto属性的值赋给输出参数photo。如果从数据库找到产品并获取到图片，那么该方法返回true；如果发生异常则返回false。&lt;/div&gt;&#xD;
&lt;div&gt;4. 打开ShoppingCartGUIClient程序中的ClientWindow.xaml文件。该XAML文件定义了一个WPF窗口，其包含一个图片控件，该控件占据了整个窗口的大部分。此外，该窗口还包含一个文本标签，一个文本输入框和一个按钮控件。用户可以在文本框内输入产品编码，然后点击按钮获取产品图片。&lt;/div&gt;&#xD;
&lt;div&gt;5. 打开ClientWindows.xaml.cs文件。你可以看到当用户点击获取图片按钮之后，将运行getPhoto_Click方法。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251658461706.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述方法创建一个客户端代理实例，读取用户输入的产品编码，然后创建一个新的byte数组，然后调用GetPhoto操作，并将byte数组和产品编码作为参数传递给操作。如果操作返回true，那么方法将使用包含产品图片的byte数组去填充BitmapImage对象，该对象负责显示产品图片。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在非调适模式下运行解决方案。ShoppingCartPhotoService启动ASP.NET开发Web服务器并在9080端口侦听客户端的请求。&lt;/div&gt;&#xD;
&lt;div&gt;当ShoppingCartGUI客户端窗口出现，在产品编号文本框内输入WB-H098，然后点击获取照片按钮。将显示一张包含两个水瓶的照片。如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251658492871.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;7. 关闭ShoppingCartGUI客户端窗口，然后返回到Visual Studio。&lt;/div&gt;&#xD;
&lt;div&gt;8. C:\...\MTOMService项目中选中web.config，然后点击右键，选择使用WCF服务配置管理工具。这将启动WCF服务配置管理工具编辑web.config文件。&lt;/div&gt;&#xD;
&lt;div&gt;9. 在配置面板，点击诊断文件夹。然后在诊断面板，点击启动消息日志。&lt;/div&gt;&#xD;
&lt;div&gt;10. 在配置面板，点击诊断文件夹下的消息日志节点。在消息日志面板，设置LogEntireMessage属性为true。&lt;/div&gt;&#xD;
&lt;div&gt;11. 在配置面板，展开侦听文件夹，然后点击ServiceModelMessageLoggingListener节点。在右边面板中，设置InitData属性为web_messages.svclog，该文件位于*\WCF\Step.by.Step\Solutions\Chapter13\文件夹下。&lt;/div&gt;&#xD;
&lt;div&gt;12. 在配置面板，展开诊断文件夹下的源节点，然后点击ServiceModelMessageLogging节点。在右边面板中，设置追踪级别为Verbose（提供详细的输出信息）。&lt;/div&gt;&#xD;
&lt;div&gt;13. 在配置面板，再次展开诊断文件夹。在诊断面板，点击启动追踪。使用该选项后，你可以捕获WCF运行时生成发送和接收消息的行为信息。&lt;/div&gt;&#xD;
&lt;div&gt;14.  在配置面板，点击侦听文件夹下的ServiceModelTraceListener节点。在右边面板中，设置InitData属性为web_trace.svclog，该文件位于*\WCF\Step.by.Step\Solutions\Chapter13\文件夹下。&lt;/div&gt;&#xD;
&lt;div&gt;15. 在配置面板，点击源文件夹下System.ServiceModel节点。在右边面板，设置追踪级别属性为Verbose。&lt;/div&gt;&#xD;
&lt;div&gt;16. 保存配置文件。&lt;/div&gt;&#xD;
&lt;div&gt;17. 回到Visual Studio中，在非调试模式下运行解决方案。在ShoppingCartGUI客户端程序中，在产品编码文本框内分别输入WB-H098和PU-M044，你将会分别得到两张产品的图片。然后关闭客户端程序，并停止ASP.NET开发Web服务器。&lt;/div&gt;&#xD;
&lt;div&gt;18. 启动服务追踪查看器。在服务追踪查看器中，打开*\WCF\Step.by.Step\Solutions\Chapter13\文件夹下的web_messages.svclog&lt;/div&gt;&#xD;
&lt;div&gt;19. 在右边面板中，点击消息标签。你将看到列出了4条消息：两条请求消息和两条响应消息（因为ShoppingCartPhotoService服务配置实用basicHttpBinding绑定，所以没有生产额外的消息；比如安全凭证或者建立可靠消息会话的消息，等等）。&lt;/div&gt;&#xD;
&lt;div&gt;20. 点击第一条消息，在右下面板中，点击消息标签，然后滚动到消息的底部。你将看到该消息请求产品WB-H098的产品图片。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251658586373.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;21.  在左边面板，点击第二条消息。然后在右下面板，点击"消息"标签。这是一条将图片数据的包含在&amp;lt;photo&amp;gt;元素中响应消息。你可以看到该数据包含一个长字符串，其为Base64编码的二进制数据。检查余下的消息，第三条消息请求产品PU-M044的产品图片，第四条响应消息包含Base64编码的图片数据。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659008094.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;22. 打开*\WCF\Step.by.Step\Solutions\Chapter13\文件夹下的web-tracelog.svclog文件。在左边面板中点击"活动"标签。第一个文件包含一条由WCF运行时产生的日志，并且活动面板显示一系列的由服务端的WCF运行时执行的任务。&lt;/div&gt;&#xD;
&lt;div&gt;23. 找到并点击第一条名为"Process action 'http://adventure-works.com/2010/07/01/ShoppingCartPhotoService/GetPhoto'"的活动，右上的面板将显示由该活动所执行的任务，其包含从通道接收消息，打开服务实例，执行操作，创建响应消息，最后关闭服务实例。&lt;/div&gt;&#xD;
&lt;div&gt;24. 在右上面板中，向下滚动任务列表，然后选中"A Message was written"。右下面板将显示该消息的相关信息。在消息属性的头部区域，请注意编码器属性值为text/xml;charset=utf-8。这表明消息使用文本编码：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/20110825165912427.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;25. 在服务追踪查看器的菜单中，点击文件，然后选择关闭所有文件。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;上述练习为你演示了，在默认情况下，ShoppingCartPhotoService服务发送的所有消息&amp;mdash;包括含有大量二进制数据的消息&amp;mdash;均使用文本编码和传输。对于包含二进制图片的消息，如果ShoppingCartPhotoService服务中包含图片的消息使用MTOM编码将更有效。&lt;/div&gt;&#xD;
&lt;div&gt;在下面的练习中，你将学习如何修改ShoppingCartService服务的绑定配置以使用MTOM编码二进制数据，然后使用HTTP协议从服务端向客户端传输编码后的数据。&lt;/div&gt;&#xD;
&lt;div &gt;练习：配置ShoppingCartPhotoService服务以启动MTOM&lt;/div&gt;&#xD;
&lt;div&gt;1. 返回到WCF服务配置编辑器，该编辑器当前显示ShoppingCartPhotoService服务的web.config文件&lt;/div&gt;&#xD;
&lt;div&gt;2. 在左边面板，点击绑定文件夹。在绑定面板，点击创建新的绑定配置。为basicHttpBinding添加一个新的绑定配置。该绑定的名字为ShoppingCartPhotoServiceBasicHttpBindingConfig，并修改MessageEnconding属性的值为Mtom。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659147754.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;3. 在配置面板，展开服务文件夹下的ShoppingCartPhotoService.ShoppingCartPhotoServiceImpl节点，然后展开端点文件夹，然后点击未命名服务端点。在服务端点面板，设置BindingConfiguration属性为ShoppingCartPhotoServiceBasicHttpBindingConfig。&lt;/div&gt;&#xD;
&lt;div&gt;4. 保存配置文件，然后关闭WCF服务配置编辑器。&lt;/div&gt;&#xD;
&lt;div&gt;5. 在Visual Studio中，使用WCF服务配置编辑器打开ShoppingCartGUIClient项目下的app.config文件。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在配置面板中，展开绑定文件夹，然后点击BasicHttpBinding_ShoppingCartPhotoService绑定配置。客户端端点使用该绑定的配置；该绑定配置在使用添加服务向导时自动生成。&lt;/div&gt;&#xD;
&lt;div&gt;7. 在右边面板中，设置绑定BasicHttpBinding_ShoppingCartPhotoService的MessageEncoding属性为Mtom以匹配服务绑定的配置。&lt;/div&gt;&#xD;
&lt;div&gt;8. 保存配置文件。&lt;/div&gt;&#xD;
&lt;div&gt;9. 使用Windows文件浏览器，删除*\WCF\Step.by.Step\Solutions\Chapter13文件夹下的web_messages.svclog和web_tracelog.svclog文件。&lt;/div&gt;&#xD;
&lt;div&gt;10. 回到Visual Studio中，在非调适模式下运行解决方案。分别输入产品编号WB-H098和PU-M044，并确认这两个产品对于的图片在客户端窗口中显示。然后关闭客户端程序和停止ASP.NET开发Web服务器。&lt;/div&gt;&#xD;
&lt;div&gt;11. 在WCF服务追踪查看器中，打开web_tracelog.svclog文件。在活动标签，找到并选择名为"Process Action 'http://adventure-works.com/2010/07/01/ShoppingCartPhotoService/GetPhoto'"。然后在右上面板中，找到并点击任务"A message was written"。在右下面板中，检查消息属性的头部区域的Encoder属性。这次编码器属性的值为multipart/related;type='application/xop+xml'。这表明服务现在使用使用MTOM编码的MIME多部分消息传输数据。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659155081.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;12. 关闭WCF服务追踪查看器。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;控制消息大小&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;你已经看到了配置绑定使用MTOM编码是一项相当简单的任务。启用MTOM不会影响你程序的功能，而且你不必编写任何特别的编码以更改绑定使用MTOM编码。但是，你应该注意到刚才使用MTOM的环境是一个非常适合使用MTOM编码的环境。发送和接收包含大二进制数据消息的服务容易引起DOS攻击；如果一个服务启用MTOM，那么攻击者可能试图发送一些非常大的消息以试图使服务"交通阻塞"。同样地，一个恶意的服务也可能对客户端请求返回大量的响应消息以试图中断用户计算机。因此，WCF运行时允许你限制客户端和服务可接收消息的大小。&lt;/div&gt;&#xD;
&lt;div&gt;在下面的练习中，你将检查当尝试接受一些超出WCF运行时允许的大小的数据时将发生什么情况，并且学习如果配置绑定以使其支持较大的消息。&lt;/div&gt;&#xD;
&lt;div &gt;练习：在客户端接收大消息&lt;/div&gt;&#xD;
&lt;div&gt;1. 在Visual Studio中，在非调适模式下运行解决方案。在ShoppingCartClientGUI程序中，在产品编码出输入BK-M38S-46，然后点击Get Photo按钮。ShoppingCartClientGUI程序将显示下面的异常消息对话框：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659166028.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;异常产生的原因是因为默认情况下，WCF运行时只允许在消息中的数组长度为16384字节（16Kb）。在AdventureWorks数据库中，Mountain-400-W自行车产品的图片大小超过了16Kb。WCF运行时允许服务发送数据（服务可以发送的数据不受限制），但阻止客户端程序接收该数据。&lt;/div&gt;&#xD;
&lt;div&gt;2. 在消息对话框中，点击确认按钮，然后关闭ShoppingCartGUI客户端窗口。&lt;/div&gt;&#xD;
&lt;div&gt;3. 返回到WCF服务配置编辑器，打开的是ShoppingCartGUIClient项目下的app.config。在控制面板中，点击绑定文件夹下的BasicHttpBinding_ShoppingCartPhotoService绑定。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在右边面板，在绑定区域向下滚动内容，找到ReaderQuota属性。请注意该熟悉当前的值为16384。更改其为32768.更改的结果是WCF运行时将允许客户端接收的消息所包含的数组最大为32Kb。保存配置文件，并退出WCF服务配置编辑器。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/20110825165918323.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 再次在非调试模式下运行解决方案。在客户端程序的产品编码处，输入BK-M38S-46。这次产品图片将成功的显示。&lt;/div&gt;&#xD;
&lt;div&gt;6. 关闭客户端程序窗口。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;除了MaxArrayLength属性之外，ReaderQuotas元素还包括所有绑定都具有的其他几个属性：MaxBytesPerRead，MaxDepth，MaxNameTableCharCount和MaxStringContentLength。这些属性确定了WCF运行时在处理消息并抛出异常前的复杂性。消息体所包含的数据是一个XML文档；在内部，WCF运行时使用XmlDictionaryReader对象转化消息体的内容并将这些内容分割成适当的数据，然后把这些数据以其所期望的值返回给服务或客户端程序。WCF运行时使用ReaderQuota属性配置XmlDictionaryReader对象并限制该对象可以处理的消息大小和结构。MaxBytesPerRead属性指定当XmlDictionaryReader处理消息时，一次可以从消息中读取多少字节的数据。MaxDeptch属性指定消息中元素节点的最大深度。MaxNameTableCharCount属性限制XmlDictionaryReader的NameTable中原子化的字符串中的总字符数。（原子化字符串时，会将字符串插入 NameTable 且永远不会移除。 这会导致在 NameTable 中累积大量字符数据。 因此需要对XmlDictionaryReader的 NameTable 中可以缓冲的数据量设置限制）。MaxStringContentLength限制由各种 API 创建和返回的字符串的长度。如果你设置这些属性的值为0，那么XmlDictionaryReader将使用它们的默认值。&lt;/div&gt;&#xD;
&lt;div&gt;ReaderQuotas属性中，你最可能修改的就是MaxArrayLenght属性和MaxStringContentLenght属性，因为这两个属性直接关系到发送消息所包含数据的大小。&lt;/div&gt;&#xD;
&lt;div&gt;但是，这事到此还没结束。一条消息可能含有多个数组或者字符串。ReaderQuotas的属性仅仅限制单个元素的大小，而不是一条消息中所有元素的大小；因此，DOS攻击仍然可能发生。如果你在WCF服务配置编辑器中检查一个绑定配置，你将发现绑定还有一个MaxReceivedMessageSize属性；此属性管理能接收消息的总大小。请注意，该属性的值包含在SOAP消息的头部中或者其他管理信息中（如果帮定没有使用SOAP）。&lt;/div&gt;&#xD;
&lt;div&gt;该属性的默认值大小为65536bytes（65Kb），这对足以适应大多数情况，但如果有必要你可以增加该属性的值。你可以设定的最大值为2,147,483,647（Int32的最大值）。如果没有足够充分的理由，请不要设置改最大值。相反，你应该确保该值至少比MaxArrayLength和MaxStringContentLength大一些。如果改值较小，那么改值将破坏ReaderQuotas属性规定的限制。&lt;/div&gt;&#xD;
&lt;div&gt;还有另外一个属性MaxBufferSize值得关注。当一条消息传输时，它在网络中以字节流传输。当接收到一条消息，流必须重新组建为消息。WCF运行时使用内存缓冲来完成该任务，内存所分配的最大缓存由该属性MaxBufferSize决定。在大多数情况下，MaxBufferSize与MaxReceivedMessageSize是相同的。但是，当你使用流模式后，MexBufferSize的大小将会明显减小。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;WCF服务中启用流模式传输大数据&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;MTOM对于编码消息中的大二进制数据对象是非常有用的，但是如果这些对象变得十分巨大，那么它们将消耗寄宿WCF服务的计算机和接收消息客户端的计算机上相当数量的内存。此外，创建和传输巨大的消息都需要花费较长的时间，而且客户端在等待包含大二进制对象的响应消息时，可能发生超时。&lt;/div&gt;&#xD;
&lt;div&gt;在多数情况下，尝试将数据打包到一条单独的消息中是没有意义的。假如一个WCF服务的操作输出音频和视频数据。在这种情况下，比起只用一个"大数据块（所有的数据都放在同一条消息的某一个标签中）"来传输数据，启动流模式发送和接收数据将更有效。流模式允许客户端程序在服务传输完整条消息之前就开始接收和处理数据，这样服务和客户端均不需要大缓存来容纳整条消息，同时还解决了超时的问题。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;在WCF服务和客户端程序中启动流模式&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;若要WCF的操作支持流，你只需要修改基于basicHttpBinding, netTcpBinding, netNamedPipeBinding绑定对应的绑定配置的TransferMode属性。你可以设置该属性的值为：&lt;/div&gt;&#xD;
&lt;ul&gt;&#xD;
&lt;li&gt;Buffered，这是TransferMode属性的默认值。在内存中整个消息创建完成后，然后开始传输。&lt;/li&gt;&#xD;
&lt;li&gt;StreamedRequest， 以流模式发送请求消息，以缓冲方式接受和返回响应。&lt;/li&gt;&#xD;
&lt;li&gt;StreamedResponse，以缓冲方式发送和接收请求，但以流模式返回响应。&lt;/li&gt;&#xD;
&lt;li&gt;Streamed，两个方向均使用流模式发送/接收请求/响应。&lt;/li&gt;&#xD;
&lt;/ul&gt;&#xD;
&lt;div&gt;启用流模式后，可接收消息的最大值仍由绑定的MaxReceivedMessageSize属性决定。但是，当你对绑定启动流模式后，仅仅消息头需要在接收方缓冲，因此你应该减少MaxBufferSize属性的值。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;设计操作以支持流模式&lt;span style="font-size: 15pt;"&gt;&#xD;
		&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;如果启用流模式，除了更改TransferMode属性的值之外，还有其他更多的工作，并且不是所有的操作都可以利用流模式。为了支持以流模式发送请求，一个操作仅仅能接收一个输入参数，而且该参数要么是一个流对象，要么是一个消息对象或者能够被序列化为XML的对象。为了支持以流模式返回响应，一个操作要么没有任何返回类型，要么通过一个输出参数返回；与输入参数一样，输出参数也必须是一个流对象或者能序列化为XML的对象。为什么有这个限制呢？这是因为输入参数（或者输出参数）必须可以构成一条完整的输入消息或者输出消息。&lt;/div&gt;&#xD;
&lt;div &gt;练习：在ShoppingCartPhotoService中实现流模式返回响应消息&lt;/div&gt;&#xD;
&lt;div&gt;1. 打开C:\...\MTOMService项目中App_Code文件夹下的IShoppingCartPhotoService.cs文件，添加如下的一个新版本的获取产品图片的操作，其用于支持流模式：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659183878.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;2. 打开App_Code文件夹中的服务实现类ShoppingCartPhotoService.cs文件，添加一个新的GetPhoto方法，其返回一个MemoryStream对象，该对象包含图片数据：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659195175.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;3. 使用WCF服务配置编辑器修改ShoppingCartPhotoServcie服务和ShoppingCartGUIClient程序的的绑定配置，修改两者的TransferMode为StreamResponse以使服务支持流模式返回响应消息：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659215566.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;请注意，ShoppingCartGUI程序的绑定设置也必须设置为StreamedResponse；否则，客户端的WCF运行时会在消息传递给客户端程序之前，把接收的响应消息缓冲起来。&lt;/div&gt;&#xD;
&lt;div&gt;4. 在ShoppingCartGUIClient中更新服务引用，然后修改getPhoto_Click方法：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659228814.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;5. 删除*\WCF\Step.by.Step\Solutions\Chapter13文件夹下的web_messages.svclog和web_tracelog.svclog文件。&lt;/div&gt;&#xD;
&lt;div&gt;6. 在非调适模式下运行解决方案，然后客户端产品编码处输入WB-H098，如果一切正常，客户端窗口将显示该产品的图片。&lt;/div&gt;&#xD;
&lt;div&gt;7. 关闭客户端程序，并停止ASP.NET开发Web服务器。&lt;/div&gt;&#xD;
&lt;div&gt;8. 启动WCF服务追踪查看工具，在左边面板中点击消息标签，然后点击第一个动作，在右上的面板中，选择第二条消息，然后在右下点击消息标签，你将看到该消息的消息体部分如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108251659238682.png" alt="" /&gt;&lt;span style="color: #17365d; font-size: 15pt;"&gt;&lt;strong&gt;&#xD;
			&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;启动流模式后需考虑的安全事项&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;消息层面的安全特性，比如签名和加密在ws2007HttpBinding绑定中广泛使用，并且它们要求WCF运行时可以访问整个消息。当你在绑定上启动流模式后，WCF运行时将不再可能访问整个消息。因为这个原因，ws2007HttpBinding和其他相关的绑定都不支持流模式。解决方法是使用在传输级别的实现安全basicHttpBinding绑定。&lt;/div&gt;&#xD;
&lt;div&gt;此外，你还不能运用可靠的消息传输。因为该特性依赖于缓冲，以使协议能知道整个消息的传递情况（如果在绑定配置中设置了排序，还会对消息进行排序）。同样地，这对于ws2007HttpBinding系列绑定又成为一个问题。这是因为TCP传输协议和命名管道自身提供了可靠的传递机制，它们独立于WCF实现的WS-ReliableMessaing协议。&lt;/div&gt;&#xD;
&lt;div&gt;安全事项的最后一个关注点，在默认情况下WCF创建的绑定允许接收最大为64Kb大小的消息。如果一条正在接收的消息超出了该限制，WCF运行时将抛出一个异常并终止操作。正如早期提到的，该限制主要是减少DOS攻击的范围。该值对大多数面向消息的操作已经足够，但对于多数支持流模式的场景而言该值太低。在这些场景下，你需要增加绑定配置中MaxReceivedMessageSize属性的值。但是，该属性是对应绑定的一个全局变量，因此它将会影响该绑定上暴露给客户端的所有操作；因此最好为不支持流模式的操作定义一个单独的服务合约。&lt;/div&gt;&#xD;
&lt;div&gt;更多关于大型数据和流的信息，请参考MSDN http://msdn.microsoft.com/zh-cn/library/ms733742.aspx。&lt;span style="color: #17365d; font-size: 15pt;"&gt;&lt;strong&gt;&#xD;
			&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2153521.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/08/25/2153521.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yang_sy/archive/2011/08/24/2151258.html</id><title type="text">WCF 4.0 进阶系列 – 第十三章以更好的性能实现WCF服务（上）</title><summary type="text">良好的性能是大多数应用程序和服务的关键因素，你可以通过周密地设计，以及选择合适的特性以确保WCF服务维持其吞吐量，保持响应并具有可扩展性。到目前为止，这些技术包括事务，session状态，可靠地消息传递，以及异步操作。还有其他一些影响性能的方面，比如安全。维持性能的一个重要的方面是需要确保服务不会耗尽宿主计算机上可用的资源，进而导致计系统变慢，甚至停止响应。WCF提供服务阀值以帮助控制资源的利用。使用该特性可以最大程度的维持服务的最大扩展性。负载均衡是另外一个技术你可以用来分发请求至多个服务器并保持相同的输出。你还可以基于Windows网络负载均衡和WindowsServer AppFabric（Windows云平台中间件）创建一个负载均衡的架构，尽管该技术的详细信息超出本书的讨论范围。</summary><published>2011-08-24T01:57:00Z</published><updated>2011-08-24T01:57:00Z</updated><author><name>On the road....</name><uri>http://www.cnblogs.com/yang_sy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yang_sy/archive/2011/08/24/2151258.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yang_sy/archive/2011/08/24/2151258.html"/><content type="html">&lt;div id="yangsy"&gt;&#xD;
&lt;p&gt;&lt;strong&gt;前言&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;良好的性能是大多数应用程序和服务的关键因素，你可以通过周密地设计，以及选择合适的特性以确保WCF服务维持其吞吐量，保持响应并具有可扩展性。到目前为止，这些技术包括事务（&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/06/29/2091996.html"&gt;上&lt;/a&gt;，&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/07/01/2093321.html"&gt;下&lt;/a&gt;），&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/06/15/2081249.html"&gt;session状态&lt;/a&gt;，&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/07/09/2101324.html"&gt;可靠地消息传递&lt;/a&gt;，以及异步操作（&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/08/09/2131301.html"&gt;上&lt;/a&gt;，&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/08/10/2133231.html"&gt;中&lt;/a&gt;，&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/08/22/2149674.html"&gt;下&lt;/a&gt;）。&lt;/div&gt;&#xD;
&lt;div&gt;还有其他一些影响性能的方面，比如安全（&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/05/24/2054834.html"&gt;企业内部WCF的安全&lt;/a&gt;，&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/05/30/2063706.html"&gt;因特网环境下WCF的安全&lt;/a&gt;）。如同我们在前面章节中讨论的那样，实现消息级别的安全性和安全地进行会话会导致复杂的消息交换，以协商其使用的协议以及交换身份信息；此外消息内容本身也变得比较大，这是因为额外的安全信息包含在消息的头部中&amp;mdash;这些信息导致消息在网络中传输时需要更长的时间和更多的内存去处理。加密和解密同样也是十分消耗资源的任务。但是，所有这些对于一个安全的系统都是必须的，因此多数人都牺牲性能以确保数据的隐秘性和身份信息的隐秘性。（如果解密非常迅速并且非常容易，那么执行加密就没有什么意义了。加密消息时消耗越多的资源，便可以更好地保护消息）&lt;/div&gt;&#xD;
&lt;div&gt;维持性能的一个重要的方面是需要确保服务不会耗尽宿主计算机上可用的资源，进而导致计系统变慢，甚至停止响应。WCF提供服务阀值以帮助控制资源的利用。使用该特性可以最大程度的维持服务的最大扩展性。负载均衡是另外一个技术你可以用来分发请求至多个服务器并保持相同的输出。第十四章"发现服务和路由消息"描述了通过创建一个特别的WCF服务以实现一个简单的负载均衡。你还可以基于Windows网络负载均衡和WindowsServer AppFabric（Windows云平台中间件）创建一个负载均衡的架构，尽管该技术的详细信息超出本书的讨论范围。&lt;/div&gt;&#xD;
&lt;div&gt;在传输数据时，使用合适的编码机制对提高性能亦有显著的影响。你已经了解到，WCF支持使用文本和二进制对消息进行编码。两者相比较，二进制经常更具有影响力并且占用较少的网络流量，但是二进制编码格式是与平台相关的，因此运行在非Windows平台上的服务和客户端程序不能轻易地使用它。但是，WCF还支持消息传输优化机制（MTOM），该机制为传输大块的二进制数据提供了一个标准的，互操作的，并且有效的格式。&lt;/div&gt;&#xD;
&lt;div&gt;如果你知道服务传输数据的大小，那么此时MTOM是非常有用的。有些服务发送未知大小的数据块，这种类型的数据最好使用流进行传输，WCF同样支持从服务输出流。&lt;/div&gt;&#xD;
&lt;div&gt;在本章中，你将了解如何使用服务阀值以维护服务的扩展性，如何使用MTOM编码数据以减少大二进制数据对象的系统开销，以及何如启动流以最大化利用网络带宽。&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;使用服务端的阀值控制资源使用&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;你可以使用服务阀值以禁止过度消耗WCF服务的资源。你可能记得在第十一章"&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/08/01/2116492.html"&gt;编写代码控制配置和通信&lt;/a&gt;"中，当一条消息由服务的宿主接收到后，该消息被传送至通道堆栈的顶部，然后它把消息传送至一个ChannelDispatcher对象，该对象将消息传递至对于的EndpointDispatcher对象，该对象接着调用服务实例中对应的方法。然而，在传送请求至EndpointDispatcher对象之前，ChannelDispatcher对象可以检查服务当前的负载，如果该请求会导致服务超出许可的负载那么选择延迟该请求。在这种情况下，请求将被阻塞并且保存在内部的队列中直到服务的负载得以释放。ChannelDispacher对象有一个名为ServiceThrottle的属性，你可以使用该属性来帮助ChannelDispatcher对象决定阻塞并将请求加入到等待队列或者将请求直接传送至服务实例并让其执行。ServiceThrottle属性是ServiceThrottle类的一个实例变量，该实例变量本身对外暴露三个属性：&lt;/div&gt;&#xD;
&lt;ul&gt;&#xD;
&lt;li&gt;MaxConcurrentInstances, 该属性设定并发服务实例的最大数量。&lt;/li&gt;&#xD;
&lt;li&gt;MaxConcurrentCalls, 该属性设定服务可以处理的并发消息的最大数量。如果客户端程序产生了大量并发调用，无论使用单向调用操作或在客户端使用多线程，都会导致其迅速耗用服务资源。在这种情况下，你可能希望限制每个客户端均访问服务的单个线程实例，这可以通过设置ConcurrencyMode.Single属性为True来完成。这样客户端程序继续异步地执行，而且服务还可以响应其他用户；但是客户端提交的请求将被服务以各种方式处理。&lt;/li&gt;&#xD;
&lt;li&gt;MaxConcurrentSessions, 该属性设定并发会话的最大数量。客户端程序负责建立和终止会话，并在在会话中调用服务的操作。创建长时间运行会话的客户端可能会导致其他客户端被阻塞，因此尽量保持短暂的会话，而且会话应该避免执行等待用户输入这样的任务。&lt;/li&gt;&#xD;
&lt;/ul&gt;&#xD;
&lt;p&gt;&lt;strong&gt;配置服务阀值&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;默认情况下，ChannelDispatcher对象的ServiceThrottle属性为null；并且WCF运行时使用最大并发实例、方法调用、服务会话的默认值。为了控制扩展性，你应该设置WCF运行时创建一个ServiceThrottle对象并显示地设定这些属性以适合你环境，此外还应考虑到并发客户端数量以及客户端程序可能执行的任务数量。你可以通过代码来创建一个ServiceThrottle对象，然后设置该对象的属性，最后添加该对象到ServiceHost对象的行为集合中，以完成该项任务。请注意，你必须在打开ServiceHost对象之前完成上述任务。下面的例子展示了如果执行该任务：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211177931.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;但是，应该警告你的地方是ServiceThrottle对象的属性可能会对服务的响应时间和服务的带来剧烈的影响；因此你应该实时监控WCF服务的性能，如果寄宿服务的计算机性能下降，立即更改之前的设置。此外，如果这些属性的值设置的过低，那么会导致大量客户端被阻塞甚至发生超时或者在客户端程序中发生其他错误，也有可能在客户端堆栈处发生其他错误；因此你需要设置捕获异常的代码并处理这些异常。&lt;/div&gt;&#xD;
&lt;div&gt;因为你可能需要方便地更改ServiceThrottle对象的值，一个较为复杂地创建ServiceThrottle对象并设置该对象属性的方式是添加一个包含&amp;lt;serviceThrottle&amp;gt;元素的服务行为到服务配置文件中。我们将在下面的练习中使用这种方式。此外，在练习中，你还需要修改服务宿主程序以显示当前的服务阀值信息。&lt;/div&gt;&#xD;
&lt;div &gt;练习：在 ShoppingCartService服务中应用服务阀值&lt;/div&gt;&#xD;
&lt;div&gt;1. 使用Visual Studio打开位于*\WCF\Step.by.Step\Solutions\Chapter13\ServiceThrottle目录下的ShoppingCart.sln解决方案。&lt;/div&gt;&#xD;
&lt;div&gt;该解决方案包含一个简单的非事务版本的ShoppingCartService服务，该服务并不更新数据库。该服务还包含一个扩展版本的客户端程序，其与服务之间建立多个并发的会话。&lt;/div&gt;&#xD;
&lt;div&gt;2. 打开ShoppingCartService项目下的IShoppingCartService.cs文件。请注意该服务通过ServiceContract特性类为接口IShoppingCartService指定SessionMode为SessionMode.Required。打开ShoppingCartService.cs文件，你可以看到服务实现类的服务实例模式为PerSession。&lt;/div&gt;&#xD;
&lt;div&gt;3. 检查ShoppingCartService.cs文件的AddItemToCart方法，该方法首先调用WriteLine语句在屏幕上显示方法的名称。在该方法中每个可能终止方法的地方都添加了一个对应的WriteLine语句。你将使用这些语句追踪每个服务实例运行的过程。你还应该注意到该方法在第一个WriteLine语句后调用 System.Threading.Thread.Sleep(10000)以使当前线程停止10秒钟，以模拟执行数据库更新的操作。这么做的目的也方便了观察服务阀值参数带来的效果。另外的public方法RemoveItemFromCart，GetShoppingCart和Checkout都按照上述方式实现。&lt;/div&gt;&#xD;
&lt;div&gt;4. 打开ShoppingCartClient项目下的Programm.cs文件，然后找到doClientWork方法。该方法创建一个新的代理对象实例，然后通过代理对象实例调用ShoppingCartService服务的各种操作。该方法包含多个WriteLine语句，它们用以在控制台窗口中显示方法处理的进度。WirteLine输出指明当前连接到服务的客户端数。此外，客户端使用标准的TCP绑定连接至服务。&lt;/div&gt;&#xD;
&lt;div&gt;5. 再来检查Programm.cs的main方法。该方法使用Parallel.For构造方法，该方法10次异步地调用doClientWork方法。每次调用都创建一个并行的任务。它模拟10个不同的但是具有相同身份的客户端在同一时间连接至服务。&lt;/div&gt;&#xD;
&lt;div&gt;6. 打开ShoppingCartHost项目的Programm.cs文件，该程序寄宿ShoppingCart服务。在该文件的头部添加下面的引用。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211186404.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;7. 添加下面的代码至Main方法&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211203664.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上述代码获取服务ChannelDispatcher对象的引用（在本例中，服务仅仅只是有一个绑定，因此当宿主程序启动服务运行时，WCF运行时仅仅创建单个ChannelDispatcher对象）。然后，代码检查ChannelDispatcher对象的ServiceThrottle属性，如果其为null，那么表明管理员或者开发人员没有指定任何自定义的服务阀值，因此使用默认的服务阀值。如果ServiceThrottle属性不为null，那么服务使用自定义的服务阀值行为，控制台窗口将显示管理员或开发人员所设定的服务阀值信息。&lt;/div&gt;&#xD;
&lt;div&gt;8. 在非调适模式下，运行解决方案。在服务控制台窗口中，你将发现服务当前使用默认的服务阀值行为。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211217153.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;在客户端控制台窗口中按ENTER键后，控制台窗口将显示如下图所示结果&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211241905.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;此时，在服务宿主控制台窗口，你应该会当每个客户端发送一个AddItemToCart的请求后看到"AddItemToCart operation started"出现：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211276035.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;这就意味着服务同时处理多个客户端的请求。当每个方法完成后，服务宿主控制台窗口将显示"AddItemToCart Operation completed"，然后客户端将调用其他的操作。在此时，控制台输出会变得有点混乱，但最重要的一点是还没有突破服务阀值，因为服务不会拒绝10个客户端中任何一个调用操作（默认的最大并发调用数为10）。下图显示了所有操作执行完成后的界面。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211285870.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当消息"Tests complete：Press ENTER to finish"处客户端控制台窗口中出现，按下ENTER键以关闭客户端程序控制台窗口，然后在服务宿主程序中按下ENTER键停止服务。&lt;/div&gt;&#xD;
&lt;div&gt;上述练习使用服务阀值的默认值，接下来，你将自定义服务阀值。&lt;/div&gt;&#xD;
&lt;div&gt;9. 在Visual Studio中，使用WCF服务配置编辑器打开ShoppingCartHost项目的App.config文件&lt;/div&gt;&#xD;
&lt;div&gt;10. 在控制面板中，展开高级文件夹，然后选择服务行为。然后在右边面板中，点击创建新的服务行为配置。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211329934.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;11. 在右边面板中，更改新行为的名字为ThrottleBeahavior。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211344163.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;12. 在右下面板中，点击添加按钮添加serviceThrottlig元素之刚创建的服务行为ThrottleBahavior&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211447774.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;13. 在配置面板，点击serviceThrottling行为元素。该元素的三个属性机器默认值将在右边面板中显示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211486789.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;14. 更改MaxConcurrentCalls值为3.&lt;/div&gt;&#xD;
&lt;div&gt;15. 在配置面板，点击ShoppingCartService.ShoppingCartServiceImple服务，然后在右边面板中，设置BehaviorConfiguration的属性为ThrottleBehavior&lt;/div&gt;&#xD;
&lt;div&gt;16. 保存配置文件；&lt;/div&gt;&#xD;
&lt;div&gt;17. 在Visual Studio中，在非调适模式下运行解决方案。在服务技术控制台窗口中，你将看到现在服务使用你指定的阀值行为。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211511508.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;你可能会非常惊讶在服务寄宿控制台窗口中显示的值。在服务配置编辑器中，为serviceThrottling元素的属性生成的值并不是服务寄宿程序所使用的实际值，除非你修改它们。在WCF 4.0中，默认值是基于宿主计算机的可用资源决定的。举例来说，一台电脑使用单核CPU，那么最大的并发实例数为116，最大的的并发调用数为16， 最大的并发会话数为100.&lt;/div&gt;&#xD;
&lt;div&gt;在一个双核的电脑上，你将发现这些默认值变为单核默认值的两倍&amp;mdash;但serviceThrottle默认值将仍然为第十三步中显示的值。此外，serviceTrhottling三个属性的值之间存在一个关系，那就是MaxConcurrentInstances = MaxConcurrentCalls + MaxConcurrentSessions.这就意味着如果你仅仅更改MaxConcurrentClass属性的值，而不修改另外两个属性的值，那么WCF运行时将自动为其他两个属性生成值。请注意，我目前使用的电脑是四核的CPU，因此最大并发会话数为100&amp;times;4=400；所以最大并发实例为400+3 = 403.&lt;/div&gt;&#xD;
&lt;div&gt;当然，你还可以修改MaxConcurrentCalls和MaxConcurrentSession属性的值，那么WCF运行时将不会再计算这两个属性的值，而是直接使用你指定的值。&lt;/div&gt;&#xD;
&lt;div&gt;18. 在客户端程序控制台中按下ENTER键。&lt;/div&gt;&#xD;
&lt;div&gt;在客户端控制台窗口中，所有10个客户端输出"Clinet n：1&lt;sup&gt;st&lt;/sup&gt; AddItemToCart"，但是服务宿主控制台与之前显示的不一样；最开始，仅仅显示3条"AddItemToCart operation started"消息。这是因为服务现在仅仅支持3个并发操作调用。ChannelDispatcher将后续的请求放入等待队列中。当每个请求完成，会显示"AddItemToCart operation completed"消息，然后ChannelDispatcher从等待队列中释放下一个请求，你将看到消息"AddItemToCart operation started"。此后，每次操作完成，ChannelDispatcher都释放下一个请求。你将看到"Completed"和"Started"消息交替显示，直到所有的客户端完成了它们的工作。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211564360.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当客户端程序结束，按下ENTER键关闭客户端程序控制台窗口，然后在服务宿主控制台窗口按ENTER键停止服务。&lt;/div&gt;&#xD;
&lt;div&gt;19. 返回到WCF服务配置编辑器，在左边面板中点击serviceThrottling服务行为元素。然后在右边面板中，修改MaxConcurrentCalls属性值为16，并设置MaxConcurrentSessions属性为3。然后保存配置文件并退出WCF服务配置编辑器。&lt;/div&gt;&#xD;
&lt;div&gt;20. 回到Visual Studio，在为调适模式下运行解决方案。在服务宿主控制台窗口中，你将看到服务现在使用阀值的最大并发实例数为19. 请注意这里为什么是19；因为你如果手动修改MaxConcurrentCalls的值为16，即使该属性的默认值也为16.但是你的修改的背后，会在配置文件中产生如下的结果：（也就是系统认为你手动设置了最大并发调用数为16）&lt;br /&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211585242.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;根据MaxConcurrentInstances = MaxConcurrentCalls + MaxConcurrentSessions；所以控制台窗口显示19。如果你删除maxConcurrentCalls="16"；然后再运行方案，你会发现此时maxConcurrentCalls不再为19了；如果你是双核电脑，那么现在maxConcurrentCalls的值应该为16*2+3=35；如果你使用4核电脑，那么maxConcurrentCalls的值为16*4+3=67。&lt;/div&gt;&#xD;
&lt;div&gt;在客户端控制台窗口中，按下ENTER键。&lt;/div&gt;&#xD;
&lt;div&gt;同样，在客户端窗口中，所有10个客户端都输出消息"Client n: 1&lt;sup&gt;st&lt;/sup&gt; AddItemToCart"；服务控制台窗口显示3个AddItemToCart操作开始和操作结束。但是，当这些操作结束完成后，如果你查看客户端控制台窗口的消息，你你将会发现仅仅第一批客户端（先被服务处理的三个客户端）可以第二次调用AddItemToCart操作。其他的客户端由ChannelDispacther管理并处于pending状态，这是因为服务已经达到了所允许的最大会话数。第一批客户端（也就是头三个客户端）完成整套工作，第三次调用AddItemToCart，随后执行GetShoppingCart操作，最后执行Checkout操作。只有当Checkout完成而且客户端在关闭自己的会话后，下一个客户端才允许继续。你应该在客户端中看到消息始终以三条为循环的方式出现。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232211599603.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;之后的一些会话将在客户端控制台窗口中显示如下的异常消息：&lt;br /&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/20110823221201451.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;这是因为它们提交AddItemToCart之后，一直处于等待状态；在轮到它们被服务处理前，已经发生超时了。&lt;/div&gt;&#xD;
&lt;div&gt;当测试结束，按下ENTER键关闭客户端程序控制台窗口，然后在服务宿主控制台窗口按ENTER键停止服务。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;上述练习演示了使用服务阀值控制服务所允许的最大并发调用和最大并发会话的效果。很不幸，该练习并不能告诉你应该为你的服务设定具体的服务阀值。你需要在真实负载环境中测试你的服务，并观察客户端程序是否被阻塞了相当长的时间。请记住服务阀值的目的是为了阻止服务因大量的请求造成"洪水泛滥"&amp;mdash;因为服务没有足够的资源处理如此大量的请求。你应该设置服务阀值的属性以确保当客户端请求被接受后，便开始处理该请求，而且寄宿服务的计算机在客户端发生超时之前拥有足够的可用资源来完成操作。否则，你的更改会进一步妨碍整个服务的性能。请注意如果服务支持事务，失败的客户端要求服务执行更多的工作，因为当超时发生，服务必须回滚之前已经执行的事务。&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;WCF和服务实例池&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;WCF运行时创建服务实例以处理客户端请求。如果服务使用PerSession实例模式，那么服务实例可以跨越多个操作而存在。如果服务使用PerCall实例模式，每个操作都将产生一个新的服务实例，当操作完成时该服务实例将被丢弃并销毁。创建和销毁服务实例不仅非常消耗资源的，而且还可能耗费较长时间。在这种场景下，服务实例池非常有用。&lt;/div&gt;&#xD;
&lt;div&gt;当使用服务实例池，WCF运行时在服务启动时创建一个服务实例对象池。如果服务使用PerCall实例模式，当客户端程序访问调用操作，WCF运行时将从池中获取一个预先创建的服务实例，并在操作完成后将该服务实例放回池中。如果使用PerSession实例模式时，从语义来讲工作模式与PerCall是相同的，但是WCF运行时在会话开始时才从池中获取一个服务实例，在会话结束时将该服务实例放回池中。处于安全目的，服务实例持有的所有数据（服务实现类中的成员变量）都将在返回池中时全部清除。&lt;/div&gt;&#xD;
&lt;div&gt;实际上，WCF并不直接提供服务实例池，但是可以通过定义自定义行为扩展WCF从而实现服务实例池。WCF通过System.ServiceModel.Dispatcher命名空间提供IInstanceProvider接口，你可以使用该接口来定义自定义服务实例分发机制。这是一个非常有用的技术，但是其详细信息超出本书讨论的范畴。更多关于服务实例池的内容，请参考Visual Studio帮助文档的服务实例池主题。该主题同样可以在MSDN查阅到：http://msdn.microsoft.com/en-gb/library/ms751482.aspx&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;指定内存需求&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div&gt;应用服务阀值行为允许你限制会话的数量和连接的数量以使服务维持其吞吐量。但是，服务是运行在电脑上的应用程序，并且服务执行耗费资源的操作，因此有必要确保其在开始运行时拥有足够的可用资源。&lt;/div&gt;&#xD;
&lt;div&gt;一个常见并且紧缺的资源就是内存。正因为这个原因，WCF运行时允许你指定服务在激活前可用内存的最小值。你可以在服务配置文件中，通过&amp;lt;serviceHostingEnvironment&amp;gt;元素下的minFreeMemoryPercentageToActivateService特性来定制该值。其默认值为5，在本例中我们将其修改为10.&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/201108232212026449.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;在这种情况下，当WCF运行时尝试激活服务时，如果可用的总内存小于总内存的10%，那么WCF运行时将失败并抛出ServiceActivationException异常。&lt;/div&gt;&#xD;
&lt;div&gt;当然，你还可以通过WCF服务配置编辑器来设定该参数的值。在配置面板中，展开高级文件夹，然后点击宿主环境。在宿主环境面板，指定所需内存的最小数目。如下图所示：&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/yang_sy/201108/20110823221209182.png" alt="" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yang_sy/aggbug/2151258.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yang_sy/archive/2011/08/24/2151258.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
