精华内容
下载资源
问答
  • 在第章WCF简介中,你已经了解SOA的基本原则--服务之间共享数据架构和协定,而并非类或者类型。当你定义服务时,你通过定义服务协定以指定操作。服务协定描述该服务的操作、操作所使用的参数类型及其操作返回值的...

    【摘要】

    在第一章WCF简介中,你已经了解SOA的基本原则--服务之间共享数据架构和协定,而并非类或者类型。当你定义服务时,你通过定义服务协定以指定操作。服务协定描述该服务的操作、操作所使用的参数类型及其操作返回值的类型。WCF服务对外公布服务协定的定义,服务开发人员使用这些定义去构建相应的客户端。开发人员可以通过Visual Sutdio自带的添加服务向导或者svcutil实用工具为根据服务的WSDL描述生成一个客户端代理类,客户端使用该代理类与进行服务通信。

    服务协定仅仅是整个故事的一部分。服务协定中的操作可以使用参数并且操作可以有返回值。客户端程序必须提供服务所要求格式的数据。在.NET Framwork中,主要数据类型都有预先定义的格式;而类,结构,枚举等类型则拥有比较复杂的格式;这些复杂格式的数据类型要求客户端程序在想服务发送消息时,先将这些复杂数据打包然后才发送。同样地,服务端也需要格式化这些复杂数据,然后才发送给客户端。你可以使用数据协定来封装这些复杂格式的数据类型;服务所使用的复杂数据类型都应有一个对应的数据协定。服务将数据协定和服务协定一起对外公布,那么这些复杂格式的数据类型将包含在通过svcutil实用工具或visual studio添加服务向导所生产的客户端代理类中。

    服务协定和数据协定是WCF服务非常基础的部分;如果客户端不能识别服务对外公布的操作或者服务所使用的数据类型,那么该客户端在与服务进行通信时将遇到很多麻烦。

    【目录】

    更改服务协定

       选择性地保护操作

      服务的版本

      对服务协定实施中断行更改和非中断性更改

    更改数据协定

      数据协定特性和数据成员特性

      数据协定的兼容性

    【正文】

    修改服务协定

    服务协定就是一个接口,WCF工具和架构能将该协定转换成一个WSDL文档,在该文档中列出一个服务的所有操作(一系列SOAP消息和消息响应)。你在服务实现类中实现这些接口中的方法。当WCF服务开始运行时,WCF运行时使用服务配置文件中的绑定创建一个通道堆栈,然后开始侦听来自客户端的消息请求。WCF运行时将来自WCF客户端的消息转换成方法的调用并且调用服务实现类实例中对应的方法。该方法返回的数据将转换成一个SOAP消息然后回传给通道堆栈,最后通道堆栈传将该消息传输至客户端。

    从上述过程,可以得出两个结论:

    (1). 服务协定独立于服务端的通信。根据服务配置文件中绑定而创建的通道堆栈管理通信机制。这就意味着,即使变更了通信传输协议或者服务的地址,也不需修改服务或者访问该服务客户端程序的代码。更进一步讲,服务的安全方面也独立于服务协定。

    (2). 与服务通信的客户端程序必须构建与服务向适应的SOAP消息。这些消息依赖于服务协定。如果服务协定变更,客户端必须也提供新版本的SOAP消息。否则,服务端将不能识别客户端发送的消息或者客户端想服务发送格式不正确的SOAP消息。此外,如果服务返回的消息发生变更,客户端程序很可能也不能处理这些返回消息。

    你可以从本章后续的练习中来验证上述两点结论。

    可选择性的保护操作

    在第四章和第五章讲述了如何保护客户端和服务端传送的消息。但是,你所使用的技术侧重于通过绑定和服务的行为来实现整个服务的安全(即一个服务的所有操使用同一个安全设置)。而通过修改服务协定,你可以在同一个服务中根据不同的安全需求对各个操作设定对应的安全性。

    根据安全需求设定WCF服务的操作的安全性

    1. 使用Visual Studio打开*\Chapter6\ProductsService文件夹下的ProductsServcie方案。该方案复制了第四章的ProductsServcie解决方案。该解决方案包含了ProductsServiceLibrary项目、ProductsServcieHost项目和ProductsClient项目;在本章中,该服务将使用非SSL端点,并且该端点WS2007HttpBinding绑定。WS2007HttpBinding绑定默认实现了消息安全并且使用Windows token来验证客户端用户。

    2. 打开ProductsServiceLibrary项目下的IProducts.cs文件;添加下面的using语句

    usingSystem.Net.Security;

    3. 修改ListProducts和GetProduct方法

    OperationContract特性类的ProtectLevel属性用于指定如何保护调用该操作的消息(即规定了客户端发送至该操作消息的安全特性)。在本例中,EncryptAndSign指定调用的ListProducts和GetProduct操作的消息必须由客户端签名、并使用与服务协商的密钥加密。这就要求服务端和客户端采用的绑定必须设置其安全模式为消息安全验证,此外客户端和服务端还必须为AlgorithmSuite属性指定同样的值。 实际上,上述设置是WS2007HttpBinding绑定使用消息安全时的默认设置。此外,ProtectLevel还可能有下列值:

    请注意ProtectLevel属性的默认值还取决于端点所使用的传输协议。如果使用BasicHttpBinding,那么默认的值将为None。

    4. 修改CurrentStockLevel和ChangeStockLevel的OperationContract特性

    5. 使用WCF服务配置工具打开ProductsServiceHost的app.config文件

    6. 点击"Disgnostics",确认右边面板中的MessageLogging的状态为On

    7. 点击"Listeners—Messagelog", 修改InitData的路径为"*\Chapter6\ProductsService"

    8. 点击"Diagnostics—Message logging",在右边面板中设置LogMessageAtServiceLevel属性的值为false,确认LogMessageAtransportLevel属性的值为true

    9. 保存配置文件,并退出WCF服务配置工具。

    测试修改后的服务

    1. 运行ProductsServcieHost,点击"开始"按钮启动ProductsServcie服务

    2. 启动ProductsClient,你将得到如下结果:

    Test1和test2都成功完成,因为绑定实现了加密和签名,这符合定义在ListProducts和GetProduct方法上的操作协定。但是test3抛出了一个异常:"The primary signature must be encrypted",因为CurrentStockLevel操作在操作协定中设定了保护级别为签名,但是客户端的绑定根据默认的设置提供了加密和签名。产生该问题的原因就是你更新了服务协定,但是没有更细客户端代码中相应的代码。所以客户端的代理在test3中仍旧向服务发送加密和签名后的消息。

    3. 退出ProductsClient

    4. 在Visual Studio中,修改ProductsClient项目下的Products.cs文件

    5. 删除位于*\Chapter6\productsService文件夹下的ProductsService.svclog

    6. 打开ProductsServcieHost,然后点击"开始"按钮启动ProductsServcie

    7. 启动ProductsClient,然后你会得到如下结果:

    8. 关闭ProductsClient

    9. 启动"Service trace viewer",并打开位于*\Chapter6\productsService文件夹下的ProductsService.svclog

    10. 在服务追踪查看器中,点击左边面板中的"消息"标签。你可以看到六条消息关于客户端和服务之间的协商加密的密钥,它们的Action都使用http://docs.oasis-open.org命名空间。 在这六条消息之后,是8条服务端和客户端收发的消息,它们的Action都是使用http://tempuri.org命名空间。

    11. 选中http://tempuri.org/IProductsService/ListProducts, 在后边面板中,点击"Formatted"标签,并滚动到内容的底端,你可以看到"Envelope Information section"的内容。你可看到Method对应的值为e:EncryptedData;由此可见,客服端发送给服务的操作名已经加密

    11. 选中http://tempuri.org/IProductsService/ListProductsResponse, 在右下面板中,确认服务返回的消息也已经加密。确认http://tempuri.org/IProductsService/GetProduct 和ttp://tempuri.org/IProductsService/GetProductResponse的消息也被加密

    12. 选中http://tempuri.org/IProductsService/CurrentStockLevel,同样切换到右下的"Formatted"标签下,你可以发现客户端向服务发送的操作名为及该操作的参数都没加密。

    13. 检查http://tempuri.org/IProductsService/CurrentStockLevelResponse、http://tempuri.org/IProductsService/ChangeStockLevel以及http://tempuri.org/IProductsService/ChangeStockLevelResponse;确认它们都操作的名字和参数或返回结果没有被加密。

    14. 关闭ProductsService.svclog;并退出Service trace viewer

    服务版本管理

    更改时刻发生。一个被广泛使用的服务几乎不可避免的随着业务过程的变化而演化。那么发生的更改可能有哪些方式呢?

    • 在大多数情况下,通过服务实现类中修改代码;
    • 有时候,通过修改服务操作的定义;
    • 当然,可以通过添加新的服务操作;
    • 或者通过删除一些不再使用的或重复的操作;
    • 或者变更一些现有操作的参数类型或返回类型。

    显然,这些更改都会导致更新服务协定。然后,客户端通过服务协定指定向服务收发的消息。如果服务协定变更,而客户端仍使用之前版本的服务协定,那么将导致什么结果? 是客户端能继续工作,还是需要检查每个客户端并更新客户端的代码呢? 你是否知道每个客户端的具体位置? 因为客户端可能通过因特网连接到服务,所以这些客户端可能分布在世界上的任何地方。 你可以看到,修改一个服务并不是话费稍许的气力就可以实现的。从上述情况来看,你需要采取多个步骤以确保现有的客户端即使没有更新仍能继续工作。 下面的练习展示了一些常见的场景,可以帮助理解当服务或服务协定变更时实际发生了什么;以及为减少服务变更带来的负面影响所应遵循的策略。

    为WCF服务添加一个方法和修改方法的业务逻辑

    1. 使用Visual Studio打开ProductsServiceVersion方案下ProductsServcieLibrary项目中的ProductsServcie.cs文件

    2. 添加下面方法(检查一个产品是否存在)到服务实现类中

    3. 修改GetProduct方法的业务逻辑

    4. 修改CurrentStockLevel的业务逻辑

    5. 修改ChangeStockLevel的业务逻辑

    6. 重新生成项目ProductsServiceLibrary;然后打开ProductsServiceHost,点击"开始"运行ProductsServcie;并打开ProductClient,所有的操作都可以成功执行。

    7. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    8. 在Visual Studio中,按照下图中红框中的内容修改program.cs文件中的test2;

    9. 再次启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。你将得到如下结果:

    10. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    11. 再次编辑program.cs文件;修改test2的方法使用一个存在的productnumber

    从上面的练习我们可以看到,尽管服务发生变更,并且为该服务添加了一个新方法。但是客户端的服务协定没有更新;因此客户端程序不能访问ProductExist方法;客户端仍旧与使用之前一样的方式访问服务。上述变更是一个非中断性服务变更。

    服务协定中现有的操作添加一个参数

    1. 打开ProductsServcieLibrary项目下的IProductsServcie.cs

    2. 添加一个参数 到ListProducts操作

    3. 打开ProductsServcieLibrary项目下的ProductsServcie.cs,修改ListProducts方法:有两处修改,第一处为ListProducts方法添加参数;第二处为修改LINQ方法,使其根据参数过滤返回的产品编码列表。

    4. 重新生成解决方案后,启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试都将通过,而且ListProducts将返回所有的产品。

    从上图可以看到,test1方法,也就是ListProducts并未返回任何值。因为客户端仍然使用没有参数的ListProducts方法向服务发送消息;当宿主WCF服务的WCF运行时接收到该消息后,反序列化消息,然后发现该消息的body部分没有包含参数数据,然后WCF运行时向服务传输一个null的参数值。服务的ListProducts方法将相应地返回一个空的列表。

    5. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    你可能非常奇怪上面练习的结果,你发现你可以对一个操作添加一个参数(或从一个操作移除一个参数),现有的客户端仍然可以调用该操作。如果你对一个操作添加一个参数,并且客户端程序没有为该操作提供参数,那么WCF运行时根据参数的数据类型为其提供该数据类型的默认值:引用类型为null,值类型为0;Boolean为false。请注意,即使你在定义操作时,设定了参数的值;但当客户端调用该操作时如果没有提供参数,那么这些默认值(null,0和false)仍然传至所调用的方法,并且覆盖你在服务中设定的参数值;比如你按照下列方式定义ListProducts 

    如果客户端调用该方法时没有提供参数值,那么WCF运行时将为ListProducts方法的参数设置为null。

    同样地,如果你在客户端变更了参数的类型,那么当该参数传递值寄宿WCF服务的WCF运行时后,WCF运行时将尝试类型转化。如果转化失败,那么将抛出异常。

    添加、移除和修改服务协定中操作参数是不值得推荐地,如果发生上述情况,你应该格外小心这些变更导致的结果。比如,如果移除某个操作包含的多个参数中间某一个参数,那么由客户端传送至服务端的参数在反序列化时可能会反序列化为错误参数。

    如果你变更一个操作的返回类型。当服务返回的类型不能转化成客户端程序希望的类型,那么客户端程序处理消息所使用的格式化工具将抛出一个异常。

    一般地,你应当避免修改操作的参数类型或者参数个数。相反地,在你修改服务协定后,保证新客户端工作的同时,应确保与现有客户端的兼容性。 实际上,你应该定义一个新版本的服务协定,并且保存之前版本的服务协定。在下面的练习中你完成上述目的。

    为WCF服务添加一个新操作

    1. 打开ProductsServcieLibrary项目下的IProductsServcie.cs

    2. 在接口IProductsService中,从ListProducts方法中移除match参数,并添加另外一个版本的ListProducts方法。

    请注意,虽然C#支持方法重载,但是SOAP标准不允许一个服务对外公布多个具有相同名字的操作。但是这里有另外一种方法,可以让你在使用重载的同时还支持SOAP;那么就是在相同方法名字上设置OperationContract特性的Name属性值以在SOAP消息中生成不同的操作名。比如

    3. 打开ProductsService.cs,做如下修改:

    4. 重新生成解决方案后,启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;并且ListProducts将返回所有的产品。

    5. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    为服务协定所添加新方法并是一个非中断变更。如果你构建一个新的WCF客户端程序,你可以使用svcutil施工工具生成一个包含该新操作的代理类。现有的客户端程序使用老版本代理类仍然可以和服务端通信,而且老版本的代理类不关心新操作的存在。

    但是,如果你希望新的代理类仅仅使用新的操作(ListMatchedProducts)而不能使用旧的操作(ListProducts)时,将带来潜在的问题。该如何对新版本的客户端程序隐藏旧的操作呢?答案就是使用多重服务协定。保持现有的服务协定不做任何变更,新版本的服务协定仅仅包含新操作。如下表所示:

    老版本服务协定

    新版本服务协定

    上面的代码展示了使用ServiceOperation特性类的命名空间属性和Name属性来区分和命名不同的服务版本。默认情况下,服务协定使用http://tempuri.org命名空间,使用接口的名字来命名来服务协定。当你定义一个新版本的服务协定时,可以通过命名空间属性值来区分不同的服务协定,但仍保持相同的Name属性。 请注意,设置命名空间属性和Name属性后,将导致一个中断性变更,因为这些信息用以识别服务和客户端之间收发的SOAP消息。

    如果你定义了两个不同的版本的服务,那么此时你的服务实现类需要同时实现这两个接口。

    最后,你需要在服务端创建一个使用新版本服务的服务端点,并且该端点使用ProductsServcie.IProductsServcieV2服务协定。

    对服务协定创建中断和非中断性变更

    严格地讲,你应该考虑服务的稳定性。服务协定的任何变化都很可能影响到客户端程序,导致客户端不再能继续与服务进行正常通信。在实际中,你可以对服务协定做一些变更,且不中断现有的客户端程序通过之前的服务协定连接到该服务。下表列举了一些开发人员经常对服务协定所作的变更和这些变更对客户端程序所带来的影响

    变更

    影响

    添加新操作

    非中断变更。现有客户端不受影响,但是使用老版本代理类的客户端程序看不到新操作。但是使用代码创建查询和消息的客户端可以使用新的操作,详细内容参考第十一章

    移除一个操作

    中断变更。现有客户端将不能继续正常工作;尽管不调用该操作客户端不受影响

    变更操作名

    中断变更。现有客户端将不能继续正常工作;尽管不调用该操作客户端不受影响。请注意,操作名默认对应服务协定中的方法名。你可以变更服务协定中的方法名;但应保留操作名。比如你可以通过下列方式来完成:

    这是一个不错的方法,因为它移除了服务协定和实现该操作的名字之间的依赖关系。

    修改操作的保护级别

    中断变更。现有客户端将不能再调用该操作

    对操作添加新参数

    非中断操作。现有客户端可以继续调用该操作,但是WCF运行时将为根据新参数的类型为新参数提供其类型对应的默认值

    变更操作参数的顺序

    中断操作。结果不易预料(某些现有客户端还能继续保持工作)

    移除操作的一个参数

    可能是非中断操作,只要被移除的参数是该操作的最后一个参数。在这种情况下,现有客户端传递过来的多余参数将被忽略。如果被移除的参数为第一个或中间的参数,那么将导致一个中断操作,引用于变更参数的顺序相同

    变更操作参数的类型
    或变更返回值类型

    可能是中断操作,如果WCF格式化工具不能将之前的数据类型转化成新的数据类型。现有客户端可能继续工作,但是将导致SOAP消息中数据丢失或者错误地转化的风险。该变更还会影响到应用或移除ref和out参数的服务协定,及时ref和out的参数类型没有发生变更。更多消息请参考本章后续内容"修改数据协定"

    添加FaultOperation特性

    中断变更。现有客户端程序能发送fault消息,但是这些消息不能正确的理解

    移除FaultOperation特性

    非中断变更。现有客户端将可以继续正常工作,尽管指定的异常处理被认为过期

    变更服务协定的命名空间属性和Name属性

    中断变更。使用之前的Name或者命名空间现有客户端程序将不能再向服务发送消息。

    如果你对服务协定创建了一个中断性变更,你必须更新所有访问该服务的客户端程序。如果客户端程序使用代理,你需要更新这些代理。推荐的修改服务协定的方式是:创建一个新版本的服务协定,并同时保持旧版本的服务协定。这种方式你不需要更新现有的客户端程序,尽管它们不使用服务的新操作。

    修改数据协定

    服务协定中的方法能使用参数,并且返回值;这些参数和返回值包含在SOAP消息中并在服务和客户端之间传输。SOAP消息将这些数据编码成XML文本。WCF运行时使用内建的XML序列化器将这些XML文本序列化和反序列化为.NET Framework的主要数据类型,比如整数,数字,或者字符串。对于更复杂的接口类型,服务必须指定确切的序列化格式;可以用多种方式将同一个结构化数据描述成不同XML。你使用数据协定来定义结构化的类型。WCF运行时使用数据协定来序列化和反序列化这些结构化的类型。

    使用数据协定,你可以准确地为服务指定XML格式的数据。在WCF客户端,数据协定序列化器使用数据协定将参数数据序列化为XML。在WCF服务端,数据协定序列化器使用数据协定将XML数据反序列化为其能处理的参数数据。服务的返回值同样地在服务端序列化为XML数据,传送至客户端后再被反序列化。

    数据协定特性和数据成员特性

    在第一章,你已经看到如何定义一个简单的数据协定,用以呈现返回给客户端的产品数据。这就是一个数据协定的具体模样:

    为一个类添加DataContract特性后,那么该类将可以被数据协定序列化器格式化。数据协定序列化器将序列化和反序列化该类中标记了DataMember特性的成员。ProductData中的成员都是.NET Framework主要的数据类型,序列化器使用内建的规则将这些成员转换成XML消息。比如下面就是ProudctData序列化后的XML片段:

    如果ProductData类中包含结构类型的成员,那么该成员也应标记DataContract特性。相应地,序列化器将对这些成员嵌套使用序列化和反序列化过程。

    DataContract和DataMember特性具有可选属性,你可以使用这些属性来调整序列化器工作的方式。你将在下面的练习中研究这些属性。

    通过数据协定变更序列化后ProductData成员的顺序

    1. 打开解决方案*\Chapter6\ProductsServiceVersion

    2. 打开ProductsServiceLibrary下的IProdutsService.cs,找到ProductData类,你可以看到该类有四个成员,他们是Name,ProductNumber,Color和ListPrice

    3. 使用WCF服务配置工具,打开ProductsServiceHost项目下的app.config; 变更"诊断—侦听器—消息日志"的InitData属性的值到*\Chapter6\ProductsServiceVersion文件夹下

    4. 保存设置,并退出WCF服务配置工具

    5. 使用资管管理器,转到目录*\Chapter6\ProductsServiceVersion,删除ProductsService.svclog

    6. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;并且ListProducts将返回所有的产品。

    7. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    8. 回到目录*\Chapter6\ProductsServiceVersion,双击ProductsService.svclog。以使用服务追踪查看器打开该日志

    从GetProductResponse中,我们可以看到ProductData的序列化为XML后,成员的顺序为Color, ListPrice, Name和ProductNumber(按照字母顺序排列)

    9. 关闭服务追踪查看器

    10. 回到Visual Studio,按照下列方式编辑IProductsServcie

    并不是变更成员的名字以实现序列化后的顺序,而是通过指定DataMember特性类的Order属性值来指定一个数字序列。数据协定序列化器将根据Order属性的值来顺利地序列化ProductData成员,数字值小的先序列化;如果两个成员Order值相同,那么再按照字母顺序排列 。

    11. 使用资管管理器,转到目录*\Chapter6\ProductsServiceVersion,删除ProductsService.svclog

    12. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;并且ListProducts将返回所有的产品。

    13. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    14. 回到目录*\Chapter6\ProductsServiceVersion,双击ProductsService.svclog。以使用服务追踪查看器打开该日志

    我们可以看到ProductData的成员按照我们指定的顺序被序列化。

    注意:为了使上述代码可以正常工作,你需要重新生成客户端代理类。但在做这个任务前,我们先来了解一下如果变更成员的名字,将会对数据协定带来什么影响?

    与服务协定相似,数据协定序列化器使用数据成员的名字形成每个序列化后字段的名字。想应地,变更数据成员的名字会导致中断性变更,其要求更新客户端程序。与服务协定中的操作一样,你可以为数据成员提供一个逻辑名字,数据协定序列化器将使用该名字来替换数据成员的实际名字。DataMember特性类提供了一个Name的属性,你可以通过该属性来为数据成员指定逻辑名,这样你即使更换了实际的名字,逻辑名字仍然可以保持不变。比如:

    数据协定特性类提供了NameSpace属性。默认情况下,WCF使用命名空间为http://schemas.datacontract.org/2004/07 加上该数据协定类的命名空间。以IProductsService为例,那么数据协定序列化器将使用的命名空间为http://wchemas.datacontract.org/2004/07/Products. 你可以通过指定Namespace属性的值覆盖默认的命名空间。这种方法是值得推荐的方式。你可以在命名空间里指定一个日期用以辨别数据协定的不同版本。如果你更新了数据协定,那么你紧接着应该通过数据协定特性类的Namespace属性指定该数据协定修改的日期。

    修改ProductData数据协定的Namespace属性

    1. 在Visual Studio,打开ProductsServiceLibrary项目下的IProductsServcie.cs文件

    2. 指定ProductData数据协定特性类的Namespace属性值

    3. 资管管理器,转到目录*\Chapter6\ProductsServiceVersion,删除ProductsService.svclog

    4. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;但是Test2,Name字段没有显示任何值。

    5. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    6. 回到目录*\Chapter6\ProductsServiceVersion,双击ProductsService.svclog。以使用服务追踪查看器打开该日志

    7. 关闭服务追踪查看器

    通过上述练习,你可以看到ProductsService服务按照期望的方式格式化消息,尽管此时客户端程序还不能正确的处理这些消息(客户端代理为更新)。下一步的练习我们将重新生成客户端的代理类。

    重新生成代理类,并更新WCF客户端程序

    1. 使用管理员身份运行Visual Studio command prompt,然后转到路径*\Chapter6\ProductsServiceVersion\ProductsServiceLibrary\bin\Debug下,

    2. 执行命令svcutil.exe ProductsServiceLibrary.dll

    执行完该命令后,将生成下列文件

    请注意,服务包含两个服务协定,所以上述命令生成了两个WSDL描述文件,每个都有用自己的schema(数据架构)。

    3. 输入下列命令从使用描述IProductsServiceV2的WSDL文件和对于的schema文件生成代理类:

    注意:如果你想生成IProductsServcie对应的代理类,请使用相应的WSDL文件。

    4. 回到Visual studio, 删除product.cs,然后通过添加已存文件的方式,添加上一步生成的ProductsV2.cs文件添加到ProductsClient下,然后修改program.cs:

    5. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;

    6. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    7. 最后,我们再来看一下日志,可以发现,消息的命名空间已经使用最新的服务协定指定的命名空间。

    你应该仔细评估数据协定变更导致的影响。更改数据协定会以一种不明显的方式导致客户端程序不能正常工作。原始的SOAP序列化可以为打乱了顺序的或是丢失的字段自动添加该字段类型的默认值。你当然也可以为当前数据协定添加新的成员。在某些情况下,你可以完成该任务而不需要中断现有的客户端程序。你应该注意添加一个成员到数据协定会更改从WCF导出的schema。客户端程序使用该schema去定义与服务收发SOAP消息中数据的格式。 然而,一部分使用其他技术开发的客户端程序会执行严格的schema检查。如果你的服务又必须支持这些类型的客户端程序,你不可以添加数据协定成员后,而不去更新这些客户端。 在这种情况下,与服务版本一样,你应该采取数据协定版本策略。关于数据协定的更多消息,请参考:http://msdn.microsoft.com/en-us/library/ms733832.aspx

    在下面的练习中,你将检查添加数据协定成员导致的结果,并且还可以看到WCF客户端如何处理这些新增加的数据成员。

    添加一个新成员到ProductData数据协定

    1. 转到Visual studio,然后开发ProductsServiceLibrary项目下的IProductsServcie.cs文件

    2. 为ProductData添加一个新成员

    3. 打开ProductsService.cs中,修改GetProduct方法

    4. 使用资管管理器,转到目录*\Chapter6\ProductsServiceVersion,删除ProductsService.svclog

    5. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;包括test2,新添加的字段没有影响到现有的客户端。

    6. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    7. 最后,我们再来看一下日志,可以发现,StandardCost已经包含在服务端的返回消息中。

    数据协定序列化器序列化数据协定的所有成员。现有的WCF客户端不仅不关注新字段StandardCost,而且还不执行schema验证,因此客户端直接忽略多余的字段。

    8. 退出ProductsService.log,并退出服务追踪查看器

    9. 使用svcutil重新生成客户端代理类

    10. 回到Visual studio;然后ProductsClient下的ProductsV2.cs文件,然后添加将上一步生成的ProductsV2.cs

    11. 修改Programm.cs

    12. 重新生成解决方案;然后启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;包括test2,新添加的字段已经能显示

    13. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    添加一个操作到WCF服务并检查数据协定序列化

    1. 在Visual Studio中,打开ProductsServiceLibrary项目下的IProductsService.cs文件

    2. 按照下列方式修改IProductsServiceV2,添加新操作UpdateProductDetials

    3. 重新生成项目ProductsServiceLibrary,在Visual Studio command prompt中 使用svcutil重新生成客户端代理类

    4. 回到Visual studio;然后ProductsClient下的ProductsV2.cs文件,然后添加将上一步生成的ProductsV2.cs

    5. 修改Programm.cs

    6. 重新生成解决方案;然后启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过; 当test5,运行时,将出现一个消息框显示ProductNumber和更新后的ProductName。

    7. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    客户端程序使用数据协定定义,成功地将Product对象发送至WCF服务;但是如果客户端程序使用的某个版本的数据协定定义中缺少一个字段,会发生什么?

    添加另外一个成员到ProductData数据协定并检查其默认值

    1. 在visual studio中,打开ProductsServiceLibrary项目下的IProductsService.cs文件

    2. 添加下面的成员(红色矩形内)到ProductData数据协定

    3. 修改ProductService.cs类

    4. 重新生成解决方案;然后启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过; 当test5,运行时,将出现一个消息框显示FinshedGoodFlag为false。客户端程序仍旧使用旧版本的Product数据协定并且不会为FinsihedGoodFlag赋值。显示为false的原因是数据协定序列号器为其赋予了默认值。点击OK关闭消息提示框。

    5. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    当为数据协定添加新成员时,该新添加的成员会变更数据协定序列号后的顺序,此时,你应当考虑到现有的客户端程序。 如果客户端程序并没有为一个序列化后的对象的每个字段赋值,WCF将使用每个成员的数据类型的默认值,比如boolean类型为false,值类型为0,引用类型为null。如果默认值不可识别,你可以通过序列化回调方法自定义序列化和反序列化过程。 关于自定义序列化的信息,请参考http://msdn.microsoft.com/en-us/library/ms229752.aspx

    数据协定的兼容性

    如果你需要定义不同版本的数据协定,你需要在确保现有客户端程序兼容性的前提下去实现。DataMember特性提供了两个属性可以帮助你实现数据协定的兼容性

    • IsRequired 如果设置该属性的值为true,那么服务收到的SOAP消息必须包含该成员的值;默认情况下,该属性的值为falseWCF运行时将为任何为提供值的成员根据其数据类型提供该类型的默认值。
    • EmitDefaultValue 如果设置该属性值为true,客户端的WCF运行时将为该成员生产其默认值,如果该成员为包含在客户端发送的SOAP消息中。该属性的默认值为true

    如果你需要在一个未来版本的服务中严格地保持数据协定的一致性,你应该为数据协定的每个成员上设置IsRequired属性值为true;并且当你创建第一个版本的服务时应在数据协定的每个成员上设置EmdiDefaultValue属性值为false。如果一个成员在上一个版本中IsRequired为false,你绝对不要在新版本中设置该成员的IsRequired为true。 这样能使数据协定对老版本的客户端保持兼容性。

    还有一个进一步的问题需要考虑:客户端程序可能请求遵守服务数据协定的数据,然后修改数据并回传给服务。就好比在ProductsService服务中,先请求GetProduct方法从服务端得到ProductData,随后通过调用UpdateProductDetails将更新后的后的ProductData传回给服务? 如果客户端程序使用未包含新数据协定成员的老版本数据协定,客户端向服务回传数据时会发生什么情况? WCF运行时实现了一项技术叫做"round-tripping"用以确保新的数据成员不会丢失。你将在下面的练习中检查上述情况。

    调查WCF运行时如何指定round-tripping

    1. 在Visual Studio中,打开ProductsServiceLibrary项目下的ProductsServcie.cs文件

    2. 修改GetProduct方法

    在代码中,我们设置FinsihedGoodsFlag为true。请记住Boolean类型的默认值为false。

    3. 重新生成解决方案;然后启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。你将会得到如下结果:

    在消息提示框中,FishishedGoodsFlag的值为true。FinishedGoodsFlag最先由服务端提供,然后它发送至客户端;然后由客户端回传至服务。尽管此时客户端使用老版本的代理(即客户端根本不知道FinishedGoodsFlag字段),客户端WCF运行时管理该字段,并使其保持初始值。 点击OK按钮,关闭消息提示框。

    4. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost 。

    WCF运行时通过IExtensibleDataObject接口实现round-tripping。如果你检查ProductsV2.cs文件,你可以看到客户端代理ProductData类实现了该接口。该接口定义了一个名为ExtensionData的属性,该属性的类型为ExtensionDataObject。 ExtensionData属性专为客户端代理而使用,它可以读取和写入数据到一个类型为ExtensionObjectData私有成员extensionDataField,如下面所示:

    extensionDataField成员的作用好比一个"水桶",它可以容纳客户端接收到的所有未定义的数据成员;而不是丢弃这些数据成员;代理自动将这些数据成员存贮到该水桶中。当客户端代理向服务回传ProductData对象时,该对象包含了这个水桶中所有的数据成员。 如果你想停止该功能,你可以在客户端的端点行为中,设置dataContractSerializer元素的IgnoreExtensionDataObject属性值为 true。比如下面这样:

    你同样可以在服务端停止该特性:

    【总结】

    在本章,你了解了如何使用服务和数据协定来定义服务暴露给客户端程序的操作,这些操作可供客户端程序接收和回传至服务端。你也已经了解到为什么要仔细地设计服务和数据协定,以及如何创建一个新版本的服务和数据协定的同时保持与现有客户端程序之间的兼容性。

    【代码】 本章相关源代码下载

    展开全文
  • 在第章WCF简介中,你已经了解SOA的基本原则--服务之间共享数据架构和协定,而并非类或者类型。当你定义服务时,你通过定义服务协定以指定操作。服务协定描述该服务的操作、操作所使用的参数类型及其操作返回值的...

    【摘要】

    在第一章WCF简介中,你已经了解SOA的基本原则--服务之间共享数据架构和协定,而并非类或者类型。当你定义服务时,你通过定义服务协定以指定操作。服务协定描述该服务的操作、操作所使用的参数类型及其操作返回值的类型。WCF服务对外公布服务协定的定义,服务开发人员使用这些定义去构建相应的客户端。开发人员可以通过Visual Sutdio自带的添加服务向导或者svcutil实用工具为根据服务的WSDL描述生成一个客户端代理类,客户端使用该代理类与进行服务通信。

    服务协定仅仅是整个故事的一部分。服务协定中的操作可以使用参数并且操作可以有返回值。客户端程序必须提供服务所要求格式的数据。在.NET Framwork中,主要数据类型都有预先定义的格式;而类,结构,枚举等类型则拥有比较复杂的格式;这些复杂格式的数据类型要求客户端程序在想服务发送消息时,先将这些复杂数据打包然后才发送。同样地,服务端也需要格式化这些复杂数据,然后才发送给客户端。你可以使用数据协定来封装这些复杂格式的数据类型;服务所使用的复杂数据类型都应有一个对应的数据协定。服务将数据协定和服务协定一起对外公布,那么这些复杂格式的数据类型将包含在通过svcutil实用工具或visual studio添加服务向导所生产的客户端代理类中。

    服务协定和数据协定是WCF服务非常基础的部分;如果客户端不能识别服务对外公布的操作或者服务所使用的数据类型,那么该客户端在与服务进行通信时将遇到很多麻烦。

    【目录】

    更改服务协定

       选择性地保护操作

      服务的版本

      对服务协定实施中断行更改和非中断性更改

    更改数据协定

      数据协定特性和数据成员特性

      数据协定的兼容性

    【正文】

    修改服务协定

    服务协定就是一个接口,WCF工具和架构能将该协定转换成一个WSDL文档,在该文档中列出一个服务的所有操作(一系列SOAP消息和消息响应)。你在服务实现类中实现这些接口中的方法。当WCF服务开始运行时,WCF运行时使用服务配置文件中的绑定创建一个通道堆栈,然后开始侦听来自客户端的消息请求。WCF运行时将来自WCF客户端的消息转换成方法的调用并且调用服务实现类实例中对应的方法。该方法返回的数据将转换成一个SOAP消息然后回传给通道堆栈,最后通道堆栈传将该消息传输至客户端。

    从上述过程,可以得出两个结论:

    (1). 服务协定独立于服务端的通信。根据服务配置文件中绑定而创建的通道堆栈管理通信机制。这就意味着,即使变更了通信传输协议或者服务的地址,也不需修改服务或者访问该服务客户端程序的代码。更进一步讲,服务的安全方面也独立于服务协定。

    (2). 与服务通信的客户端程序必须构建与服务向适应的SOAP消息。这些消息依赖于服务协定。如果服务协定变更,客户端必须也提供新版本的SOAP消息。否则,服务端将不能识别客户端发送的消息或者客户端想服务发送格式不正确的SOAP消息。此外,如果服务返回的消息发生变更,客户端程序很可能也不能处理这些返回消息。

    你可以从本章后续的练习中来验证上述两点结论。

    可选择性的保护操作

    在第四章和第五章讲述了如何保护客户端和服务端传送的消息。但是,你所使用的技术侧重于通过绑定和服务的行为来实现整个服务的安全(即一个服务的所有操使用同一个安全设置)。而通过修改服务协定,你可以在同一个服务中根据不同的安全需求对各个操作设定对应的安全性。

    根据安全需求设定WCF服务的操作的安全性

    1. 使用Visual Studio打开*\Chapter6\ProductsService文件夹下的ProductsServcie方案。该方案复制了第四章的ProductsServcie解决方案。该解决方案包含了ProductsServiceLibrary项目、ProductsServcieHost项目和ProductsClient项目;在本章中,该服务将使用非SSL端点,并且该端点WS2007HttpBinding绑定。WS2007HttpBinding绑定默认实现了消息安全并且使用Windows token来验证客户端用户。

    2. 打开ProductsServiceLibrary项目下的IProducts.cs文件;添加下面的using语句

    using System.Net.Security;

    3. 修改ListProducts和GetProduct方法

    201106012344163161.png

    OperationContract特性类的ProtectLevel属性用于指定如何保护调用该操作的消息(即规定了客户端发送至该操作消息的安全特性)。在本例中,EncryptAndSign指定调用的ListProducts和GetProduct操作的消息必须由客户端签名、并使用与服务协商的密钥加密。这就要求服务端和客户端采用的绑定必须设置其安全模式为消息安全验证,此外客户端和服务端还必须为AlgorithmSuite属性指定同样的值。 实际上,上述设置是WS2007HttpBinding绑定使用消息安全时的默认设置。此外,ProtectLevel还可能有下列值:

    201106012344163684.png

    请注意ProtectLevel属性的默认值还取决于端点所使用的传输协议。如果使用BasicHttpBinding,那么默认的值将为None。

    4. 修改CurrentStockLevel和ChangeStockLevel的OperationContract特性

    201106012344174698.png

    5. 使用WCF服务配置工具打开ProductsServiceHost的app.config文件

    6. 点击"Disgnostics",确认右边面板中的MessageLogging的状态为On

    201106012344197847.png

    7. 点击"Listeners—Messagelog", 修改InitData的路径为"*\Chapter6\ProductsService"

    201106012344214585.png

    8. 点击"Diagnostics—Message logging",在右边面板中设置LogMessageAtServiceLevel属性的值为false,确认LogMessageAtransportLevel属性的值为true

    201106012344222501.png

    9. 保存配置文件,并退出WCF服务配置工具。

    测试修改后的服务

    1. 运行ProductsServcieHost,点击"开始"按钮启动ProductsServcie服务

    2. 启动ProductsClient,你将得到如下结果:

    201106012344239021.png

    Test1和test2都成功完成,因为绑定实现了加密和签名,这符合定义在ListProducts和GetProduct方法上的操作协定。但是test3抛出了一个异常:"The primary signature must be encrypted",因为CurrentStockLevel操作在操作协定中设定了保护级别为签名,但是客户端的绑定根据默认的设置提供了加密和签名。产生该问题的原因就是你更新了服务协定,但是没有更细客户端代码中相应的代码。所以客户端的代理在test3中仍旧向服务发送加密和签名后的消息。

    3. 退出ProductsClient

    4. 在Visual Studio中,修改ProductsClient项目下的Products.cs文件

    201106012344251331.png

    5. 删除位于*\Chapter6\productsService文件夹下的ProductsService.svclog

    6. 打开ProductsServcieHost,然后点击"开始"按钮启动ProductsServcie

    7. 启动ProductsClient,然后你会得到如下结果:

    201106012344274514.png

    8. 关闭ProductsClient

    9. 启动"Service trace viewer",并打开位于*\Chapter6\productsService文件夹下的ProductsService.svclog

    10. 在服务追踪查看器中,点击左边面板中的"消息"标签。你可以看到六条消息关于客户端和服务之间的协商加密的密钥,它们的Action都使用http://docs.oasis-open.org命名空间。 在这六条消息之后,是8条服务端和客户端收发的消息,它们的Action都是使用http://tempuri.org命名空间。

    11. 选中http://tempuri.org/IProductsService/ListProducts, 在后边面板中,点击"Formatted"标签,并滚动到内容的底端,你可以看到"Envelope Information section"的内容。你可看到Method对应的值为e:EncryptedData;由此可见,客服端发送给服务的操作名已经加密

    201106012344308295.png

    11. 选中http://tempuri.org/IProductsService/ListProductsResponse, 在右下面板中,确认服务返回的消息也已经加密。确认http://tempuri.org/IProductsService/GetProduct 和ttp://tempuri.org/IProductsService/GetProductResponse的消息也被加密

    12. 选中http://tempuri.org/IProductsService/CurrentStockLevel,同样切换到右下的"Formatted"标签下,你可以发现客户端向服务发送的操作名为及该操作的参数都没加密。

    201106012344334344.png

    13. 检查http://tempuri.org/IProductsService/CurrentStockLevelResponse、http://tempuri.org/IProductsService/ChangeStockLevel以及http://tempuri.org/IProductsService/ChangeStockLevelResponse;确认它们都操作的名字和参数或返回结果没有被加密。

    14. 关闭ProductsService.svclog;并退出Service trace viewer

    服务版本管理

    更改时刻发生。一个被广泛使用的服务几乎不可避免的随着业务过程的变化而演化。那么发生的更改可能有哪些方式呢?

    • 在大多数情况下,通过服务实现类中修改代码;
    • 有时候,通过修改服务操作的定义;
    • 当然,可以通过添加新的服务操作;
    • 或者通过删除一些不再使用的或重复的操作;
    • 或者变更一些现有操作的参数类型或返回类型。

    显然,这些更改都会导致更新服务协定。然后,客户端通过服务协定指定向服务收发的消息。如果服务协定变更,而客户端仍使用之前版本的服务协定,那么将导致什么结果? 是客户端能继续工作,还是需要检查每个客户端并更新客户端的代码呢? 你是否知道每个客户端的具体位置? 因为客户端可能通过因特网连接到服务,所以这些客户端可能分布在世界上的任何地方。 你可以看到,修改一个服务并不是话费稍许的气力就可以实现的。从上述情况来看,你需要采取多个步骤以确保现有的客户端即使没有更新仍能继续工作。 下面的练习展示了一些常见的场景,可以帮助理解当服务或服务协定变更时实际发生了什么;以及为减少服务变更带来的负面影响所应遵循的策略。

    为WCF服务添加一个方法和修改方法的业务逻辑

    1. 使用Visual Studio打开ProductsServiceVersion方案下ProductsServcieLibrary项目中的ProductsServcie.cs文件

    2. 添加下面方法(检查一个产品是否存在)到服务实现类中

    201106012344344768.png

    3. 修改GetProduct方法的业务逻辑

    201106012344357243.png

    4. 修改CurrentStockLevel的业务逻辑

    201106012344378964.png

    5. 修改ChangeStockLevel的业务逻辑

    201106012344397894.png

    6. 重新生成项目ProductsServiceLibrary;然后打开ProductsServiceHost,点击"开始"运行ProductsServcie;并打开ProductClient,所有的操作都可以成功执行。

    201106012344414107.png

    7. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    8. 在Visual Studio中,按照下图中红框中的内容修改program.cs文件中的test2;

    201106012344414631.png

    9. 再次启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。你将得到如下结果:

    201106012344428959.png

    10. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    11. 再次编辑program.cs文件;修改test2的方法使用一个存在的productnumber

    201106012344428402.png

    从上面的练习我们可以看到,尽管服务发生变更,并且为该服务添加了一个新方法。但是客户端的服务协定没有更新;因此客户端程序不能访问ProductExist方法;客户端仍旧与使用之前一样的方式访问服务。上述变更是一个非中断性服务变更。

    服务协定中现有的操作添加一个参数

    1. 打开ProductsServcieLibrary项目下的IProductsServcie.cs

    2. 添加一个参数 到ListProducts操作

    201106012344421434.png

    3. 打开ProductsServcieLibrary项目下的ProductsServcie.cs,修改ListProducts方法:有两处修改,第一处为ListProducts方法添加参数;第二处为修改LINQ方法,使其根据参数过滤返回的产品编码列表。

    201106012344443777.png

    4. 重新生成解决方案后,启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试都将通过,而且ListProducts将返回所有的产品。

    201106012344456677.png

    从上图可以看到,test1方法,也就是ListProducts并未返回任何值。因为客户端仍然使用没有参数的ListProducts方法向服务发送消息;当宿主WCF服务的WCF运行时接收到该消息后,反序列化消息,然后发现该消息的body部分没有包含参数数据,然后WCF运行时向服务传输一个null的参数值。服务的ListProducts方法将相应地返回一个空的列表。

    5. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    你可能非常奇怪上面练习的结果,你发现你可以对一个操作添加一个参数(或从一个操作移除一个参数),现有的客户端仍然可以调用该操作。如果你对一个操作添加一个参数,并且客户端程序没有为该操作提供参数,那么WCF运行时根据参数的数据类型为其提供该数据类型的默认值:引用类型为null,值类型为0;Boolean为false。请注意,即使你在定义操作时,设定了参数的值;但当客户端调用该操作时如果没有提供参数,那么这些默认值(null,0和false)仍然传至所调用的方法,并且覆盖你在服务中设定的参数值;比如你按照下列方式定义ListProducts 201106012344459709.png

    如果客户端调用该方法时没有提供参数值,那么WCF运行时将为ListProducts方法的参数设置为null。

    同样地,如果你在客户端变更了参数的类型,那么当该参数传递值寄宿WCF服务的WCF运行时后,WCF运行时将尝试类型转化。如果转化失败,那么将抛出异常。

    添加、移除和修改服务协定中操作参数是不值得推荐地,如果发生上述情况,你应该格外小心这些变更导致的结果。比如,如果移除某个操作包含的多个参数中间某一个参数,那么由客户端传送至服务端的参数在反序列化时可能会反序列化为错误参数。

    如果你变更一个操作的返回类型。当服务返回的类型不能转化成客户端程序希望的类型,那么客户端程序处理消息所使用的格式化工具将抛出一个异常。

    一般地,你应当避免修改操作的参数类型或者参数个数。相反地,在你修改服务协定后,保证新客户端工作的同时,应确保与现有客户端的兼容性。 实际上,你应该定义一个新版本的服务协定,并且保存之前版本的服务协定。在下面的练习中你完成上述目的。

    为WCF服务添加一个新操作

    1. 打开ProductsServcieLibrary项目下的IProductsServcie.cs

    2. 在接口IProductsService中,从ListProducts方法中移除match参数,并添加另外一个版本的ListProducts方法。

    201106012344456644.png

    请注意,虽然C#支持方法重载,但是SOAP标准不允许一个服务对外公布多个具有相同名字的操作。但是这里有另外一种方法,可以让你在使用重载的同时还支持SOAP;那么就是在相同方法名字上设置OperationContract特性的Name属性值以在SOAP消息中生成不同的操作名。比如

    201106012344463579.png

    3. 打开ProductsService.cs,做如下修改:

    201106012344469642.png

    4. 重新生成解决方案后,启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;并且ListProducts将返回所有的产品。

    201106012344479477.png

    5. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    为服务协定所添加新方法并是一个非中断变更。如果你构建一个新的WCF客户端程序,你可以使用svcutil施工工具生成一个包含该新操作的代理类。现有的客户端程序使用老版本代理类仍然可以和服务端通信,而且老版本的代理类不关心新操作的存在。

    但是,如果你希望新的代理类仅仅使用新的操作(ListMatchedProducts)而不能使用旧的操作(ListProducts)时,将带来潜在的问题。该如何对新版本的客户端程序隐藏旧的操作呢?答案就是使用多重服务协定。保持现有的服务协定不做任何变更,新版本的服务协定仅仅包含新操作。如下表所示:

    老版本服务协定

    新版本服务协定

    201106012344483315.png

    201106012344497186.png

    上面的代码展示了使用ServiceOperation特性类的命名空间属性和Name属性来区分和命名不同的服务版本。默认情况下,服务协定使用http://tempuri.org命名空间,使用接口的名字来命名来服务协定。当你定义一个新版本的服务协定时,可以通过命名空间属性值来区分不同的服务协定,但仍保持相同的Name属性。 请注意,设置命名空间属性和Name属性后,将导致一个中断性变更,因为这些信息用以识别服务和客户端之间收发的SOAP消息。

    如果你定义了两个不同的版本的服务,那么此时你的服务实现类需要同时实现这两个接口。

    20110601234449217.png

    最后,你需要在服务端创建一个使用新版本服务的服务端点,并且该端点使用ProductsServcie.IProductsServcieV2服务协定。

    对服务协定创建中断和非中断性变更

    严格地讲,你应该考虑服务的稳定性。服务协定的任何变化都很可能影响到客户端程序,导致客户端不再能继续与服务进行正常通信。在实际中,你可以对服务协定做一些变更,且不中断现有的客户端程序通过之前的服务协定连接到该服务。下表列举了一些开发人员经常对服务协定所作的变更和这些变更对客户端程序所带来的影响

    变更

    影响

    添加新操作

    非中断变更。现有客户端不受影响,但是使用老版本代理类的客户端程序看不到新操作。但是使用代码创建查询和消息的客户端可以使用新的操作,详细内容参考第十一章

    移除一个操作

    中断变更。现有客户端将不能继续正常工作;尽管不调用该操作客户端不受影响

    变更操作名

    中断变更。现有客户端将不能继续正常工作;尽管不调用该操作客户端不受影响。请注意,操作名默认对应服务协定中的方法名。你可以变更服务协定中的方法名;但应保留操作名。比如你可以通过下列方式来完成:
    201106012344493249.png

    这是一个不错的方法,因为它移除了服务协定和实现该操作的名字之间的依赖关系。

    修改操作的保护级别

    中断变更。现有客户端将不能再调用该操作

    对操作添加新参数

    非中断操作。现有客户端可以继续调用该操作,但是WCF运行时将为根据新参数的类型为新参数提供其类型对应的默认值

    变更操作参数的顺序

    中断操作。结果不易预料(某些现有客户端还能继续保持工作)

    移除操作的一个参数

    可能是非中断操作,只要被移除的参数是该操作的最后一个参数。在这种情况下,现有客户端传递过来的多余参数将被忽略。如果被移除的参数为第一个或中间的参数,那么将导致一个中断操作,引用于变更参数的顺序相同

    变更操作参数的类型
    或变更返回值类型

    可能是中断操作,如果WCF格式化工具不能将之前的数据类型转化成新的数据类型。现有客户端可能继续工作,但是将导致SOAP消息中数据丢失或者错误地转化的风险。该变更还会影响到应用或移除ref和out参数的服务协定,及时ref和out的参数类型没有发生变更。更多消息请参考本章后续内容"修改数据协定"

    添加FaultOperation特性

    中断变更。现有客户端程序能发送fault消息,但是这些消息不能正确的理解

    移除FaultOperation特性

    非中断变更。现有客户端将可以继续正常工作,尽管指定的异常处理被认为过期

    变更服务协定的命名空间属性和Name属性

    中断变更。使用之前的Name或者命名空间现有客户端程序将不能再向服务发送消息。

    如果你对服务协定创建了一个中断性变更,你必须更新所有访问该服务的客户端程序。如果客户端程序使用代理,你需要更新这些代理。推荐的修改服务协定的方式是:创建一个新版本的服务协定,并同时保持旧版本的服务协定。这种方式你不需要更新现有的客户端程序,尽管它们不使用服务的新操作。

    修改数据协定

    服务协定中的方法能使用参数,并且返回值;这些参数和返回值包含在SOAP消息中并在服务和客户端之间传输。SOAP消息将这些数据编码成XML文本。WCF运行时使用内建的XML序列化器将这些XML文本序列化和反序列化为.NET Framework的主要数据类型,比如整数,数字,或者字符串。对于更复杂的接口类型,服务必须指定确切的序列化格式;可以用多种方式将同一个结构化数据描述成不同XML。你使用数据协定来定义结构化的类型。WCF运行时使用数据协定来序列化和反序列化这些结构化的类型。

    使用数据协定,你可以准确地为服务指定XML格式的数据。在WCF客户端,数据协定序列化器使用数据协定将参数数据序列化为XML。在WCF服务端,数据协定序列化器使用数据协定将XML数据反序列化为其能处理的参数数据。服务的返回值同样地在服务端序列化为XML数据,传送至客户端后再被反序列化。

    数据协定特性和数据成员特性

    在第一章,你已经看到如何定义一个简单的数据协定,用以呈现返回给客户端的产品数据。这就是一个数据协定的具体模样:

    201106012344496281.png

    为一个类添加DataContract特性后,那么该类将可以被数据协定序列化器格式化。数据协定序列化器将序列化和反序列化该类中标记了DataMember特性的成员。ProductData中的成员都是.NET Framework主要的数据类型,序列化器使用内建的规则将这些成员转换成XML消息。比如下面就是ProudctData序列化后的XML片段:

    201106012344501721.png

    如果ProductData类中包含结构类型的成员,那么该成员也应标记DataContract特性。相应地,序列化器将对这些成员嵌套使用序列化和反序列化过程。

    DataContract和DataMember特性具有可选属性,你可以使用这些属性来调整序列化器工作的方式。你将在下面的练习中研究这些属性。

    通过数据协定变更序列化后ProductData成员的顺序

    1. 打开解决方案*\Chapter6\ProductsServiceVersion

    2. 打开ProductsServiceLibrary下的IProdutsService.cs,找到ProductData类,你可以看到该类有四个成员,他们是Name,ProductNumber,Color和ListPrice

    3. 使用WCF服务配置工具,打开ProductsServiceHost项目下的app.config; 变更"诊断—侦听器—消息日志"的InitData属性的值到*\Chapter6\ProductsServiceVersion文件夹下

    4. 保存设置,并退出WCF服务配置工具

    5. 使用资管管理器,转到目录*\Chapter6\ProductsServiceVersion,删除ProductsService.svclog

    6. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;并且ListProducts将返回所有的产品。

    7. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    8. 回到目录*\Chapter6\ProductsServiceVersion,双击ProductsService.svclog。以使用服务追踪查看器打开该日志

    201106012344519048.png

    从GetProductResponse中,我们可以看到ProductData的序列化为XML后,成员的顺序为Color, ListPrice, Name和ProductNumber(按照字母顺序排列)

    9. 关闭服务追踪查看器

    10. 回到Visual Studio,按照下列方式编辑IProductsServcie

    201106012344527935.png

    并不是变更成员的名字以实现序列化后的顺序,而是通过指定DataMember特性类的Order属性值来指定一个数字序列。数据协定序列化器将根据Order属性的值来顺利地序列化ProductData成员,数字值小的先序列化;如果两个成员Order值相同,那么再按照字母顺序排列 。

    11. 使用资管管理器,转到目录*\Chapter6\ProductsServiceVersion,删除ProductsService.svclog

    12. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;并且ListProducts将返回所有的产品。

    13. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    14. 回到目录*\Chapter6\ProductsServiceVersion,双击ProductsService.svclog。以使用服务追踪查看器打开该日志

    201106012344539788.png

    我们可以看到ProductData的成员按照我们指定的顺序被序列化。

    注意:为了使上述代码可以正常工作,你需要重新生成客户端代理类。但在做这个任务前,我们先来了解一下如果变更成员的名字,将会对数据协定带来什么影响?

    与服务协定相似,数据协定序列化器使用数据成员的名字形成每个序列化后字段的名字。想应地,变更数据成员的名字会导致中断性变更,其要求更新客户端程序。与服务协定中的操作一样,你可以为数据成员提供一个逻辑名字,数据协定序列化器将使用该名字来替换数据成员的实际名字。DataMember特性类提供了一个Name的属性,你可以通过该属性来为数据成员指定逻辑名,这样你即使更换了实际的名字,逻辑名字仍然可以保持不变。比如:

    201106012344534772.png

    数据协定特性类提供了NameSpace属性。默认情况下,WCF使用命名空间为http://schemas.datacontract.org/2004/07 加上该数据协定类的命名空间。以IProductsService为例,那么数据协定序列化器将使用的命名空间为http://wchemas.datacontract.org/2004/07/Products. 你可以通过指定Namespace属性的值覆盖默认的命名空间。这种方法是值得推荐的方式。你可以在命名空间里指定一个日期用以辨别数据协定的不同版本。如果你更新了数据协定,那么你紧接着应该通过数据协定特性类的Namespace属性指定该数据协定修改的日期。

    修改ProductData数据协定的Namespace属性

    1. 在Visual Studio,打开ProductsServiceLibrary项目下的IProductsServcie.cs文件

    2. 指定ProductData数据协定特性类的Namespace属性值

    201106012344533343.png

    3. 资管管理器,转到目录*\Chapter6\ProductsServiceVersion,删除ProductsService.svclog

    4. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;但是Test2,Name字段没有显示任何值。

    201106012344549100.png

    5. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    6. 回到目录*\Chapter6\ProductsServiceVersion,双击ProductsService.svclog。以使用服务追踪查看器打开该日志

    20110601234456505.png

    7. 关闭服务追踪查看器

    通过上述练习,你可以看到ProductsService服务按照期望的方式格式化消息,尽管此时客户端程序还不能正确的处理这些消息(客户端代理为更新)。下一步的练习我们将重新生成客户端的代理类。

    重新生成代理类,并更新WCF客户端程序

    1. 使用管理员身份运行Visual Studio command prompt,然后转到路径*\Chapter6\ProductsServiceVersion\ProductsServiceLibrary\bin\Debug下,

    2. 执行命令svcutil.exe ProductsServiceLibrary.dll

    201106012344565488.png

    执行完该命令后,将生成下列文件

    201106012344572390.png

    请注意,服务包含两个服务协定,所以上述命令生成了两个WSDL描述文件,每个都有用自己的schema(数据架构)。

    3. 输入下列命令从使用描述IProductsServiceV2的WSDL文件和对于的schema文件生成代理类:

    20110601234457406.png

    注意:如果你想生成IProductsServcie对应的代理类,请使用相应的WSDL文件。

    4. 回到Visual studio, 删除product.cs,然后通过添加已存文件的方式,添加上一步生成的ProductsV2.cs文件添加到ProductsClient下,然后修改program.cs:

    201106012344579849.png

    5. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;

    20110601234458830.png

    6. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    7. 最后,我们再来看一下日志,可以发现,消息的命名空间已经使用最新的服务协定指定的命名空间。

    201106012344594144.png

    你应该仔细评估数据协定变更导致的影响。更改数据协定会以一种不明显的方式导致客户端程序不能正常工作。原始的SOAP序列化可以为打乱了顺序的或是丢失的字段自动添加该字段类型的默认值。你当然也可以为当前数据协定添加新的成员。在某些情况下,你可以完成该任务而不需要中断现有的客户端程序。你应该注意添加一个成员到数据协定会更改从WCF导出的schema。客户端程序使用该schema去定义与服务收发SOAP消息中数据的格式。 然而,一部分使用其他技术开发的客户端程序会执行严格的schema检查。如果你的服务又必须支持这些类型的客户端程序,你不可以添加数据协定成员后,而不去更新这些客户端。 在这种情况下,与服务版本一样,你应该采取数据协定版本策略。关于数据协定的更多消息,请参考:http://msdn.microsoft.com/en-us/library/ms733832.aspx

    在下面的练习中,你将检查添加数据协定成员导致的结果,并且还可以看到WCF客户端如何处理这些新增加的数据成员。

    添加一个新成员到ProductData数据协定

    1. 转到Visual studio,然后开发ProductsServiceLibrary项目下的IProductsServcie.cs文件

    2. 为ProductData添加一个新成员

    201106012344596063.png

    3. 打开ProductsService.cs中,修改GetProduct方法

    201106012344598538.png

    4. 使用资管管理器,转到目录*\Chapter6\ProductsServiceVersion,删除ProductsService.svclog

    5. 启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;包括test2,新添加的字段没有影响到现有的客户端。

    6. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    7. 最后,我们再来看一下日志,可以发现,StandardCost已经包含在服务端的返回消息中。

    201106012344591570.png

    数据协定序列化器序列化数据协定的所有成员。现有的WCF客户端不仅不关注新字段StandardCost,而且还不执行schema验证,因此客户端直接忽略多余的字段。

    8. 退出ProductsService.log,并退出服务追踪查看器

    9. 使用svcutil重新生成客户端代理类

    201106012345002650.png

    201106012345005997.png

    10. 回到Visual studio;然后ProductsClient下的ProductsV2.cs文件,然后添加将上一步生成的ProductsV2.cs

    11. 修改Programm.cs
    201106012345014502.png

    12. 重新生成解决方案;然后启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过;包括test2,新添加的字段已经能显示

    201106012345019486.png

    13. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    添加一个操作到WCF服务并检查数据协定序列化

    1. 在Visual Studio中,打开ProductsServiceLibrary项目下的IProductsService.cs文件

    2. 按照下列方式修改IProductsServiceV2,添加新操作UpdateProductDetials

    20110602162556660.png

    3. 重新生成项目ProductsServiceLibrary,在Visual Studio command prompt中 使用svcutil重新生成客户端代理类

    20110601234501881.png

    201106012345019453.png

    4. 回到Visual studio;然后ProductsClient下的ProductsV2.cs文件,然后添加将上一步生成的ProductsV2.cs

    5. 修改Programm.cs
    201106012345018897.png

    6. 重新生成解决方案;然后启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过; 当test5,运行时,将出现一个消息框显示ProductNumber和更新后的ProductName。

    7. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    客户端程序使用数据协定定义,成功地将Product对象发送至WCF服务;但是如果客户端程序使用的某个版本的数据协定定义中缺少一个字段,会发生什么?

    添加另外一个成员到ProductData数据协定并检查其默认值

    1. 在visual studio中,打开ProductsServiceLibrary项目下的IProductsService.cs文件

    2. 添加下面的成员(红色矩形内)到ProductData数据协定

    20110601234503617.png

    3. 修改ProductService.cs类

    201106012345037237.png

    4. 重新生成解决方案;然后启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。所有的测试方法将成功通过; 当test5,运行时,将出现一个消息框显示FinshedGoodFlag为false。客户端程序仍旧使用旧版本的Product数据协定并且不会为FinsihedGoodFlag赋值。显示为false的原因是数据协定序列号器为其赋予了默认值。点击OK关闭消息提示框。

    5. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost

    当为数据协定添加新成员时,该新添加的成员会变更数据协定序列号后的顺序,此时,你应当考虑到现有的客户端程序。 如果客户端程序并没有为一个序列化后的对象的每个字段赋值,WCF将使用每个成员的数据类型的默认值,比如boolean类型为false,值类型为0,引用类型为null。如果默认值不可识别,你可以通过序列化回调方法自定义序列化和反序列化过程。 关于自定义序列化的信息,请参考http://msdn.microsoft.com/en-us/library/ms229752.aspx

    数据协定的兼容性

    如果你需要定义不同版本的数据协定,你需要在确保现有客户端程序兼容性的前提下去实现。DataMember特性提供了两个属性可以帮助你实现数据协定的兼容性

    • IsRequired 如果设置该属性的值为true,那么服务收到的SOAP消息必须包含该成员的值;默认情况下,该属性的值为falseWCF运行时将为任何为提供值的成员根据其数据类型提供该类型的默认值。
    • EmitDefaultValue 如果设置该属性值为true,客户端的WCF运行时将为该成员生产其默认值,如果该成员为包含在客户端发送的SOAP消息中。该属性的默认值为true

    如果你需要在一个未来版本的服务中严格地保持数据协定的一致性,你应该为数据协定的每个成员上设置IsRequired属性值为true;并且当你创建第一个版本的服务时应在数据协定的每个成员上设置EmdiDefaultValue属性值为false。如果一个成员在上一个版本中IsRequired为false,你绝对不要在新版本中设置该成员的IsRequired为true。 这样能使数据协定对老版本的客户端保持兼容性。

    还有一个进一步的问题需要考虑:客户端程序可能请求遵守服务数据协定的数据,然后修改数据并回传给服务。就好比在ProductsService服务中,先请求GetProduct方法从服务端得到ProductData,随后通过调用UpdateProductDetails将更新后的后的ProductData传回给服务? 如果客户端程序使用未包含新数据协定成员的老版本数据协定,客户端向服务回传数据时会发生什么情况? WCF运行时实现了一项技术叫做"round-tripping"用以确保新的数据成员不会丢失。你将在下面的练习中检查上述情况。

    调查WCF运行时如何指定round-tripping

    1. 在Visual Studio中,打开ProductsServiceLibrary项目下的ProductsServcie.cs文件

    2. 修改GetProduct方法

    20110601234504584.png

    在代码中,我们设置FinsihedGoodsFlag为true。请记住Boolean类型的默认值为false。

    3. 重新生成解决方案;然后启动ServcieHost,点击"开始"运行服务ProductsServcie;然后启动productsClient。你将会得到如下结果:

    201106012345055469.png

    在消息提示框中,FishishedGoodsFlag的值为true。FinishedGoodsFlag最先由服务端提供,然后它发送至客户端;然后由客户端回传至服务。尽管此时客户端使用老版本的代理(即客户端根本不知道FinishedGoodsFlag字段),客户端WCF运行时管理该字段,并使其保持初始值。 点击OK按钮,关闭消息提示框。

    4. 退出ProductsClient;切换到ProductsServiceHost,点击"停止"按钮停止ProductsServcie服务;然后退出ProductsServcieHost 。

    WCF运行时通过IExtensibleDataObject接口实现round-tripping。如果你检查ProductsV2.cs文件,你可以看到客户端代理ProductData类实现了该接口。该接口定义了一个名为ExtensionData的属性,该属性的类型为ExtensionDataObject。 ExtensionData属性专为客户端代理而使用,它可以读取和写入数据到一个类型为ExtensionObjectData私有成员extensionDataField,如下面所示:

    201106012345066416.png

    extensionDataField成员的作用好比一个"水桶",它可以容纳客户端接收到的所有未定义的数据成员;而不是丢弃这些数据成员;代理自动将这些数据成员存贮到该水桶中。当客户端代理向服务回传ProductData对象时,该对象包含了这个水桶中所有的数据成员。 如果你想停止该功能,你可以在客户端的端点行为中,设置dataContractSerializer元素的IgnoreExtensionDataObject属性值为 true。比如下面这样:

    201106012345064988.png

    你同样可以在服务端停止该特性:

    201106012345082871.png

    【总结】

    在本章,你了解了如何使用服务和数据协定来定义服务暴露给客户端程序的操作,这些操作可供客户端程序接收和回传至服务端。你也已经了解到为什么要仔细地设计服务和数据协定,以及如何创建一个新版本的服务和数据协定的同时保持与现有客户端程序之间的兼容性。

    【代码】 本章相关源代码下载

    【题外】

    谢谢热心同学的留言,从第七章开始,将根据各章内容的长短来调整文章的长短。确实文章太长了,你们看着累,我写着也累。并预祝大家端午节快乐:P

    转载于:https://www.cnblogs.com/yang_sy/archive/2011/06/02/2067533.html

    展开全文
  • 单位协定存款

    2011-07-22 20:57:53
    协定存款是指客户通过与银行签订《协定存款合同》,约定期限、商定结算账户需要保留的基本存款额度,由银行对基本存款额度内的存款按结息日或支取日活期存款利率计息,超过基本存款额度的部分按结息日或支取日人行...
        协定存款是指客户通过与银行签订《协定存款合同》,约定期限、商定结算账户需要保留的基本存款额度,由银行对基本存款额度内的存款按结息日或支取日活期存款利率计息,超过基本存款额度的部分按结息日或支取日人行公布的高于活期存款利率、低于六个月 定期存款利率的协定存款利率给付利息的一种存款。






    办理指南

      中国工商银行可与客户签订单位协定存款合同,在结算账户之上开立协定存款账户,并约定结算账户的额度,由银行将结算账户中超额度的部分转入协定存款账户,单独按照协定存款利率计息。

    一、开户

      单位应与开户行签订《协定存款合同》,合同期限最长为一年(含一年),到期任何一方如未提出终止或修改,则自动延期。凡申请在中国工商银行开立协定存款账户的单位,须同时开立 基本存款账户一般存款账户(以下简称“结算户”),用于正常经济活动的会计核算,该账户称为A户,同时电脑自动生成协定存款账户(以下简称B户)。如单位已有结算账户,则将原有的结算账户作为A户,为其办理协定存款手续。

    二、存入

      协定存款的起存金额请向当地工商银行咨询。

    三、支取

      协定存款账户的A户视同一般结算账户管理使用,可用于现金转账业务支出,A户、B户均不得透支,B户作为结算户的后备存款账户,不直接发生经济活动,资金不得对外支付。

    四、结息

      每季末月二十日或协定存款户(B户)销户时应计算 协定存款利息。季度计息统一于季度计息日的次日入账;如属协定存款合同期满终止续存,其销户前的未计利息于季度结息时一并计入结算户(A户)。

    五、销户

       协定存款合同期满,若单位提出终止合同,应办理协定存款户销户,将协定户(B户)的存款本息结清后,全部转入基本存款账户或一般存款账户中。结清A 户,B户也必须同时结清。在合同期内原则上客户不得要求清户,如有特殊情况,须提出书面声明,银行审核无误后,办理清户手续。

    六、注意事项

      1. 如开户行已开办通存通兑业务的,协定存款账户(A户)内资金可以在其它已联网机构使用。   2. 协定存款余额两年以上(含两年)低于起存金额的,将利息结清后,作为一般账户处理,不再享受优惠利率。   3. 协定存款账户连续使用两年以后仍需继续使用,须与银行签订《协定存款合同》。

    编辑本段单位协定存款的规定

      协定存款是在原来 单位活期存款基础上延伸出来的,协定存款帐户与其相对应的活期存款帐户有着密切的联系,在活期存款帐户的存款超过约定额度后,超过额度部分可享受协议存款利率,若活期帐户销户,协定存款帐户也须同时销户。   协定存款的最低约定额度为人民币10万元,客户可根据实际情况与银行约定具体的基本额度。   协定存款帐户分A户(结算户)与B户(协定户),A户按结算日中国人民银行公布的活期存款利率计息,B户按结算日中国人民银行公布的协定存款利率计息。   协定存款帐户不是一个独立存款帐户,客户可以通过结算户办理日常结算业务,协定存款帐户的操作和管理由银行负责。   协定存款的A户视同 基本存款帐户或一般存款帐户管理使用,A户、B户均不得透支。   协定存款帐户月均余额两年或两年以上低于最低约定额度的,将 利息结清后,作为基本存款帐户或 一般存款帐户处理,不再享受协定存款利率。客户在合同期内如需清户,必须提出书面声明,银行审核无误后,方可办理。

    来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/9240380/viewspace-702891/,如需转载,请注明出处,否则将追究法律责任。

    转载于:http://blog.itpub.net/9240380/viewspace-702891/

    展开全文
  • 、如何发布网页? 要在web上发布网页,需要个在web上全天候工作的服务器。 www.starbuzzcoffe.com www这部分是域中个特定服务器的名字。 starbuzzcoffe.com这部分是域名。不同的域结尾有不同的用途: .con \...

    一、如何发布网页?

    要在web上发布网页,需要一个在web上全天候工作的服务器。
    www.starbuzzcoffe.com
    www这部分是域中一个特定服务器的名字。
    starbuzzcoffe.com这一部分是域名
    不同的域结尾有不同的用途: .con \ .org\.goc\.edu
    另外还可以表示不同国家 .co.uk\.co.jp等
    www.starbuzzcoffe.com是一个网站名
    只有starbuzzcoffe.com部分就是域名。
    一个域名可以用于多个网站

    当找到了托管公司,得到了一个域名,另外已经找好了一个服务器来维护你的web页面
    就要开始“搬家”了。
    在web上,把文件从你自己的根文件夹转移到web服务器上的根文件夹。
    根文件夹是页面的顶级文件夹。在web服务器上,根文件夹更为重要,因为根文件夹中的内容可以从网上访问到。
    把这个网站的所有页面都放在一个文件夹里,这个文件夹称作根文件夹。现在我们要把它们统统复制到服务器的根文件夹。
    实际上你要把你自己的根文件夹下的所有页面、文件和文件夹都复制到服务器上

     

    二、FTP文件传输协议--File Transfer Protocol

    怎么把文件传送到web服务器呢?大多数托管公司支持一种叫做FTP的文件传输方法,

    1、首先,使用ftp连接到你的服务器,要完成连接,需要托管公司提供的一个用户名和口令。
    文件夹和目录这两个词可以互换,大多数ftp应用使用“目录”。
    2、使用cd命令把当前目录切换到文件传输的目标目录 eg: cd starbuzz
    3、使用put命令将文件上传到服务器 eg:put index.html
    4、还可以用mkdir命令在服务器上创建一个新目录


    5、还可以用get命令获取文件


    对于小型网站,每次想要在计算机上编辑文件,每次想要更新网站时都要上传这些文件
    对于比较大的网站,通常会创建一个测试网站和一个真实网站,这样在转向真实网站之前,可以先在测试网站上预览修改的结果。

    http://www.stanuzzcoffee.com/index.html
    web页面的地址都以http开头,www.starbuzzcoffee.com,/表示根文件夹,index.html是页面的文件名

    三、URL

    在浏览器中输入的web地址称为URL,或统一资源定位符(Uniform Resource Locators)。
    URL是一个全局地址,可以用来定位web上的任意资源,包括html页面、音频、视频和很多其他形式的web内容。
    要定位web上的某个资源,只要你知道维护这个资源的服务器,以及资源的绝对路径,就可以创建一个URL,
    让web浏览器使用某个协议为你获取这个资源,通常会使用HTTP
    http://www.stanuzzcoffee.com/index.html 
    这就是一个URL 
    http:这一部分指出用来获取资源的协议。
    第二部分是网站名,第三部分是从根文件夹到资源的绝对路径。

    四、HTTP协定

    HTTP也称为超文本传输协议(HyperText Transfer Protocol)。这是在web上传输超文本文档的公认的一种方法(协定)。
    超文本文档通常只是指HTML页面,但这个协议实际上还可以用来传输图像或web页面可能需要的任何其他文件。
    每次在浏览器的地址栏中输入一个URL时,浏览器就会使用HTTP向服务器请求响应的资源。
    如果服务器找到这个资源,就会把它返回给浏览器,由浏览器显示。如果找不到就显示#404错误。
     

    五、绝对路径

    绝对路径告诉服务器如何从你的根文件夹达到某个特定的页面或文件,如果服务器没有的到绝对路径,它就不知道去那里查找。
    可以在<a>元素的href属性中放入相对路径。后台浏览器会根据这个相对路径和所单机页面的路径创建一个绝对路径。
    web服务器看到的都是绝对路径。

    六、如果浏览器向web服务器请求一个目录而不是文件时会发生什么?

    eg:http://www.starbuzzcoffee.com/
    web服务器接收到一个类似这样的请求时,它会尝试查找这个目录中的一个默认文件。
    通常默认文件名为index.html或default.htm
    如果服务器接收到了一个连根目录都没有的URL时,就会自动给后面加上/

    URL并不只是在浏览器中输入,还可以在HTML中使用。
    链接页面有两种方式,相对路径和绝对路径。

    七、file协议

    浏览器从你的计算机本地读取文件时会使用file协议。
    比如:file:///D:/starbuzz/Head%20First%20HTML与CSS%20第2版%20(2).pdf
    file协定是三个/

    八、端口

    http://www.mydomain.com:8000/index.html
    :8000是一个可以放在HTTP URL中的可选的“端口”。端口就像是这个地址的邮箱号。
    通常web上的所有东西都会传送到一个默认端口80,但有时web服务器会配置为在另外一个不同的端口接受请求,这种情况经常在测试服务器上出现。
    正常的web服务器几乎都在端口80接受请求,默认80。

     

     

    展开全文
  • 公报介绍说,总协定将从以下6方面确保个人数据在传输过程中得到保护:、明确限定使用个人数据的目的只能是用于防范、侦查、调查和起诉刑事犯罪行为;二、在相关数据传输到美国和欧盟成员国以外的国家和国...
  • 站对站密钥协定

    2011-07-25 23:00:19
    站对站密钥协定是基于Diffie-Hellman的种方法。它使用带有公钥证书(参看后面这部分)的数字签名,在爱丽丝和鲍勃之间建立会话密钥,如图15-12所示。下面就是这种方法的步骤:●算出R1后,爱丽丝把R1发送给鲍勃(图...
  • 也正是因此,能生产高端光刻机的厂商非常少,到最先进的14nm光刻机就只剩下ASML,日本佳能和尼康已经基本放弃第代EUV光刻机的研发。 相比之下, 国内光刻机厂商则显得非常寒酸 ,处于技术领先的上海微电子装备有限...
  • 公众号:参江湖 在基建和地产双驱动下,我 们预计工程机械板块未来仍将保持较高景气度。由于下游需求逐步回 暖,制造业整体环境改善,工业机器人产量增速呈 V 型走势,我们预 计工业机器人行业复苏趋势有望持续。...
  • ZigBee 是种低速短距离传输的无线网络协定,底层采用 IEEE 802.15.4 标准规范的媒体存取层与实体层。ZigBee 主要特色有低速、低耗电、低成本,可支援大量网络节点、支援多种网络拓扑,而且具有低复杂度、快速、可靠...
  • Interconnection security agreement 相互联系安全协定,是个专门定义至少两个公司之间计划、建立、维护和断开安全连接的技术和安全需求的协议。 0x09 ALE & SLE ARO(年度发生概率)和SLE(单次损失期望)通过...
  • Day55 1.brook[n....v....eg: He is so serious that he would no interruptions. ...化妆粉盒,合约,协定] 3.compel[v.强迫,胁迫] eg: Nothing can compel me to do such a thing.   &nb
  • 区块链大核心算法

    千次阅读 2018-06-18 14:02:02
    区块链核心算法:拜占庭协定 拜占庭的故事大概是这么说的:拜占庭帝国拥有巨大的财富,周围10个邻邦垂诞已久,但拜占庭高墙耸立,固若金汤,没有个单独的邻邦能够成功入侵。任何单个邻邦入侵的都会失败,同时也...
  • !” …… 我带着我的小姐跟小文跟他的小姐讫玩伏了大话骰子,据说这是SALES的必建课,实在就是TM望谁能骗己。这我可是高手儿,多少十轮下来,小皂就喝得钝不言了。“不来了,不去了,下次再跟你丫逝世...
  • 由于忙于硕士毕业相关事宜,非常久没有更新博客,现在回来更新博客,第篇还是资源整理类。 文章目录1 Coding:2 Paper: 1 Coding: 1.USGS EROS数据分发系统的API接口,非官方API。 espa api 2.R语言包Taipan,...
  • 章 传输层 5-01 试说明运输层在协议栈中的地位和作用,运输层的通信和网络层的通信有什么重要区别?为什么运输层是必不可少的? 答:运输层处于面向通信部分的最高层同时也是用户功能中的最低层,向它上面的应用层...
  • 20162317袁逸灏 第十周实验报告:实验 网络编程与安全 实验内容 客户端与服务器 加密 实验要求 创建个类实现中缀表达式转化为后缀表达式 结队编程:人负责客户端,另人负责服务器 实现客户端与服务器的...
  • 小编之前也是制造业的苦工,做技术的话工资涨幅涨不过货币贬值,做管理自己又不是那个性格也没有背景,当时也想过考个含金量的证来改善前途,找来找去只有电器工程师还不错但对专业卡得太死,其他容易考的证基本都...
  • 区块链技术大核心算法

    千次阅读 2018-06-16 11:36:54
    -----转载2018-01-15 14:49技术/操作系统区块链核心算法:拜占庭协定拜占庭的故事大概是这么说的:拜占庭帝国拥有巨大的财富,周围10个邻邦垂诞已久,但拜占庭高墙耸立,固若金汤,没有个单独的邻邦能够成功入侵...
  • 区块链技术的大核心算法

    千次阅读 2019-03-14 11:47:06
    区块链核心算法:拜占庭协定 拜占庭的故事大概是这么说的:拜占庭帝国拥有巨大的财富,周围10个邻邦垂诞已久,但拜占庭高墙耸立,固若金汤,没有个单独的邻邦能够成功入侵。任何单个邻邦入侵的都会失败,同时也...
  • 鸦片战争后的形式广州人的仇外心理口通商以后,广州与外国人的冲突最多,感情最坏。其原因有1、英国人在广州受气多年,鸦片战争让英国人“腰杆子硬起来”,对中国人带几分仇视;2、《南京条约》协定了关税,广州的...
  • 10、Linux(

    2017-06-02 16:56:00
    种使用于分散式文件系统的协定,由Sun公司开发,于1984年向外公布。功能是通过网络让不同的机器、不同的操作系统能够彼此分享个别的数据,让应用程序在客户端通过网络访问位于服务器磁盘中的数据,是在类Unix系统...
  • 区块链技术大核心算法,读懂大核心算法就变成区块链专家  近日,在加密货币经历“混乱时期... 区块链核心算法:拜占庭协定  拜占庭的故事大概是这么说的:拜占庭帝国拥有巨大的财富,周围10个邻邦垂诞已...
  • excel学习笔记 数据分析类型: 描述性统计分析,概括表述事物关系 探索性数据分析,发现数据的新特征 验证性数据分析,对假设...2、进行数据收集,渠道可分为个:企业商家的内部数据库,互联网的公开数据,出版物查
  • 、简介 本工具是由复旦NLP中的时间分析功能修改而来,做了一些细节和功能的优化,经SpringBoot封装成web工具。 泛指时间的支持,如:早上、晚上、中午、傍晚等。 时间未来倾向。 如:在周五输入“周一...
  • WCF学习记录

    2019-03-08 13:39:34
    由浅入深,循序渐进,对WCF有个整体的了解。...学习过程总结: .WCF 程序最基本的...(2) 用个服务类实现服务协定接口和接口中的操作协定。 (3) 建立个宿主程序用来寄存服务。 [针对控制台程序宿主] (4) 为服务...
  • 章一 软件测试的背景

    千次阅读 2007-01-17 00:01:00
    章一 软件测试的背景、软件失败的术语缺点defect,偏差variance,故障fault,失败...二、软件缺陷的定义1、产品说明书(product specification):是软件开发小组的协定。它对开发的产品进行定义,给出产品的

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,284
精华内容 1,713
关键字:

五一六协定