转-WCF详解文档最新
一、分布式
分布式计算是一门计算机科学,它研究如何把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分,然后把这些部分分配给许多计算机进行处理,最后把这些计算结果综合起来得到最终的结果。分布式网络存储技术是将数据分散的存储于多台独立的机器设备上。分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息,不但解决了传统集中式存储系统中单存储服务器的瓶颈问题,还提高了系统的可靠性、可用性和扩展性。
二、面向服务的体系结构(Service-Oriented Architecture,SOA)
SOA是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种这样的系统中的服务可以一种统一和通用的方式进行交互。
面向服务的体系结构传统的面向对象的模型的替代模型,面向对象的模型是紧耦合的,现在的 SOA 已经有所不同了,因为它依赖于一些更新的进展,这些进展是以可扩展标记语言(Extensible Markup Language,XML)为基础的。通过使用基于 XML 的语言(Web 服务描述语言[Web Services Definition Language,WSDL])来描述接口,服务已经转到更动态且更灵活的接口系统中。
面向服务的体系结构作用:
对 SOA 的需要来源于需要使业务 IT 系统变得更加灵活,以适应业务中的改变。通过允许强定义的关系和依然灵活的特定实现,IT 系统既可以利用现有系统的功能,又可以准备在以后做一些改变来满足它们之间交互的需要。
对于面向同步和异步应用的,基于请求/响应模式的分布式计算来说,SOA是一场革命。一个应用程序的业务逻辑(business logic)或某些单独的功能被模块化并作为服务呈现客户端。例如,服务的接口和实现相独立。应用开发人员或者系统集成者可以通过组合一个或多个服务来构建应用,而无须理解服务的底层实现。举例来说,一个服务可以用.NET或J2EE来实现,而使用该服务的应用程序可以在不同的平台之上,使用的语言也可以不同。
三、WCF
1、WCF简介
WCF是Windows Communication Foundation的缩写,原来代号为Indigo,它是MS为SOA(Service Oriented Architecture 面向服务架构)而设计的一套完整的技术框架。利用它能够开发出分布式(Distributed)应用程序, WCF是目前微软平台下最先进的SOA框架,具有很多优点,总结起来,其最大的特征在于:
- 1. 充分整合了原来的.NET Remoting,ASP.NET Xml Web Service(ASMX, Web服务),MSMQ(Message Queuing 消息队列),WSE(Web Services Enhancements),Enterprise Service(COM+)等多项分布式技术。
- 2. WCF是Microsoft为构建面向服务的应用提供的分布式通信编程框架,是.NET Framework 3.5的重要组成部分。使用该框架,开发人员可以构建跨平台、安全、可靠和支持事务处理的企业级互联应用解决方案。
- 3. WCF提供了现有ASP.NET Web Service、.NET Remoting、Message Queuing和Enterprise Services技术的全部功能。现有解决方案均有各自的优点,也有相应的缺点,WCF综合了上述技术的优点,形成了一种新的技术。
WCF简介之WCF提供了如下特性:
- 1. 驻留组件和服务:与使用定制主机、.NET Remoting 和WSE一样,可以把WCF服务放在ASP.NET库中、Windows 服务上、COM+过程中,或Windows Form程序上,进行对等计算。
- 2. 声明性操作:不是必须从基类中派生(.NET Remoting中远程对象从MarshalByRefObject派生),可以使用属性定义服务,类似与ASP.NET的Web服务(以[WebMethod]属性说明方法,标记为可通过Web服务访问)。
- 3. 通信信道:.NET Remoting 在改变通信信道方面非常灵活,而WCF是一个很好的替代品,它提供了相同的灵活性。WCF可以使用HTTP、TCP、IPC(Internet Process Connection进程间通信)信道进行通信。并且支持使用UDP信道。
- 4. 安全体系结构:要实现独立于平台的Web服务,必须使用标准的安全环境。现有提供的标准是用WSE2.0实现的,在WCF中继续使用它WS-Security,WS-Trust和WS-SecureConversation均被添加到SOAP消息中,以用于用户认证,数据完整性验证,数据隐私等多种安全因素。
- 5. 可扩展性:.NET Remoting 有丰富的扩展功能,它可以创建定制的信道、格式化器和代理程序,还可以在客户机和服务器的消息流中插入功能。WCF提供了类似的可扩展性,但这些扩展功能使用SOAP标题实现。
- 6. 兼容性:使用WCF,无需重写分布式解决方案,WCF可以与已有的技术很好的集成。WCF提供一个信道可以使用DCOM与所服务的组件通信。以ASP.NET Web服务创建独立于平台的服务也可以很方便实现与WCF的集成。
WCF简介之WCF作用:
在win32中,应用程序是运行在进程(Process)的线程(Thread)中的,.Net平台出现之后,出现了AppDomain(应用程序域),其实就相当于在进程和线程之间又有了一层包装,类似于子进程的概念,在一个进程(或应用程序域)中的对象能进行直接的访问和控制,但超出这个范围,便不能进行直接的访问和控制了,所以进程(或应用程序域)有一定的隔离作用,如果是分布在不同网络,不同操作系统上的不同进程,他们进行通讯的难度就更大,而分布式应用程序要求的就是将原本被隔离的作用域通过一定的契约联系起来,从而达成多个系统的沟通协作。比如在Win32时代,经常使用映射内存文件来实现进程之间的互操作问题,还有在Windows程序中,用Windows消息wm也能实现不同进程之间的通讯问题,但这些都有局限性,因为他们不能很好的解决跨平台,跨网络问题,而WCF可以。
2、WCF概述
2.1 WCF体系结构
应用程序 |
WCF拥有一个非常灵活的分层体系结构,分布式应用程序可以使用高级API或者低级API编写。高级API或者服务层可以用于调用方法和事件。服务层把这些高级的抽象代码转换为消息,以使用低级API上的信道和端口。
图1中显示了WCF应用程序的各个层。
契约模型 |
数据契约 |
消息契约 |
服务契约 |
策略绑定与服务 |
消息传输模式 |
WS安全信道 |
WS可靠传输信道 |
编码:二进制/MTOM/文本/XML |
HTTP信道 |
TCP信道 |
事务流信道 |
命名管道 |
MSMQ |
激活与宿主环境 |
Windows Activation服务 |
.EXE应用程序 |
Windows服务 |
COM+ |
服务运行时模型 |
分流 |
错误 |
元数据 |
实例 |
消息 |
事务 |
分发 |
并发 |
参数过滤 |
图1 WCF体系结构图
WCF提供了对可靠性、事务性、并发管理、安全性以及实例激活等技术的有力支持,而这些支持均依赖于如图2所示的WCF构架。
在客户端,分布式应用通过一个代理来转发对宿主端所提供服务的调用,而代理拥有和服务相同的操作接口,另外还有一些附加的代理管理方法。这也就意味着客户端从来不会直接调用服务,即便这个服务就在本机的内存中。当客户端代理接收到来自客户端的调用请求后,它将消息通过信道链向下传递。每个信道都会执行相应的消息的调用前处理,例如对消息的编码、提供可靠的会话、对消息进行加密等。客户端的最后一个信道则是传输信道,根据配置的传输方式发送消息给宿主。
在宿主端,消息同样通过信道链进行传输。与客户端信道相对应,宿主端信道也会对消息执行相应的宿主端的调用前处理,例如对消息的解码、提供会话管理、对消息进行解密等。宿主端的最后一个信道则负责将消息发送给消息分发器(Dispatcher),由分发器负责调用服务的实例。
客户端 |
信道 |
信道 |
传输信道 |
传输信道 |
信道 |
信道 |
消息分发器 |
服务 |
enterPoint入口点 |
图2 WCF构架示意图
2.2 WCF服务框架
宿主(Host),即承载WCF Service运行的环境。可用的宿主环境包括:
(1) 自承载方式:在控制台应用程序与基于WinForm的应用程序中都可以使用这种方式;
(2) 系统服务方式:服务可以随着操作系统的启动而自动启动;
(3) IIS方式:与Web Services 的部署方式类似,由请求消息来激活服务,但只支持HTTP方式的绑定;
(4) WAS(Windows Process Activation Service)方式:这个宿主是 IIS7 的一部分,只有 Windows Vista 和 Windows Server 2008提供默认支持,它支持几乎所有的通讯协议并提供了应用程序池、循环回收、空闲管理、身份管理、隔离等强大的功能。
服务类(Service Class)是指一个标记了一些WCF特有的属性的类,它包含了对服务的业务逻辑的具体实现。
端点(Endpoints)是WCF实现通信的核心要素,客户端和服务端都通过端点来交换消息,WCF 允许我们为服务添加多个绑定和端点。端点由地址(Address)、绑定(Binding)以及契约(Contract)三部分组成,如图5所示。在WCF中,类ServiceEndpoint代表了一个Endpoint,在类中包含的EndpointAddress,Binding,ContractDescription类型分别对应端点中的地址、绑定以及契约。
绑定 binding |
契约 contract |
地址 address |
地址:每个服务都会关联到一个唯一的地址,因此地址定位和唯一标志了一个端点,其主要提供了两个重要信息: 服务位置以及传送协议。在WCF 中,地址由System.ServiceModel.EndpointAddress对象来表示,其包括URI、Identity、Headers三个要素。
绑定:绑定提供了一种可设置的方式来选择传输协议、消息编码、通讯模式、可靠性、安全性、事务传播以及交互方式等。例如在传输协议上可以选择HTTP/HTTPS、TCP、P2P、IPC甚至是MSMQ等方式。消息编码上可以选择使用纯文本方式来确保互操作能力,或者选择二进制编码来优化性能,或者使用 MTOM来提高负载能力,甚至是自定义编码方式。 WCF中提供了BasicHttpBinding、NetTcpBinding、NetPeerTcpBinding、NetNamedPipeBinding、WSHttpBinding、WSFederationHttpBinding、WSDualHttpBinding 、NetMsmqBinding以及MsmqIntegrationBinding九种标准类型的绑定。
契约:契约是用来描述服务功能的一种平台中立的标准方式,WCF 所有服务都需要实现一个或多个契约。WCF 定义了四种类型的契约:
(1) 服务契约 (Service Contracts): 定义了客户端可以使用哪些服务操作。
(2) 数据契约 (Data Contracts): 定义了服务传输的数据类型。WCF 定义了一些隐式数据契约,比如 int、string 等,但更多时候需要使用 DataContractAttribute 显式定义那些自定义类型数据的数据契约。
(3) 错误处理契约 (Fault Contracts): 定义了服务引发的错误信息,以及如何将这些异常传递给客户端。
(4) 消息契约 (Message Contracts): 允许直接操控服务的消息内容和格式。
一般情况下,应当用接口来定义服务契约,尽管也可以使用类。将服务契约定义为接口基于如下几点优点:
(1) 便于契约的继承,不同根的类型可以自由实现相同的契约;
(2) 同一服务类型可以同时实现多个契约;
(3) 类似于接口隔离原则,可以随时修改服务类型的实现而不影响其它实现;
(4) 便于制定版本升级策略,新、旧版本的服务契约可以同时使用而互不影响。
在WCF中,对于自承载的服务,端点的相关的信息可以有代码实现与配置文件两种定义方式。而对于IIS承载服务,端点的相关的信息一般定义在虚拟根目录下的Web.Config文件中。一般来讲,使用配置文件来定义端点相关信息是更为灵活、更为推荐的一种方式,其可以在不修改代码、不重新发布系统的情况下对服务的地址、绑定和契约等参数进行修改(因为修改config类型文件的内容是不需要重新编译和重新部署的)。
在下面的代码中具体说明了如何定义宿主端的端点相关信息。其中地址为http://localhost:8080/HelloService,契约为WCFService命名空间下的IEcho接口,绑定采用的是WSHttpBinding方式。值得注意的是,代码中的Service为相对地址,http://localhost:8080/提供的是基址,当然去掉基址直接将address设为http://localhost:8080/Service也是可以的。代码中还添加了名为MyServiceTypeBehaviors的行为配置,其将serviceMetadata节中的httpGetEnabled属性设为了true,目的是为了自动透过 HTTP-GET发布服务的元数据。WCF提供的另外一种发布元数据的方式是使用专门的 MEX 端点。
<system.serviceModel>
<services>
<service name=" WCFService. Service " behaviorConfiguration="MyServiceTypeBehaviors" >
<endpoint contract="WCFService. IEcho" binding="wsHttpBinding" address="Service"/>
<host>
<baseAddresses >
<add baseAddress="http://localhost:8080/"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceTypeBehaviors" >
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
2.3 WCF服务模型
WCF服务通过XML文档进行描述,这些文档主要由三个部分组成:
- 服务部分说明服务位于何处。
- 绑定部分说明服务所能理解的标准通信协议。
- 第三个部分列出服务所能提供的所有操作,它定义了服务根据接收的消息所发出的响应信息。
在WCF模型中这三部分被称为:地址(address)、绑定(binding)和契约(contract),为易记起见可简称为ABC,即利用服务模型创建软件通信功能而需要遵循的三个步骤。
建立WCF模型,首先从建立契约开始(主要引用.net Framework里的System.ServiceModel)
先写一个接口interface
public interface IEcho
{
string Echo(string input);
}
将接口指定为WCF契约
[ServiceContract]
public interface IEcho
{
[OperationContract]
string Echo(string input);
}
接下来就可实现契约接口,自定义类,如果某个类实现了WCF契约接口,它就成为服务类型(service type),并可以通过ServiceBehavior特性给服务类型添加行为来控制。
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service : IEcho
{
string Echo(string input)
{ return input; }
}
最后将服务承载(host)在一个应用程序域中。IIS和其他任何.NET程序都可以为承载服务提供应用程序域,承载服务使用WCF模型提供的ServiceHost类:
using (ServiceHost host = new ServiceHost(typeof(Service)))
{
host.Open();
Console.WriteLine("服务已启动...");
Console.ReadKey(true);
host.Close();
}
客户端 |
代 理 |
服务 |
端 点 |
OneWay |
客户端 |
代 理 |
服务 |
端 点 |
Request/Reply |
客户端 |
代 理 |
服务 |
端 点 |
Duplex |
(1)OneWay:这种消息交换模式在调用方法后会立即返回而不需要等待服务端的消息返回。
(2) Request/Reply:这种消息交换模式属于同步调用。在调用服务方法后需要等待服务端的消息返回。
(3)Duplex:这种消息交换模式具有客户端与服务端双向通信的功能,同时它的实现还可以使消息交换具有异步回调的作用。
2.4客户端调用
在设置完宿主端端点之后,同样也必须为分布式应用程序定义客户端的端点,而且只有当客户端的端点与宿主端的某个端点相互匹配时,客户端的请求才能被宿主端所监听到。如果服务提供了发布元数据,那么利用.NET Framework 3.0 SDK所提供的SvcUtil.exe工具可以很轻松的自动生成与宿主端对应的客户端代理以及客户端配置文件。比如,运行宿主端应用程序,然后打开Visual Studio 2005命令提示符,键入SvcUtil http://localhost:8080,便可以在当前目录下得到客户端代理文件HelloWorld.cs与客户端配置文件output.config。另外一种简便直观的可视化工具是SDK 所附带的SvcConfigEditor.exe(C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin目录下,XP系统),使用这个工具可以非常方便地创建或修改宿主端和客户端的配置文件。
生成好客户端代理与配置文件后,在代码中直接使用客户端代理对象即可。
using (HelloClient client = new HelloClient())
{
client.Hello();
}
Console.ReadKey();
另外一种创建客户端代理的方式是使用ChannelFactory动态的来创建。虽然WCF提供了这种方式,但是在实际开发中并不推荐使用它,毕竟 ChannelFactory 直接依赖于契约,而这恰恰违背了 SOA 中边界隔离的原则。利用服务器端与客户端之间的Channel来创建客户端代理的代码举例如下:
ServiceEndpoint httpendpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IEcho)),
new WSHttpBinding(), new EndpointAddress("http://localhost:8080/HelloService"));
using (ChannelFactory< IEcho > factory =` ChannelFactory< IEcho >(httpendpoint))
{
IEcho service = factory.CreateChannel();
service.Hello();
}
Console.ReadKey();
3、WCF-Binding简介
3.1 Binding描述了哪些层面的信息
一个Binding包含着丰富的信息,每种信息都描述了服务端和客户端交互过程中的一方面,如下表所示,Binding描述了这些层面的信息:
层次 |
备注说明 |
Transactions(事务) |
TransactionFlowBindingElement,用于指定事务流程 |
Reliability(信赖) |
ReliableSessionBindingElement,用于指定对会话方式 |
Security(安全) |
SecurityBindingElement,指定安全方式 |
Encoding(编码) |
Text, Binary, MTOM, Custom,指定数据传输格式 |
Transport(传输) |
TCP, Named Pipes, HTTP, HTTPS, MSMQ, Custom,指定传输方式 |
3.2 WCF中的绑定
绑定描述了服务传输的通信方式,使用绑定可以指定:传输的协议,安全要求,编码方式,事务处理要求,可靠性等。
- 基本绑定(Basic Binding)
由BasicHttpBinding类提供。基本绑定能够将WCF服务公开为旧的ASMX Web服务,使得旧的客户端能够与新的服务协作。如果客户端使用了基本绑定,那么新的WCF客户端就能够与旧的ASMX服务协作。 - TCP绑定
由NetTcpBinding类提供。TCP绑定使用TCP协议实现在Intranet中跨机器的通信。TCP绑定支持多种特性,包括可靠性、事务性、安全性以及WCF之间通信的优化。前提是,它要求客户端与服务都必须使用WCF。 - 对等网绑定
由NetPeerTcpBinding类提供。它使用对等网进行传输。对等网允许客户端与服务订阅相同的网格(Grid),实现广播消息。因为对等网需要网格拓扑(Grid Topology)与网状计算策略(Mesh Computing Strategies)方面的知识。 - IPC绑定
由NetNamedPipeBinding类提供。它使用命名管道为同一机器的通信进行传输。这种绑定方式最安全,因为它不能接收来自机器外部的调用。IPC绑定支持的特性与TCP绑定相似。 - Web服务(WS)绑定
由WSHttpBinding类提供。WS绑定使用HTTP或HTTPS进行传输,为基于Internet的通信提供了诸如可靠性、事务性与安全性等特性。 - WS联邦绑定(Federated WS Binding)
由WSFederationHttpBinding类提供。WS联邦绑定是一种特殊的WS绑定,提供对联邦安全(Federated Security)的支持。 - WS双向绑定(Duplex WS Binding)
由WSDualHttpBinding类提供。WS双向绑定与WS绑定相似,但它还支持从服务到客户端的双向通信。 - MSMQ绑定
由NetMsmqBinding类提供。它使用MSMQ进行传输,用以提供对断开的队列调用的支持。 - 不同的绑定支持不同的特性。以WS开头的绑定MSMQ集成绑定(MSMQ Integration Binding)
由MsmqIntegrationBinding类提供。它实现了WCF消息与MSMQ消息之间的转换,用以支持与旧的MSMQ客户端之间的互操作。
是独立于平台的,支持Web服务的规范。以Net开通的绑定使用的是二进制格式,使得.NET应用程序之间的通信有很好的性能。其他特性有支持会话,可靠的会话,事务处理和双向通信。
3.3绑定的传输协议与编码格式
3.4绑定的合理使用
- BasicHttpBinding: 一个绑定,适用于与符合 WS-Basic Profile 的 Web 服务(例如基于 ASP.NET Web 服务 (ASMX) 的服务)进行的通信。此绑定使用 HTTP 作为传输协议,并使用文本/XML 作为默认的消息编码。
- WSHttpBinding: 一个安全且可互操作的绑定,适合于非双工服务约定。
- WS2007HttpBinding: 一个安全且可互操作的绑定,可为 Security, ReliableSession 的正确版本和 TransactionFlow 绑定元素提供支持。
- WSDualHttpBinding: 一个安全且可互操作的绑定,适用于双工服务协定或通过 SOAP 媒介进行的通信。
- WSFederationHttpBinding: 一个安全且可互操作的绑定,支持 WS 联合协议并使联合中的组织可以高效地对用户进行身份验证和授权。
- WS2007FederationHttpBinding: 一个安全且可互操作的绑定,它派生自 WS2007HttpBinding 并支持联合安全性。
- NetTcpBinding: 一个安全且经过优化的绑定,适用于 WCF 应用程序之间跨计算机的通信。
- NetNamedPipeBinding: 一个安全、可靠且经过优化的绑定,适用于 WCF 应用程序之间计算机上的通信。
- NetMsmqBinding: 一个排队绑定,适用于 WCF 应用程序之间的跨计算机的通信。
- NetPeerTcpBinding: 一个支持多计算机安全通信的绑定。
- WebHttpBinding:可用于为通过 HTTP 请求(而不是 SOAP 消息)公开的 WCF Web 服务配置终结点。
下面的表格分别标明了上表中的Binding在互操作性(Interoperability), 安全性(Security), 是否支持会话(Session), 是否支持事务(Transactions)和是否为全双工(Duplex)上不同。
Binding |
Interoperability |
Security |
Session |
Transactions |
Duplex |
BasicHttpBinding |
Basic Profile 1.1 |
(None), Transport, Message |
None, (None) |
None |
n/a |
WSHttpBinding |
WS |
Transport, (Message), Mixed |
(None), Transport, Reliable Session |
(None), Yes |
n/a |
WSDualHttpBinding |
WS |
(Message) |
(Reliable Session) |
(None), Yes |
Yes |
WSFederationHttpBinding |
WS-Federation |
(Message) |
(None), Reliable Session |
(None), Yes |
No |
NetTcpBinding |
.NET |
(Transport), Message |
Reliable Session, (Transport) |
(None), Yes |
Yes |
NetNamedPipeBinding |
.NET |
(Transport) |
None, (Transport) |
(None), Yes |
Yes |
NetMsmqBinding |
.NET |
Message, (Transport), Both |
(None) |
(None), Yes |
No |
NetPeerTcpBinding |
Peer |
(Transport) |
(None) |
(None) |
Yes |
MsmqIntegrationBinding |
MSMQ |
(Transport) |
(None) |
(None), Yes |
n/a |
4、WCF注意点
4.1注意:服务代理要及时关闭
WCF通过信道栈实现了消息的编码、传输及基于某些特殊功能对消息的特殊处理,而绑定对象是信道栈的缔造者,不同的绑定类型创建出来的信道栈具有不同的特性。就对会话的支持来讲,我们可以将信道分为以下两种:
- 会话信道(Sessionful Channel):会话信道确保客户端和服务端之间传输的消息能够相互关联,但是信道的错误(Fault)会影响后续的消息交换;
- 数据报信道(Datagram Channel):即使在同一个数据报信道中,每次消息的交换都是相互独立,信道的错误也不会影响后续的消息交换。
大部分绑定类型(BasicHttpBinding除外),在默认的情况下创建的都是会话信道。对于WCF客户端来说,如果进行基于会话信道的服务调用,有一些问题需要引起足够的重视,如果使用不当,不但影响客户端本身的服务调用,还会对服务处理请求的吞吐量造成很大的影响。
4.2服务代理的关闭与并发会话(Concurrent Sessions)的限制
WCF对一个ServiceHost所能处理的并发会话作了限制,在默认情况下,允许的最大并发会话数量为10。
服务允许的最大并发会话通过ServiceThrottlingBehavior服务行为的MaxConcurrentSessions属性进行配置
注意:
只有WsHttpBinding的安全(Security)或可靠会话(Reliable Session)开启的情况下,创建的信道才具有会话的特性,否则创建出来的信道是不能支持信道的。在默认的情况下,WsHttpBinding的安全模式(SecurityMode)为基于消息的安全,所以创建出来的信道自动被赋予了会话的特性。
如果在创建WsHttpBinding的时候,将安全模式设为SecurityMode.None(当然,在进行服务寄宿的时候,WsHttpBinding也须要进行相同的设置)。MaxConcurrentSessions的限制不适合非会话邦定。
4.3 自定义 WCF 配置文件
配置WCF的Service时,通常我们都会选择通过使用App.config或者Web.config来配置我们的Service。但是,当我们的程序要在不同的环境上测试或运行的时候,而作为开发人员的你在某些环境上并没有管理的权限时,通过唯一的App.config或者Web.config来配置Service就会造成一定程度上的麻烦。我们可以自定义config信息文件。
protected override void ApplyConfiguration()
{
string configFileName = System.Configuration.ConfigurationManager.AppSettings["ServiceConfigFile"];
if (string.IsNullOrEmpty(configFileName))
{
configFileName = "Config\\Service.config";
}
string filePath = System.IO.Path.Combine(physicalPath, configFileName);
if (!System.IO.File.Exists(filePath))
{
base.ApplyConfiguration();
return;
}
var configFileMap = new System.Configuration.ExeConfigurationFileMap();
configFileMap.ExeConfigFilename = filePath;
var config = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
var serviceModel = System.ServiceModel.Configuration.ServiceModelSectionGroup.GetSectionGroup(config);
bool loaded = false;
foreach (System.ServiceModel.Configuration.ServiceElement se in serviceModel.Services.Services)
{
if (!loaded)
{
if (se.Name == this.Description.ConfigurationName)
{
base.LoadConfigurationSection(se);
loaded = true;
}
}
}
if (!loaded)
throw new ArgumentException("ServiceElement doesn't exist");
}
通过从AppSetting中的ServiceConfigFile来设置config文件的相对路径,然后再和PhysicalPath合并来得到文件的绝对路径,如果这个指定的这个文件不存在,那么就调用base.ApplyConfiguration(),从web.config文件中读取信息。
4.4 .NET 自带 WCF工具
WCF配置工具
路径:C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\SvcConfigEditor.exe
可以在.NET编译环境中打开 工具àWCF服务配置编辑器
用途:以属性方式来配置WCF,更方便一些
WCF客户端测试工具
路径:(安装盘符)D:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE
用途:可用于测试服务,检查配置
注意:只能测试基础数据,如测试方法含其他类型,则该工具不能调用该方法。
使用客户端测试工具必须将httpGetEnabled 属性设置为true ,否则会报无法获得元数据错误。
<-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点--> <serviceMetadata httpGetEnabled="true"/>,位于serviceBehaviors,服务行为配置节点中。
5、WCF安全
WCF 安全编程基于三个步骤,它们分别设置以下方面:安全模式、客户端凭据类型和凭据值。 可以通过代码或配置执行这些步骤。默认情况下,除了BasicHttpBinding之外,几乎每个绑定都启用了安全。
传输安全性包含三项主要安全功能:完整性、保密性和身份验证。 完整性就是检测消息是否已被篡改的能力。 机密性就是保证除预期接收方之外的其他人员都无法读取某个消息;这可以通过加密技术实现。 身份验证就是验证已声明标识的能力。 将这三项功能结合在一起,有助于确保消息安全地从一个点到达另一个点。
在 WCF 中,两个可用于实现传输安全性的主要机制分别为:传输安全模式和消息安全模式。
可在配置文件中的security 节点中设置 mode属性。
- Transport
- Message
- TransportWithMessageCredential
此选择使用传输层来保证消息传输的安全,同时每个消息都包含其他服务需要的丰富凭据。 这便将传输安全的性能优点与消息安全的丰富凭据优点结合起来。 使用下列绑定可实现这一点:BasicHttpBinding、WSFederationHttpBinding、NetPeerTcpBinding 和 WSHttpBinding。
l Transport传输安全模式使用传输级协议(如 HTTPS)获取传输安全性。 传输模式的优点是可以被广泛采用、可用于多个平台以及计算较为简单。 但是,它的缺点是只能保证点到点的消息安全。
l Message消息安全模式使用 WS-Security(和其他规范)实现传输安全性。 因为消息安全性直接应用于 SOAP 消息并与应用程序数据一起包含在 SOAP 信封内,它的优点是独立于传输协议、可扩展性更强以及可确保端到端安全性(与点到点相对);它的缺点是比传输安全性模式慢很多倍,因为它必须处理SOAP消息的XML特性。
l TransportWithMessageCredential安全模式同时使用前两种模式,并具备这两种模式的优点。 在此模式下,消息安全性用于对客户端进行身份验证,而传输安全性则用于对服务器进行身份验证并提供消息机密性和完整性。 TransportWithMessageCredential安全模式几乎与传输安全模式一样快,并且提供与消息安全性一样的客户端身份验证可扩展性。 但是,与消息安全性模式不同,它不提供完整的端到端安全性。
Transport 模式
clientCredentialType属性设置为下列值之一:None、 Windows、Certificate
protectionLevel属性设置为下列值之一:None、Sign、EncryptAndSign,参见ProtectionLevel 枚举。
Message 模式
clientCredentialType 属性设置为下列值之一:None、 Windows、 UserName、 Certificate 、IssuedToken
algorithmSuite可指定算法的属性:具体参见SecurityAlgorithmSuite类,默认值为Default。
安全示例:
1.客户端与服务器端通信使用x509证书验证,但不用客户端安装证书。只需要服务器端配置好证书即可。
2.验证使用用户名密码形式。
需要生成一个服务器证书,运行Visual Studio 2008 命令提示工具,执行下面的指令创建证书:
makecert -r -pe -n "CN=MyServer" -sr LocalMachine -ss My -sky exchange
-sr LocalMachine保存到LodcalMachine中.
开始à运行àmmcà文件à添加/删除管理单元à添加à选择证书à计算机账户à下一步完成。
在证书\个人里可以看到刚才创建的证书。
目的就是到时如果你部署这个wcf服务的时候可以让IIS找到证书,否则,IIS会报找不到x509证书.
【服务端添加配置】
在<serviceBehaviors>节点加入配值
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="None"/>
</clientCertificate>
<serviceCertificate findValue="MyServer" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFSecurityTest.MyUserNamePasswordValidator, WCFSecurityTest"/>
</serviceCredentials>
在<wsHttpBinding>节点加入配置
<binding name="binding1">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
【客户端添加配置】
<behavior name="jacBehavior">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="None"/>
</serviceCertificate>
</clientCredentials>
</behavior>
<identity>
<!--<dns value="localhost"/>-->
<certificate encodedValue="MIIB8zCCAV……HimibkLyE="/>
</identity>
encodedValue可通过导出证书得到:
右键所有任务à导出à不导出私钥àBase64 编码 X.509 (.CER)导出文件后,用记事本打开,可看到encodedValue编码。
【服务端代码】
public class MyUserNamePasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "pt" || password != "111111")
{
SecurityTokenException detail = new SecurityTokenException("Unknown Username or Password");
throw new FaultException<SecurityTokenException>(detail);
}
}
}
UserNamePasswordValidator属于命名空间,System.IdentityModel
继承于UserNamePasswordValidator抽象类,重写抽象方法Validate(string, string)。
【客户端测试代码】
try
{
Service1Client service = new Service1Client();
service.ClientCredentials.UserName.UserName = "pt";
service.ClientCredentials.UserName.Password = "111111";
this.txtContent.Text += service.GetData(222222) + Environment.NewLine;
}
Catch
{
Response.Write("<script>window.alert('用户名或密码错误')</script>");
}
当客户端调用服务端方法时,会首先执行Validate(string, string)方法,检测用户名和密码是否正确。
注:在每一次连接调用会首先执行Validate(string, string)方法,在同一次连接调用会在调用第一个方法执行Validate(string, string)方法,接下来执行别的方法不会再验证用户名和密码。
错误和异常处理
缺省模式下,服务端发生异常,客户端会获得一个Exception,但是这个Exception并不包括详细的异常信息,因为服务端并不能完全信赖客户端,比如浏览器和Web服务器之间。为了防止非法的客户端从异常消息中获服务端的比较详细的信息,所以服务对于异常通常会比较模糊。
在进行WCF分布式应用开发的过程中,我们客户端经常会遇到一下三种常见的错误。
(1):通信错误,可能和网络、通道等相关的异常,客户端表现为Communication Exception;
(2):代理和通道的State,代理已经关闭,或者通道Fault,等问题,这个比较常见。一般通道闲置时间过久,通道会出现这个状态错误的问题。一般我们可以通过代理的State来判断。安全验证失败也会导致这个错误。
(3):服务调用错误,服务调用时抛出的异常,这个服务内部异常会序列化传递给客户端,被客户端捕获。
PerSession模式: 抛出异常,服务实例将销毁,客户端抛出FaultException,客户端代理对象无法继续使用
PerCall模式: 抛出异常,服务实例也将销毁。客户端代理对象无法继续使用
Single模式: 抛出异常,服务实例会照旧运行。客户端代理无法继续使用。
FaultException
因此如果在WCF服务中采用传统的方式处理异常,由于异常消息不能被序列化,因而客户端无法捕获和处理服务抛出的异常。为了解决这个问题,WCF提供了FaultException。这个是一个基于行业标准的SOAP异常类,WCF会将无法识别的异常处理为统一的FaultException异常对象,因此,可以把错误信息传递到客户端,客户端可以捕获FaultException或者Exception。比如:
catch(FaultException ex) { }
但是,这种情况下,比如服务端是DirectoryNotFoundException,这种情况下下却无法识别DirectoryNotFoundException所传递的错误信息。尤为严重的是这样的异常处理方式还会导致传递消息的通道出现错误,当客户端继续调用该服务代理对象的服务操作时,会获得一个CommunicationObjectFaultedException异常,无法继续使用服务。如果服务被设置为PerSession模式或者Single模式,异常还会导致服务对象被释放,终止服务
FaultException的另外一个重要的泛型定义就是FaultException<T>,这里我们可以使用任何系统类型或者自定义类型来传递错误信息,T代表要传递的错误细节。
FaultContract
WCF为异常处理专门提供了FaultContract特性,可以被应用到服务操作上,指明操作可能会抛出的异常类型。
我们可以将服务所要抛出的异常类型作为FaultException<T>的类型参数,然后创建一个FaultReason对象用以传递错误消息。客户端在调用服务代理对象时,可以捕获FaultException< DirectoryNotFoundException>异常,并且该异常不会使得通道发生错误,并且客户端可以继续使用该服务代理对象。即使服务为PerCall服务,客户端仍然可以继续调用服务操作。如果服务为Session服务或Singleton服务,那么即使发生了异常,服务对象也不会被终结。
DirectoryNotFoundException exception = new DirectoryNotFoundException();
throw new FaultException<DirectoryNotFoundException>(exception,
new FaultReason(“the directory is not exist”));
includeExceptionDetailInFaults
只是为了让客户端获得异常消息,即使不施加FaultContract特性,或者抛出非FaultException异常,我们也可以通过ServiceBehavior特性,将服务的IncludeExceptionDetailInFaults设置为true(默认为false),此时,客户端可以捕获抛出的非FaultException异常信息,但该异常仍然会导致通道出现错误
在发布服务与部署服务时,我们应避免将服务的IncludeExceptionDetailInFaults设置为true。
WCF异常处理扩展(灵活性最高)
WCF允许开发者定制异常报告和异常传递的过程,需要实现System.ServiceModel.Dispatcher.IErrorHandler接口:
IErrorHandler的定义如下:
public interface IErrorHandler
{
bool HandleError(Exception error);
void ProvideFault(Exception error, MessageVersion version, ref Message fault);
}
服务端抛出的异常会再调用ProvideFault()方法后再返回给客户端。ProvideFault不考虑异常的类型。
另外ProvideFault一个重要的作用,异常提升(Exception Promotion)。它可以将非FaultContract异常提升为FaultContract<T>异常,例如将OverflowException异常提升为FaultExceptino< OverflowException>异常。
例如将DirectoryNotFoundException异常提升为FaultException<DirectoryNotFoundException>异常:
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is DirectoryNotFoundException)
{
FaultException<DirectoryNotFoundException> faultException = new FaultException<DirectoryNotFoundException>(
new DirectoryNotFoundException(), new FaultReason(error.Message));
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version,messageFault,faultException.Action);
}
}
而HandleError()方法,我们可以重新实现代码,如log日志。
要使得错误处理扩展生效,还需要向服务通道安装错误处理扩展。方法是让服务类实现System.ServiceModel.Description.IServiceBehavior接口:
public class DocumentsExplorerService : IDocumentsExplorerService,IErrorHandler,IServiceBehavior,IDisposable
{…}
然后在ApplyDispatchBehavior()方法中安装错误处理扩展:
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{ dispatcher.ErrorHandlers.Add(this); }
}
通过这样的处理,即使服务抛出的异常为DirectoryNotFoundException异常,并且在服务契约中没有通过FaultContract特性指定该异常,客户端同样能够获得异常的错误信息,且该异常不会导致通道发生错误,客户端可以继续调用服务代理对象的操作。
6、WCF事务
事务(Transaction)是并发控制的基本单位。所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。
一、事务特性(ACID)
原子性(Atomic)
整个事务是个不可分割的整休,所有操作都只有两种状态:“全执行”或“全不执行”。不能再有其它中间子状态。
一致性(Consistent)
系统要么处于事务执行前的状行,要么处理事务成功执行后的状态。要保持数据的完整性,不能产生数据的丢失与叠加。
隔离性(Isolated)
每个独立的事务在执行的时候保护各自的资源。不同的事务之间不能产生资源访问并发冲突,通常使用“锁”来实现事务的隔离。
持久性(Durable)
当设备失效时操作结果不会损坏事务的一致性。
二、事务传播的绑定协议
WSHttpBinding、WSDualHttpBinding、WSFederationBinding、NetTcpBinding、NetNamedPipesBinding
在配置文件中指明使用的绑定协议,并在绑定协议中启动事务流机制
三、事务流
在WCF中事务往往是分布式事务,即需要事务跨越客户端、服务端,甚至跨越多个其它服务端进行执行。因此我们需要把事务对象以流的方式在客户端和服务端进行传递,以使此事务对象能有效地控制整个执行过程中的一致性。
要实现这种分布式的事务流,需要我们对客户端代码和服务端代码都做好事务的设置才能够实现事务流的控制。
通常我们使用TransactionFlowAttribute、ServiceBehaviorAttribute 和 OperationBehaviorAttribute 这三个特性来指明服务端事务控制。
其构造函数接收TransactionFlowOption枚举类型的参数,指明其参与事务的方式。
TransactionFlowOption.NotAllowed:禁止参与事务。(默认值)
TransactionFlowOption.Allowed:允许参与事务,如果调用方启用了事务,则参与。
TransactionFlowOption.Mandatory:强制参与事务,调用方必须启用事务才能调用本服务。
ServiceBehaviorAttribute:描述服务参与事务的一些参数
TransactionAutoCompleteOnSessionClose = bool:指示当会话(Session)结束时是否自动提交事务(Complete); 这个属性和服务对象实例模式紧密相关,使用的时候,应该着重小心。
ReleaseServiceInstanceOnTransactionComplete = bool:指示事务提交后是否释放服务实例对象;
TransactionIsolationLevel= System.Transactions.IsolationLevel:用于指示事务隔离级别.
TransactionTimeout = string:设置事务超时时间。
OperationBehaviorAttribute:描述契约方法如何参与事务的一些参数
TransactionScopeRequired = bool:该属性是 WCF 分布事务所必需的。它表明服务方法必须在事务范围(transaction scope)内执行。如果不添加该标记,则意味着服务方法不参与到事务中。
TransactionAutoComplete = bool: 指示方法正常结束后是否自动提交事务。
四、事务模型
1.Client/Service 事务模型
如果客户端启用了事务,则服务端就参与事务;如果客户端没有启用事务,则服务端独立启用事务。
即不管客户端是否启用事务,服务端总是运行在事务中。
实现步骤:
a) 选择一个支持事务的绑定,设置TransactionFlow = true。
b) 在方法契约上添加TransactionFlowAttribute声明,设置TransactionFlow(TransactionFlowOption.Allowed)。
c) 在方法行为上声明OperationBehavior(TransactionScopeRequired=true)。
2.Client事务模型
Client事务模型必须由客户端启动分布式事务,并强制服务端必须参与客户端事务流。
实现步骤:
a) 选择一个支持事务的Binding,设置 TransactionFlow = true。
b) 在方法契约上添加TransactionFlowAttribute声明,设置TransactionFlow(TransactionFlowOption.Mandatory)。
c) 在方法行为上声明OperationBehavior(TransactionScopeRequired=true)。
3.Service事务模型
这种事务模型是把服务端事务与客户端事务分离开,服务端代码执行总是会创建自己的事务,并不会参与到客户端事务中去。所以客户端的事务启用与否并不影响服务端事务的执行。
实现步骤:
a) 可以使用任何绑定信道。如果选择的是支持事务的绑定,需要设置TransactionFlow = false,因为服务端事务独立启动,并不需要事务流。
b) 不需要在方法契约上添加TransactionFlowAttribute声明。如果你非得设置此Attribute的话,请设置TransactionFlow(TransactionFlowOption.NotAllowed),指明服务端拒绝参与事务流。
c) 在方法行为上声明OperationBehavior(TransactionScopeRequired=true)。指明服务端需要自己启用事务。
4.None事务模型
这种事务模型中服务端代码不启用任何事务也不参与任何事务流。
事务模型的选择
上面所述的四种事务模型中,Service模型和None模型用得比较少,这两种模式带系统不一致的潜在危险。一般在需要事务流控制的地方,我们应选择Client/Service模型或Client模型来确保客户端和服务端之间系统一致。
五、事务表决(Transaction Voting)
如果多个服务参与一个事务流,必须所有服务都执行成功后才能提交整个事务流;只要有一个服务失败整个事务流就会回滚至事务流开始之前的状态。
因此事务流必须知道是否每个服务功能都执行成功,当每个服务功能都执行表决通过时,整个事务流就进行提交,这个过程就是我们所说的“事务表决”,也有人称之为“事务投票”。
WCF提供了两种事务表决的模型:“声明式表决模型”和“编程式表决模型”
1.声明式表决模型
在方法的OperationBehavior特性声明中指定TransactionAutoComplete=true来实现。当方法在执行成功是就自动表决通过,如果方法执行产生异常或终止,则表决不通过。TransactionAutoComplete属性的值默认就是true,因此也可以不用为此属性赋值。
因此下面两段代码是一样的,都实现了声明式表决
[OperationBehavior(TransactionScopeRequired = true,TransactionAutoComplete = true)]
public void MyMethod(...)
{...}
[OperationBehavior (TransactionScopeRequired = true)]
public void MyMethod(...)
{...}
在事务流中我们需要把每个参与的方法的OperationBehavior特性声明为TransactionScopeRequired = true,这样该方法就参与了事务流。但在方法内部尽量避免处理异常。
下面这种编码方式应当避免
[OperationBehavior(TransactionScopeRequired = true)]
public void MyMethod(...)
{
try
{ }
catch
{ Transaction.Current.Rollback( ); }
}
因为在此方法的catch块中把事务回滚终止了操作,即把异常处理了,那该异常就不会再反升给事务流,事务流就不会捕获到异常信息。但由于该方法产生了异常,事务表决未通过,直接导致整个事务流的回滚。因此就出现了事务流在不明原因的情况下,产生了回滚。
即使你需要在此方法中使用catch捕获异常来实现记录日志等功能,那也应当在catch块结束后再把异常抛给事务流,这样层层上抛直至事务流的根再进行异常处理。
正确的代码应当如下:
[OperationBehavior(TransactionScopeRequired = true)]
public void MyMethod(...)
{
try
{ }
catch
{
/* 本地异常处理代码 */
throw; //再把异常信息上抛至事务流
}
}
2.编程式表决模型
编程式表决模型需要把方法的OperationBehavior特性中的TransactionAutoComplete属性设为False,TransactionScopeRequired属性设为true。在方法中使用OperationContext.Current.SetTransactionComplete( )方法来进行事务表决。
代码如下:
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public void MyMethod(...)
{
/* 其它操作*/
OperationContext.Current.SetTransactionComplete( );
}
一般来说SetTransactionComplete()要放在最后执行,在SetTransactionComplete()方法之后执行任何事务操作都会导致异常产生。
这种编程式表决模型一般用在:根据其它服务的运行结果来决定是否表决通过此事务。它使用起来比“声明式表决模型”要复杂一些,但更灵活。