网站外链工具,怎样查看网站点击量,二手书网站的建设规模,主流网站开发技术框架第1章 WCF基础本章主要介绍WCF的基本概念、构建模块以及WCF体系架构#xff0c;以指导读者构建一个简单的WCF服务。从本章的内容中#xff0c;我们可以了解到WCF的基本术语#xff0c;包括地址#xff08;Address#xff09;、绑定#xff08;Binding#xff09;、契约以指导读者构建一个简单的WCF服务。从本章的内容中我们可以了解到WCF的基本术语包括地址Address、绑定Binding、契约Contract和终结点Endpoint了解如何托管服务如何编写客户端代码了解WCF的相关主题诸如进程内托管In-Proc Hosting以及可靠性的实现。即使你已经熟知WCF的基本概念仍然建议你快速浏览本章的内容它不仅能够巩固你的已有知识而且本章介绍的一些辅助类与技术术语也将有助于你阅读全书。
什么是WCF Windows通信基础Windows Communication FoundationWCF是基于Windows平台下开发和部署服务的软件开发包Software Development KitSDK。WCF为服务提供了运行时环境Runtime Environment使得开发者能够将CLR类型公开为服务又能够以CLR类型的方式使用服务。理论上讲创建服务并不一定需要WCF但实际上使用WCF却可以使得创建服务的任务事半功倍。WCF是微软对一系列产业标准定义的实现包括服务交互、类型转换、封送Marshaling以及各种协议的管理。正因为如此WCF才能够提供服务之间的互操作性。WCF还为开发者提供了大多数应用程序都需要的基础功能模块提高了开发者的效率。WCF的第一个版本属于.NET3.0的一部分为服务开发提供了许多有用的功能包括托管Hosting、服务实例管理Service InstanceManagement、异步调用、可靠性、事务管理、离线队列调用Disconnected Queued Call以及安全性。WCF的第二个版本属于.NET3.5的一部分提供了附加工具并在原有的基础上进行了扩展增加了额外的通信选项。WCF的第三个版本属于.NET4.0的一部分包含了配置变化、一些扩展和新的特性如服务发现及路由器。虽然与.NET4.0没有直接关系但是WCF也扩展了对于Windows Azure Platform App Fabric Service Bus的支持。同时WCF还提供了设计优雅的可扩展模型使开发人员能够丰富它的基础功能。事实上WCF自身的实现正是利用了这样一种可扩展模型。本书的其余章节会专注于介绍这诸多方面的内容与特征。WCF的大部分功能都包含在一个单独的程序集System.ServiceModel.dll中命名空间为System.ServiceModel。WCF是.NET4.0的一部分因此它只能运行在支持它的操作系统上。Windows XP SP2、Windows Server 2003 SP1、Windows Vista客户端和服务器Windows Server 2008和Windows 7等系统以及更新的版本。
服务 服务Services是公开的一组功能的集合。从软件设计的角度考虑软件设计思想经历了从函数发展到对象从对象发展到组件再从组件发展到服务的几次变迁。在这样一个漫长的发展旅程中最后发展到服务的一步可以说是最具革新意义的一次飞跃。面向服务Service-OrientationSO是一组原则的抽象是创建面向服务应用程序的最佳实践。如果你不熟悉面向服务的原则可以参见附录A它介绍了使用面向服务的概况与目的。本书假定你对这些原则已经了然于胸。一个面向服务应用程序SOA将众多服务聚集到单个逻辑的应用程序中这就类似于面向组件的应用程序聚合组件或者面向对象的应用程序聚合对象如图1-1所示。 服务可以是本地的也可以是远程的可以由多个参与方使用任意技术进行开发。服务与版本无关甚至可以在不同的时区同时执行。服务内部包含了诸如语言、技术、平台、版本与框架等诸多概念而服务之间的交互则只允许指定的通信模式。服务的客户端只是使用服务功能的一方。 理论上讲 客户端可以是任意的Windows窗体类、 ASP.NET页面或其他服务。客户端与服务通过消息的发送与接收进行交互。消息可以直接在客户端与服务之间进行传递也可以通过中间方进行传递。 WCF 中的消息通常为 SOAP 消息。注意 WCF 的消息与传输协议无关这与 Web 服务不同。因此 WCF 服务可以在不同的协议之间传输而不仅限于 HTTP。 WCF 客户端可以与非 WCF 服务完成互操作而 WCF 服务也可以与非 WCF 客户端交互。不过如果需要同时开发客户端与服务则创建的应用程序两端都要求支持 WCF这样才能利用 WCF 的特定优势。因为服务的创建对于外界而言是不透明的所以 WCF 服务通常通过公开元数据 Metadata的方式描述可用的功能以及服务可能采用的通信方式。元数据的发布可以预先定义它与具体的技术无关 Technology-Neutral例如采用基于HTTP-GET方式的WSDL 或者符合元数据交换的行业标准。一个非WCF客户端可以将元数据作为本地类型导入到本地环境中。相似的 WCF 客户端也可以导入非 WCF 服务的元数据然后以本地 CLR 类与接口的方式进行调用。
服务的执行边界 WCF不允许客户端直接与服务交互即使它调用的是本地机器内存中的服务。相反客户端总是使用代理Proxy将调用转发给服务。代理公开的操作与服务相同同时还增加了一些管理代理的方法。 WCF 允许客户端跨越执行边界与服务通信。在同一台机器中参见图 1-2客户端可以调用同一个应用程序域中的服务也可以在同一进程中跨应用程序域调用甚至跨进程调用。图 1-3则展示了跨机器边界的通信方式客户端可以跨越Intranet或 Internet的边界与服务交互。WCF 与位置透明度 过去诸如 DCOM或 .NET Remoting等分布式计算技术不管对象是本地还是远程都期望为客户端提供相同的编程模型。本地调用时客户端使用直接引用处理远程对象时则使用代理。因为位置的不同而采用两种不同的编程模型会导致一个问题就是远程调用远比本地调用复杂。复杂度体现在生命周期管理、可靠性、状态管理、可伸缩性 scalability以及安全性等诸多方面。由于远程对象并不具备本地对象的特征而编程模型却力图让它成为本地对象反而使得远程编程模型过于复杂。 WCF同样要求客户端保持一致的编程模型而不用考虑服务的位置。但它的实现途径却大相径庭即使对象是本地的 WCF仍然使用远程编程模型的实例化方式并使用代理。 由于所有的交互操作都经由代理完成要求相同的配置与托管方式因而对于本地和远程方式而言 WCF都只需要维持相同的编程模型。这就使得开发者不会因为服务位置的改变影响客户端同时还大大地简化了应用程序的编程模型。
地址 WCF的每一个服务都具有一个唯一的地址 Addresses。地址包含两个重要元素服务位置与传输协议 Transport Protocol或者是用于服务通信的传输样式 Transport Schema。服务位置包括目标机器名、站点或网络、通信端口、管道或队列以及一个可选的特定路径或者 URI。 URI 即统一资源标识 Universal Resource Identifier它 可以是任意的唯一标识的字符串例如服务名称或 GUID。 WCF支持下列传输样式HTTP/HTTPSTCPPeer network对等网IPC基于命名管道的内部进程通信MSMQService bus 地址通常采用如下格式 [基地址]/[可选的 URI] 基地址 Base Address通常的格式如下 [传输协议]?/[机器名或域名][:可选端口] 下面是一些地址的示例 http://localhost:8001 http://localhost:8001/MyService net.tcp://localhost:8002/MyService net.pipe://localhost/MyPipe net.msmq://localhost/private/MyService net.msmq://localhost/MyService 可以将地址 http://localhost:8001读作“采用 HTTP 协议访问 localhost 机器并在 8001 端口等待用户的调用。” 如果 URI 为 http://localhost:8001/MyService则读作“采用 HTTP 协议访问localhost 机器 MyService 服务在 8001 端口处等待用户的调用。”
TCP 地址 TCP 地址采用 net.tcp 协议进行传输通常它还包括端口号例如 net.tcp://localhost:8002/MyService 如果没有指定端口号则 TCP 地址的默认端口号为 808 net.tcp://localhost/MyService 两个 TCP 地址来自于相同的宿主具体内容将在本章后面介绍可以共享一个端口 net.tcp://localhost:8002/MyService net.tcp://localhost:8002/MyOtherService 本书广泛地使用了基于 TCP 协议的地址。注意 我们可以将不同宿主的 TCP 地址配置为共享一个端口。HTTP 地址 HTTP 地址使用 http 协议进行传输也可以利用 https 进行安全传输。 HTTP 地址通常会被用作对外的基于 Internet 的服务并为其指定端口号例如 http://localhost:8001 如果没有指定端口号则默认为 80。与 TCP 地址相似两个相同宿主的 HTTP地址可以共享一个端口甚至相同的机器。 本书广泛地使用了基于 HTTP 协议的地址。IPC 地址 IPC地址使用net.pipe进行传输这意味着它将使用Windows的命名管道机制。在WCF中使用命名管道的服务只能接收来自同一台机器的调用。因此在使用时必须指定明确的本地机器名或者直接命名为 localhost为管道名提供一个唯一的标识字符串 net.pipe://localhost/MyPipe 每台机器只能打开一个命名管道因此两个命名管道地址在同一台机器上不能共享一个管道名。 本书广泛地使用了基于 IPC 的地址。MSMQ 地址 MSMQ 地址使用 net.msmq 进行传输即使用了微软消息队列 Microsoft Message Queue MSMQ机制。使用时必须为 MSMQ 地址指定队列名。如果是处理私有队列 则必须指定队列类型但对于公有队列而言队列类型可以省略 net.msmq://localhost/private/MyService net.msmq://localhost/MyService对等网地址 对等网地址 Peer Network Address 使用 net.p2p进行传输它使用了 Windows 的对等网传输机制。如果没有使用解析器 Resolver我们就必须为对等网地址指定对等网名、唯一的路径以及端口。对等网的使用与配置超出了本书范围但在本书的后续章节中会简略地介绍对等网。 契约 WCF 的所有服务都会公开为契约 Contract。契约与平台无关是描述服务功能的标准方式。 WCF 定义了四种类型的契约。 服务契约 Service Contract 服务契约描述了客户端能够执行的服务操作。 数据契约 Data Contract 数据契约定义了与服务交互的数据类型。 WCF 为内建类型如 int 和 string 隐式地定义了契约我们也可以非常便捷地将定制类型定义为数据契约。 错误契约 Fault Contract 错误契约定义了服务抛出的错误以及服务处理错误和传递错误到客户端的方式。 消息契约 Message Contract 消息契约允许服务直接与消息交互。消息契约可以是类型化的也可以是非类型化的。如果系统要求互操作性或者遵循已有消息格式那么消息契约会非常有用。除非要利用消息契约的灵活性、强大的功能及可扩展性否则应该避免使用它因为这往往适得其反增加开发的复杂程度。在大多数情况下使用消息契约意味着要自定义应用程序的上下文这样就可以使用自定义消息来实现。服务契约 ServiceContractAttribute 的定义如下 [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class,Inherited false)] public sealed class ServiceContractAttribute : Attribute { public string Name { get; set; } public string Namespace { get; set; } // 更多成员 } 这个特性允许开发者定义一个服务契约。我们可以将该特性应用到接口或者类类型上如例 1-1 所示。 例 1-1定义和实现服务契约 [AttributeUsage(AttributeTargets.Method)] public sealed class OperationContractAttribute : Attribute { public string Name { get; set; } // 更多成员 }
[ServiceContract]
interface IMyContract
{[OperationContract]string MyMethod(string text);//不会成为契约的一部分string MyOtherMethod(string text);
}
class MyService : IMyContract
{public string MyMethod(string text){return Hello text;}public string MyOtherMethod(string text){return Cannot call this method over WCF;}
} ServiceContract特性可以将一个 CLR 接口或者通过推断获得的接口后面将详细介绍映射为与技术无关的服务契约。 ServiceContract特性公开了 CLR 接口或者类作为 WCF 契约。 WCF 契约与类型的访问限定无关因为类型的访问限定属于 CLR 的概念。即使将 ServiceContract特性应用在内部 Internal接口上该接口同样会公开为公有服务契约以便于跨越服务边界实现服务的调用。如果接口没有标记 ServiceContract 特性 WCF 客户端则无法访问它即使接口是公有的。这一特点遵循了面向服务的一个原则即明确的服务边界。为满足这一原则所有契约必须明确要求只有接口或者类可以被标记为 ServiceContract特性从而被定义为WCF 服务其他类型都不允许。即使应用了ServiceContract特性类型的所有成员也不一定就是契约中的一部分。我们必须使用OperationContractAttribute特性显式地标明哪些方法需要暴露为WCF契约中的一部分。 WCF只允许将OperationContract特性应用到方法上而不允许应用到同样属于CLR概念的属性、索引器和事件上。 WCF 只能识别作为逻辑功能的操作 Operation。通过应用 OperationContract特性可以将契约方法暴露为逻辑操作使其成为服务契约的一部分。接口或类中的其他方法如果没有应用 OperationContract 特性则与契约无关。这有利于确保明确的服务边界为操作自身维护一个明确参与Opt-In的模型。此外契约操作不能使用引用对象作为参数只允许使用基本类型或数据契约。应用 ServiceContract 特性 WCF 允许将 ServiceContract 特性应用到接口或类上。当接口应用了 ServiceContract特性后需要定义类实现该接口。总的来讲我们可以使用 C# 或 VB 去实现 接口服务类的代码无需修改自然而然成为一个 WCF 服务 [ServiceContract] interface IMyContract { [OperationContract] string MyMethod(); } class MyService : IMyContract { public string MyMethod() { return “Hello WCF”; } } 我们可以隐式或显式实现接口 class MyService : IMyContract { string IMyContract.MyMethod() { return “Hello WCF”; } } 一个单独的类通过继承和实现多个标记了 ServiceContract特性的接口可以支持多个契约。 [ServiceContract] interface IMyContract { [OperationContract] string MyMethod(); } [ServiceContract] interface IMyOtherContract { [OperationContract] void MyOtherMethod(); } class MyService : IMyContract, IMyOtherContract { public string MyMethod() {…} public void MyOtherMethod() {…} } 然而服务类还有一些实现上的约束。我们要避免使用带参构造函数因为 WCF 只能使用默认构造函数。同样虽然类可以使用内部 internal的属性、索引器以及静态成员但 WCF 客户端却无法访问它们。 WCF允许我们直接将ServiceContract特性应用到服务类上而不需要首先定义一个单独的契约 //避 免 [ServiceContract] class MyService { [OperationContract] string MyMethod() { return “Hello WCF”; } } 通过服务类的定义 WCF 能够推断出契约的定义。至于 OperationContract特性则可以应用到类的任何一个方法上不管它是私有方法还是公有方法。 警告 应尽量避免将ServiceContract特性直接应用到服务类上而应该定义一个单独的契约这有利于在不同场景下使用契约。名称与命名空间 可以为契约定义命名空间。契约的命名空间具有与 .NET 编程相同的目的确定契约的类型范围以降低类型的冲突几率。 可以使用ServiceContract类型的 Namespace属性设置命名空间[ServiceContract(Namespace “MyNamespace”)] interface IMyContract {…} 若非特别指定契约的默认命名空间为 http://tempuri.org。对外服务的命名空间通常使用公司的 URL至于企业网 Intranet内部服务的命名空间则可以定义有意义的唯一名称例如 MyApplication。 在默认情况下契约公开的名称就是接口名。但是也可以使用 ServiceContract特性的 Name属性为契约定义别名从而在客户端的元数据 Metadata中公开不同的名称[ServiceContract(Name “IMyContract”)] interface IMyOtherContract {…} 相似的操作公开的名称默认为方法名但我们同样可以使用 OperationContract特 性的 Name 属性设置别名从而公开不同的操作名[ServiceContract] interface IMyContract { [OperationContract(Name “SomeOperation”)] void MyMethod(string text); } 托管 WCF服务类不能凭空存在。每个 WCF服务都必须托管 Hosting在 Windows 进程中该进程被称为宿主进程 Host Process。单个宿主进程可以托管多个服务而相同的服务类型也能够托管在多个宿主进程中。 WCF 没有要求宿主进程是否同时又是客户端进程。显然一个独立的进程有利于错误与安全的隔离。谁提供进程或是提供何种类型的进程并不重要。宿主可以由 IIS 提供也可以由 Windows Vista 的 Windows 激活服务 Windows Activation Service WAS提供或者开发者直接将它作为应用程序的一部分。 注意 一种特殊的托管方式称为进程内托管 In-Process Hosting简称 in-proc。服务与客户端驻留在相同的进程中。通过定义开发者能够提供进程内托管。IIS 托管 在微软的 Internet 信息服务器 Internet Information Server IIS中托管服务主要的优势是宿主进程可以在客户端提交第一次请求的时候自动启动还可以借助 IIS 管理宿主进程的生命周期。 IIS 托管的主要缺点在于只能使用 HTTP 协议。如果是 IIS 5还要受端口限制要求所有服务必须使用相同的端口号。 在 IIS 中托管服务与经典的 ASMX Web 服务托管相似需要在 IIS 下创建虚拟目录并提供一个 .svc 文件。 .svc 文件的功能与 .asmx 文件相似主要用于识别隐藏在文件和类后面的服务代码。例 1-2 展示了 .svc 文件的语法结构。 例 1-2 .svc 文件 % ServiceHost Language “C#” Debug “true” CodeBehind “~/App_Code/MyService.cs” Service “MyService” %注意 我们甚至可以将服务代码注入到.svc文件中但这样的做法并不明智。这与ASMX Web服务的要求相同。 使用 IIS 托管服务的基地址必需与 .svc 文件的地址保持一致。使用 Visual Studio 2010 使用 Visual Studio 2010可以生成 IIS 托管服务的模版文件。选择 File 菜单的 New Web Site 菜单项然后从 New Web Site 对话框中选择 WCF Service。通过这种方式可以让 Visual Studio 2010 创建一个新的 Web 站点以及服务代码和对应的 .svc 文件。之后我们还可以通过 Add New Item 对话框添加另外的服务。Web.Config 文件 Web 站点的配置文件 Web.Config必须列出需要公开为服务的类型。类型使用类型全名如果服务类型来自于一个没有被引用的程序集则还要包括程序集名system.serviceModel … /system.serviceModel 我们可以将提供服务类型和地址信息直接应用于serviceHostingEnvironment部分中的web.config程序而不使用.svc文件。事实上你可以根据自己的喜好定义许多这样的服务 system.serviceModel … … /system.serviceModel 自托管 所谓自托管 Self-Hosting 就是由开发者提供和管理宿主进程的生命周期。 自托管方式适用于如下场景需要确定客户端与服务之间的进程或机器边界时使用进程内托管即服务与客户端处于相同的进程中时。进程可以是任意的 Windows 进程例如Windows 窗体应用程序、控制台应用程序或 Windows NT 服务。注意进程必须在客户端调用服务之前运行这意味着通常必须预先启动进程。但 NT 服务或进程内托管不受此限制。宿主程序的实现只需要简单的几行代码就能够实现 IIS 托管的一部分特性。与 IIS 托管相似托管应用程序的配置文件 App.Config必须列出所有希望托管和公开的服务类型system.serviceModel … /system.serviceModel 此外宿主进程必须在运行时显式地注册服务类型同时为客户端的调用打开宿主因此我们才要求宿主进程必须在客户端调用到达之前运行。创建宿主的方法通常是在Main()方法中调用 ServiceHost 类。 ServiceHost 类的定义如例 1-3 所示。 例 1-3 ServiceHost 类public interface ICommunicationObject { void Open(); void Close(); //更多成员 } public abstract class CommunicationObject : ICommunicationObject { … } public abstract class ServiceHostBase : CommunicationObject,IDisposable,… { … } public class ServiceHost : ServiceHostBase { public ServiceHost(Type serviceType, params Uri[] baseAddresses) { }
//更多成员} 创建 ServiceHost 对象时需要为 ServiceHost的构造函数提供服务类型至于默认的基地址则是可选的。可以将基地址集合设置为空。如果提供了多个基地址也可以将服务配置为使用不同的基地址。ServiceHost拥有基地址集合可以使得服务能够接收来自于多个地址和协议的调用同时只需要使用相对的 URI。注意每个 SeriviceHost实例都与特定的服务类型相关如果宿主进程需要运行多个服务类型则必须创建与之匹配的多个 ServiceHost 实例。在宿主程序中通过调用 Open()方法可以允许调用传入通过调用 Close()方法终结宿主实例完成进程中的调用。此时即使宿主进程还在运行仍然会拒绝客户端的调用。而在通常情况下执行关闭操作会停止宿主进程。例如在 Windows 窗体应用程序中托管服务 [ServiceContract] interface IMyContract {…} class MyService : IMyContract {…}我们可以编写如下的托管代码 public static void Main() { Uri baseAddress new Uri(“http://localhost:8000/”); ServiceHost host new ServiceHost(typeof(MyService), baseAddress); host.Open(); // 可以执行用于阻塞的调用: Application.Run(new MyForm()); host.Close(); } 打开宿主时将装载 WCF 运行时 WCF runtime启动工作线程监控传入的请求消息。监听线程将传入调用消息从I/O完成端口I/O completion thread pool默认具有1000个线程分发到工作线程中。由于引入了工作线程因此可以在打开宿主之后执行阻塞 blocking操作。因为宿主被正常关闭因而所消耗的时间值是未定的。一般情况下宿主会阻塞10s以等待Close()方法返回以及处理在设置的超时值过期后的关闭事宜。在打开宿主之前你可以通过ServiceHostBase的属性CloseTimeout设置不同的关闭时间值 public abstract class ServiceHostBase:… { public TimeSpan Colsetimeout { get; set; } //更多成员 } 例如可以使用编程方式设置关闭超时时间值为20sServiceHost host new ServiceHost(…); host.CloseTimeout TimeSpan.FromSeconds(20); host.Open(); 在配置文件中也可以设置关闭时间值 system.serviceModel … /system.serviceModel 通过显式控制宿主的打开与关闭提供了 IIS 托管难以实现的特征即能够创建定制的应用程序控制模块管理者可以随意地打开和关闭宿主而不用每次停止宿主的运行。使用 Visual Studio 2010 Visual Studio 2010 允许开发者为任意的应用程序项目添加 WCF 服务方法是在 Add New Item 对话框中选择 WCF Service 选项。当然这种方式添加的服务对于宿主进程而言属于进程内托管方式但进程外的客户端仍然可以访问它。自托管与基地址 启动服务宿主时无需提供任何基地址public static void Main() { ServiceHost host new ServiceHost(typeof(MyService)); host.Open(); Application.Run(new MyForm()); host.Close(); } 警告 但是我们不能向空列表传递 null 值这会导致抛出异常serviceHost host; host new ServiceHost(typeof(MyService),null); 要这些地址没有使用相同的传输样式 Transport Schema我们也可以注册多个基地址并以逗号作为地址之间的分隔符。代码实现如下所示注意例 1-3 中 params 限定符的使用Uri tcpBaseAddress new Uri(“net.tcp://localhost:8001/”); Uri httpBaseAddress new Uri(“http://localhost:8002/”); ServiceHost host new ServiceHost(typeof(MyService), tcpBaseAddress,httpBaseAddress); WCF 也允许开发者在宿主配置文件中列出基地址内容 system.serviceModel … /system.serviceModel 创建宿主时无论在配置文件中找到哪一个基地址宿主都会使用它同时还要加上以编程方式提供的基地址。需要特别注意我们必须确保配置的基地址的样式不能与代码中的基地址的样式重叠。 我们甚至可以针对相同的类型注册多个宿主只要这些宿主使用了不同的基地址Uri baseAddress1 new Uri(“net.tcp://localhost:8001/”); ServiceHost host1 new ServiceHost(typeof(MyService),baseAddress1); host1.Open(); Uri baseAddress2 new Uri(“net.tcp://localhost:8002/”); ServiceHost host2 new ServiceHost(typeof(MyService),baseAddress2); host2.Open(); 然而这并不包括第 8 章介绍的使用线程的情况以这种方式打开多个宿主并无优势可言。此外 如果基地址是配置文件提供的那么就需要使用ServiceHost的构造函数为相同的类型打开多个宿主。托管的高级特性 ServiceHost实现的ICommunicationObject接口定义了一些高级特性如例1-4所示。 例 1-4 ICommunicationObject接口 public interface ICommunicationObject { void Open(); void Close(); void Abort(); event EventHandler Closed; event EventHandler Closing; event EventHandler Faulted; event EventHandler Opened; event EventHandler Opening; IAsyncResult BeginClose(AsyncCallback callback, object state); IAsyncResult BeginOpen(AsyncCallback callback, object state); void EndClose(IAsyncResult result); void EndOpen(IAsyncResult result); CommunicationState State { get; } // 更多成员 } public enum CommunicationState { Created, Opening, Opened, Closing, Closed, Faulted } 如果打开或关闭宿主的操作耗时较长可以采用异步方式调用 BeginOpen()和BeginClose()方法。我们可以订阅诸如状态改变或错误发生等宿主事件通过调用State属性查询当前的宿主状态。 ServiceHost类同样实现了 Abort()方法。该方法提供强行退出功能能够及时中断进程中的所有服务调用然后关闭宿主。此时活动的客户端会获得一个异常。 ServiceHost 类 ServiceHost 类能够改进 WCF 提供的 ServiceHost 类它的定义如例 1-5 所示。 例 1-5 ServiceHost 类 public class ServiceHost : ServiceHost { public ServiceHost() : base(typeof(T)) { } public ServiceHost(params string[] baseAddresses) : base(typeof(T), Convert(baseAddresses)) { } public ServiceHost(params Uri[] baseAddresses) : base(typeof(T), baseAddresses) { } static Uri[] Convert(string[] baseAddresses) { Converterstring, Uri convert delegate(string address) { return new Uri(address); }; return Array.ConvertAll(baseAddresses, convert); } } ServiceHost简化了构造函数它不需要传递服务类型作为构造函数的参数还能够直接处理字符串而不是处理令人生厌的 Uri 值。在本书余下的内容中对 ServiceHost 进行了扩展增加了一些特性提高了它的性能。WAS 托管 Windows 激活服务 WAS是一个系统服务适用于 Windows Vista及更新版本。 WAS 是 IIS 7的一部分但也可以独立地安装与配置。若要使用 WAS托管 WCF服务必须提供一个.svc文件这与 IIS 托管一样。 IIS与 WAS 的主要区别在于 WAS并不局限于使用 HTTP它支持所有可用的 WCF 传输协议、端口与队列。 WAS 提供了大量基于自托管的强大功能包括应用程序池、回收机制、空闲时间管理 Idle Time Management、身份管理 Identity Management以及隔离 Isolation宿主进程可以根据情况选择使用这些功能。若需考虑可扩展性就应该使用 Windows Server 2008或更新版本服务器作为目标机器如果只有少数客户端则可以将Windows Vista或者Windows 7或更新版本 客户机作为服务器。 当然自托管进程还提供了许多卓越特性例如进程内宿主、匿名用户环境的处理同时还为之前介绍的高级宿主特性提供了便捷地编程访问方式。 选择宿主对于Internet应用程序调用客户端来自Internet中的任何地方可以依据下图来做决定对于Intranet应用发送调用的客户端与服务同处于一个Intranet内可以依据下图来做决定 绑定 服务之间的通信方式是多种多样的有多种可能的通信模式。包括同步的请求 / 应答 Request/Reply消息或者异步的“即发即弃 Fire-and-Forget”消息双向 Bidirectional消息即时消息或队列消息以及持久 Durable队列或者可变 Volatile队列。传递消息的传输协议包括 HTTP或 HTTPS、 TCP、 P2P对等网、IPC命名管道以及 MSMQ。消息编码格式包括保证互操作性的纯文本编码格式优化性能的二进制编码格式提供有效负载的 MTOM消息传输优化机制 Message Transport Optimization Mechanism编码格式。消息的安全保障也有多种策略包括不实施任何安全策略只提供传输层的安全策略消息层的隐私保护与安全策略。 当然WCF 还包括多种对客户端认证与授权的安全策略。消息传递 Message Delivery可能是不可靠的也可能是可靠的端对端跨越中间方然后断开连接的方式。消息传递可能按照发送消息的顺序处理也可能按照接收消息的顺序处理。服务可能需要与其他服务或客户端交互 这些服务或客户端或者只支持基本的 Web服务协议 或者使用了流行的WS-* 协议例如 WS-Security 或者 WS-Atomic Transaction。服务可能会基于原来的MSMQ 消息与旧的客户端 Legacy Client交互或者限制服务只能与其他的 WCF 服务或客户端交互。若要计算所有可能的通信模式与交互方式之间的组合数量可能达到上千万。在这些组合选项中有的可能是互斥的有的则彼此约束。显然客户端与服务必须合理地组合这些选项才能保证通信的顺畅。对于大多数应用程序而言管理如此程度的复杂度并无业务价值。然而一旦因此作出错误决定就会影响系统的效率与质量造成严重的后果。为了简化这些选项使它们易于管理 WCF 引入了绑定 Binding技术将这些通信特征组合在一起。一个绑定封装了诸如传输协议、消息编码、通信模式、可靠性、安全性、事务传播以及互操作性等相关选项的集合使得它们保持一致。理想状态下我们希望将所有繁杂的基础功能模块从服务代码中解放出来允许服务只需要关注业务逻辑的实现。绑定使得开发者能够基于不同的基础功能模块使用相同的服务逻辑。在使用WCF提供的绑定时 可以调整绑定的属性也可以从零开始定制自己的绑定。服务在元数据中发布绑定的选项由于客户端使用的绑定必须与服务的绑定完全相同因此客户端能够查询绑定的类型与特定属性。单个服务能够支持各个地址上的多个绑定。标准绑定 WCF 定义了 不只9 种标准绑定 基本绑定 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 绑定相似但它还支持从服务到客户端的双向通信相关内容在第 5 章介绍。 MSMQ 绑定 由 NetMsmqBinding类提供。它使用 MSMQ 进行传输用以提供对断开的队列调用的支持。相关内容在第 9 章介绍。 MSMQ 集成绑定 MSMQ Integration Binding 由 MsmqIntegrationBinding类提供。它实现了 WCF 消息与 MSMQ消息之间的转换用以支持与旧的 MSMQ 客户端之间的互操作。 MSMQ集成绑定不在本书讨论范围之内。
格式与编码 每种标准绑定使用的传输协议与编码格式都不相同如表 1-1 所示。 表 1-1标准绑定的传输协议与编码格式默认的编码格式为黑体
文本编码格式允许 WCF服务或客户端能够通过 HTTP 协议与其他服务或客户端通信而不用考虑它使用的技术。二进制编码格式通过 TCP 或 IPC 协议通信它所获得的最佳性能是以牺牲互操作性为代价的它只支持 WCF 到 WCF 的通信。选择绑定 为服务选择绑定应该遵循图 1-4 所示的决策活动图表。 首先需要确认服务是否需要与非 WCF 的客户端交互。如果是同时客户端又是旧的MSMQ客户端 选择MsmqIntegrationBinding绑定就能够使得服务通过MSMQ与该客户端实现互操作。如果服务需要与非 WCF 客户端交互并且该客户端期望调用基本的 Web 服务协议 ASMX Web 服务那么选择 BasicHttpBinding 绑定就能够模拟ASMX Web 服务即 WSI-Basic Profile公开 WCF 服务。缺点是我们无法使用大多数最新的 WS-*协议的优势。但是如果非 WCF客户端能够识别这些标准就应该选择其中一种 W S 绑定例如 WSHttpBinding 、WSFederationBinding 或者WSDualHttpBinding。如果假定客户端为 WCF客户端同时需要支持脱机或断开状态下的交互则可以选择 NetMsmqBinding使用 MSMQ 传输消息。如果客户端需要联机通信但是需要跨机器边界调用则应该选择 NetTcpBinding通过 TCP 协议进行通信。如果相同机器上的客户端同时又是服务选择NetNamePipeBinding使用命名管道可以使性能达到最优化。如果基于额外的标准例如回调选择 WSDualHttpBinding或者联邦安全选择 WSFederationBinding则应对选择的绑定进行微调。注意 即使超出了使用的目标场景大多数绑定工作仍然良好。例如我们可以使用 TCP 绑定实现相同机器甚至进程内的通信我们也可以使用基本绑定实现 Intranet 中 WCF 对 WCF 的通信。然而我们还是应尽量按照图 1-4 选择绑定。使用绑定 每种绑定都提供了多种可配置的属性。绑定有三种工作模式。如果内建绑定符合开发者的需求就可以直接使用它们。我们也可以对绑定的某些属性如事务传播、可靠性和安全性进行调整与配置还可以定制自己的绑定。最常见的情况是使用已有的绑定然后只对绑定的几个方面进行配置。应用程序开发者几乎不需要编写定制绑定但这却是框架开发者可能需要做的工作。 终结点 服务与地址、绑定以及契约有关。其中地址定义了服务的位置绑定定义了服务通信的方式契约则定义了服务的内容。为便于记忆我们可以将这种类似于“三权分立”一般管理服务的方式简称为服务的 ABC。 WCF 用终结点表示这样一种组成关系。终结点就是地址、契约与绑定的混成品参见图 1-5。
每一个终结点都包含了三个元素而宿主则负责公开终结点。从逻辑上讲终结点相当于服务的接口就像 CLR 或者 COM 接口一样。注意图 1-5 使用了传统的“棒棒糖”形式展示了一个终结点的构成。注意 从概念上讲不管是 C# 还是 VB一个接口就相当于一个终结点地址就是类型虚拟表的内存地址绑定则是 CLR 的 JIT Just-In-Time编译而契约则代表接口本身。由于经典的 .NET 编程模式不需要处理地址或绑定你可能认为它们是理所当然存在的。而 WCF 并未规定地址与绑定因而必须对它们进行配置。每个服务至少必须公开一个业务终结点每个终结点有且只能拥有一个契约。服务上的所有终结点都包含了唯一的地址而一个单独的服务则可以公开多个终结点。这些终结点可以使用相同或不同的绑定公开相同或不同的契约。每个服务提供的不同终结点之间绝对没有任何关联。重要的一点是服务代码并没有包含它的终结点它们通常放在服务代码之外。我们可以通过管理方式 Administratively使用配置文件或者通过编程方式 Programmatically配置终结点。管理方式配置终结点 以管理方式配置一个终结点需要将终结点放到托管进程的配置文件中如下的服务定义namespace MyNamespace { [ServiceContract] interface IMyContract {…} class MyService : IMyContract {…} } 例 1-6 演示了配置文件要求的配置入口。在每个服务类型下列出它的终结点。 例 1-6管理方式配置终结点 system.serviceModel /system.serviceModel 当我们指定服务和契约类型时必须使用类型全名。 在本书的其余例子中为简略起见我省略了类型的命名空间但在实际应用中命名空间是必备的。注意如果终结点已经提供了基地址则地址的样式必须与绑定一致例如 HTTP对应 WSHttpBinding。如果两者不匹配就会在装载服务时导致异常。例 1-7 的配置文件为一个单独的服务公开了多个终结点。多个终结点可以配置相同的基地址前提是 URI 互不不同。 例 1-7相同服务的多个终结点 大多数情况下我们的首选是管理的配置方式因为它非常灵活即使修改了服务的地址、绑定和契约也不需要重新编译服务和重新部署服务。使用基地址 例 1-7 中的每个终结点都提供了自己独有的基地址。如果我们提供了显式的基地址它会重写宿主提供的所有基地址。我们也可以让多个终结点使用相同的基地址只要终结点地址中的 URI 不同 反之如果宿主提供了与传输样式匹配的基地址则可以省略地址项。此时终结点地址与该基地址完全相同 如果宿主没有提供匹配的基地址则在装载服务宿主时会抛出异常。 配置终结点地址时可以为基地址添加相对 URI 此时终结点地址等于它所匹配的基地址加上 URI。当然前提是宿主必须提供匹配的基地址。绑定配置 使用配置文件可以为终结点使用的绑定进行定制。为此需要在 节中添加bindingConfiguration标志它的值应该与 配置节中定制的绑定名一致。例 1-8 介绍了使用这种技术启用事务传播的方法。其中的 transactionFlow 标志会在第 7 章详细介绍。 例 1-8服务端绑定的配置 system.serviceModel /system.serviceModel 如例 1-8 所示我们可以在多个终结点中通过指向定制绑定的方式重用已命名的绑定配置。编程方式配置终结点 编程方式配置终结点与管理方式配置终结点等效。但它不需要配置文件而是通过编程调用将终结点添加到 ServiceHost 实例中。这些调用不属于服务代码的范围。 ServiceHost定义了重载版本的 AddServiceEndpoint()方法public class ServiceHost : ServiceHostBase { public ServiceEndpoint AddServiceEndpoint(Type implementedContract,Binding binding,string address); // 其他成员 } 传入 AddServiceEndpoint()方法的地址可以是相对地址也可以是绝对地址这与使用配置文件的方式相似。例 1-9 演示了编程配置的方法它配置的终结点与例 1-7 的终结点相同。 例 1-9服务端编程配置终结点ServiceHost host new ServiceHost(typeof(MyService)); Binding wsBinding new WSHttpBinding(); Binding tcpBinding new NetTcpBinding(); host.AddServiceEndpoint(typeof(IMyContract),wsBinding,“http://localhost:8000/MyService”); host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,“net.tcp://localhost:8001/MyService”); host.AddServiceEndpoint(typeof(IMyOtherContract),tcpBinding,“net.tcp://localhost:8002/MyService”); host.Open(); 以编程方式添加终结点时 address 参数为 string 类型 contract 参数为 Type 类型而binding 参数的类型则是 Binding 抽象类的其中一个子类例如public class NetTcpBinding : Binding,… {…} 由于宿主提供了基地址因此若要使用基地址可以将空字符串赋给 address 参数或者只设置 URI 值此时使用的地址就应该是基地址加上 URIUri tcpBaseAddress new Uri(“net.tcp://localhost:8000/”); ServiceHost host new ServiceHost(typeof(MyService),tcpBaseAddress); Binding tcpBinding new NetTcpBinding(); // 使用基地址作为地址 host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,); // 添加相对地址 host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,“MyService”); // 忽略基地址 host.AddServiceEndpoint(typeof(IMyContract),tcpBinding, “net.tcp://localhost:8001/MyService”); host.Open(); 使用配置文件进行管理方式的配置宿主必须提供一个匹配的基地址否则会引发异常。事实上编程方式配置与管理方式配置并没有任何区别。使用配置文件时 WCF会解析文件然后执行对应的编程调用。绑定配置 我们可以通过编程方式设置绑定的属性。例如 以下代码就实现了与例1-8相似的功能启用事务传播ServiceHost host new ServiceHost(typeof(MyService)); NetTcpBinding tcpBinding new NetTcpBinding(); tcpBinding.TransactionFlow true; host.AddServiceEndpoint(typeof(IMyContract),tcpBinding, “net.tcp://localhost:8000/MyService”); host.Open();