什么是WCF?
WCF的全称是:Windows通信基础(WindowsCommunication Foundation)。本质来讲,他是一套软件开发包。
WCF和WebService的差别
Webservice:严格来说是行业标准。不是一种技术,使用XML扩展标记语言来表示数据(这个是跨语言和平台的关键。)
WCF事实上一定程度上就是ASP.NET WebService。由于它支持Web Service的行业标准和核心协议,因此ASP.NET Web Service和WSE能做的事情。它差点儿都能胜任。
WCF不仅支持和集成Web Service,它还兼容和具备了微软早起非常多技术的特性。
为什么要用WCF
在Microsoft提出.NET战略以来,先后推出了一系列的产品和技术,这些产品和技术为我们在.NET平台下建立企业级的分布式应用提供了非常大的便利。这些技术和产品包含:.NET Remoting,XML WebService。WSE,Enterprise Service。MSMQ……
事实上,通过合理利用上面这些技术全然能够为我们建立的一套适合不同层次须要的分布式架构。
但这里面仍然存在一些问题,那就是这些技术仅仅能解决某一方面的问题;比方.NET Remoting尽管在.NET平台下是一个非常好的依靠,可是考虑到他们不能提供不同平台之间的互操作性。另外,这些技术适合用了全然不同的编程方式,使得我们非常难从容的从当中一种转移到还有一种上来。基于这些原因。我们须要一套全新的技术整合以上这些技术。于是我们有了今天的WCF。
WCF建立一套框架,是我们通过一致的编程模式,使用不同的技术构建我们的分布式应用。
建立一个简单的WCF
整个应用框架
整个应用框架例如以下图所看到的,该解决方式由5个project组成:Client、Contract、Hosting、Service、WCFService。
ü Contract:用来保存Contract(Service Contract、Message Contract、 Data Contract),之所以把Contract独立出来的原因是考虑到他同一时候被Server端——Service本身和Service Hosting和Client端使用。
ü Service:Service的业务逻辑,这个项目医用Contract和System.ServiceModel DLL。
ü Hosting:用于以Self-Hosting的方式Host Service。这个项目引用Contract和Service和System.ServiceModel DLL。
ü Client:用以模拟现实中的调用Service的Client。这个项目引用Contract和System.ServiceModel DLL。
ü http://localhost/WCFService:用于模拟怎样把Service Host到IIS中。这个项目引用Contract、Service和System.ServiceModelDLL。
创建Contract
在这个样例中我们建立一个简单的案例,做一个计算器,如果我们仅仅要求他做简单的加法运算就能够了。在Contract中加入一个interface,名称叫做ICalculator。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; namespace Contract { [ServiceContract] public interface ICalculator { [OperationContract] double Add(double x, double y); } }
使一个Interface成为Contract的方法非常easy,就是把ServiceContractAttribute应用到这个interface上,并在代表单个Operation的方法上应用OperationContractAttribute。
这个使用CustomAttribute的编程模式被称为声明式的编程(Declarative)方式, 他在.NET Framework 1.1曾经用到的地方还不是太多。在.NET Framework 2.0。尤其是NET Framework 3.0中。这样的方式已经变得随处可见了。
在WCF中。Contract的功能实际上就定义一个Service包括哪些可用的Operation,以及的每一个Opertaion的方法签名。从消息交换(Message Exchange)的角度讲。Contract定义了调用对应的Serive採取的消息交换的模式(Message Exchange Pattern - MEP),我们常常使用的MEP包括三种:Oneway, Request/Response,和Duplex。由于调用Service的过程实际就是消息交换的过程。以常见的Request/Response为例。Client调用某个方面远程訪问Service,全部的输入參数被封装到Request Soap Message并被发送到Service端,Service端监听到这个Soap Request。创建对应的Service Object并调用相关的操作,最后将Result(这能够是Return Value,Reference Parameter和Output Parameter)封装成Soap Message发送回Client端。这里须要注意,假设採用的是Request/Response的模式,即使对应的操作没有Return Value,Reference Parameter和Output Parameter(它被标记为void),Service仍然会返回一个空的Soap Message给Client端。
创建Service
前面我们已经创建了我的Contract。
事实上我们从Contract这个单词上讲。它就是一种契约。一种承诺。他表明在上面签了字你就的履行Contract上义务。
Service就是这样一个须要履行Contract义务的人。在这个样例中。Contract以Interface的方式定义的一些Operation。作为Service,在Contract上签字的方式就是实现这种一个Interface。以下的Service得到code,非常easy。
Hosting
就像Remoting一样,我们继承自System.MarshalByRefObject的对象必须Host到某一个执行的进行中。他才開始监听来自Client端的请求,当Client才干通过Proxy远程的调用,Remoting Infrastructure监听到来自Client端的请求。他会激活对应的remoteObject。实际上对于WCF Service也须要一个Host环境才有其发挥作用的舞台。就像Rmoting一样,你能够使用不论什么一种Managed Application——Console Application、WinForm Application、ASP.NET Application——作为它的Host环境。你甚至能够用它Host到Windows Service中和IIS中。
我们知道WCF中,Client端和Service端是通过Endpoint来通信的,Endpoint有包括3个部分,经典地称为ABC。
A代表Address,它包括一个URI,它指明Service存在于网络的某个地方。也就是说它为Client断指明在什么地方去找到这个Service。
非常多人认识Address只不过一个具有Identity的URI。实际上不然, Address不止于此,它还包括一些Header,这些信息在某些情况下对于怎样寻址有非常大的意义(比方在client的终于Service之间另一些Intermediary节点的情况下)。在.NET中,Address用System.ServiceModel.EndpointAddress来表示。
B代表Binding。Binding封装了全部Client和Service段消息交换的通信细节。
比方他定义了通信应该採用的Transport-比方我们是因该採用Http。TCP。Named Pipe或者是MSMQ;通信的内容应该採取如何的编码——比方是Text/XML,Binary还是MTOM。以上这些都得一个Binding所必须定义的内容,此外。Binding还能够定义一些其它的有关通信的内容。比方Security。Reliable Messaging,Session,Transaction等等。正由于Binding对于通信的重要性,仅仅有Service端的Binding和Client的Binding相互匹配的时候。他们之间在能够相互通信。
如何使Client Binding匹配Service Binding呢?最简单也是最直接的方法就是使用同样的Binding。WCF为我们定义了一系列的System Defined Binding。这些Binding在Transport,Interoperability,Security,Session Support。以及Transaction Support方面各有側重。基本上WCF为我们定义的这些Binding已经够我们使用的了,假设,实在满足不了要求,你还能够建立自己的Custom Binding。
C代表Contract这在上面已经提及。这里不再累赘。
Host的本质就是把一个Service置于一个执行中的进程中,并以Endpoint的形式暴露出来。并開始监听来自Client端的请求。这里值得注意的是,同一个Service能够注冊若干不同的Endpoint,这样不同的Client就能够以不同的方式来訪问同一个Service.比方,同一个Intranet的Client能够以TCP的方式訪问Service,还有一个存在已Internet中的Client则仅仅能以Http的方式訪问。
我们如今能够单独执行Hosting 项目,当Hosting启动之后,因为我们为它开启了Http Enabled,Hosting为专门创建一个Metadata Endpoint,通过訪问这个Endpoint。我们可获取Service相关的Metadata。像ServiceModel Metadata Utility Tool (Svcutil.exe)这得工具就是通过获取Metadata来帮我们生成对应的client代码和配置文件。当Hosting被启动之后,我们能够在IE中输入Metadata Endpoint的Address来測试Service的可訪问性。
下面是执行后的截图。
以上我们全然以代码的方式Host一个已经创建的Service。在这个样例中,Endpoint的全部信息都在代码中指定。实际上真正的项目开发之中,我们基本上不採用这个方案。这是由于,Service的Addresss以及Binding 信息在开发阶段和部署阶段往往是不一样的。所以我们通常的做法是把这些信息放在Config文件里。这样我们能够依据实际的需求改变存储于Config文件里的信息,比方我们把Service移植上另外的位置,我们仅仅要改变Endpoint的Address配置信息就能够了; 再比方。我们把原来存在Intranet的Service放到Internet上,原来可能基于TCP的Binding必须改成基于Http的Binding,在这种情况下,我们依旧可改动配置文件就能够了,这种改动一般是非常小的。而且改动了配置文件之后我们不须要对现有的代码进行重编译和重部署。它们能够事实上生效。
以下我们来看看怎样创建基于Configuration的Hosting。
首先。在Artech.WCFService.Hosting中创建App.config,并编写例如以下结构的配置信息。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="WinformCalculator.CalculatorService" behaviorConfiguration="TestBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost/Calculator"/> </baseAddresses> </host> <endpoint binding="basicHttpBinding" address = "ServiceConfig" contract="WCFServiceDemo1.ICalculatorService"> <identity > <dns value="localhost"/> </identity> </endpoint> </service> </services> <behaviors> <serviceBehaviors> <behavior name="TestBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
然后我们对应地改变我们的Hosting代码,如今因为我们的Endpoint的信息都放在我们的配置文件里,所以我们能够较大的简化我们的Hosting 代码。
using System; using System.Collections.Generic; using System.Text; using System.ServiceModel; using Contract; using Service; using System.ServiceModel.Description; namespace Hosting { class Program { static void Main(string[] args) { HostingServiceViaCode(); } static void HostingServiceViaCode() { Uri baseUri = new Uri("http://localhost:8080/calculatorService"); using (ServiceHost calculatorServiceHost = new ServiceHost(typeof(CalculatorService), baseUri)) { BasicHttpBinding Binding = new BasicHttpBinding(); calculatorServiceHost.AddServiceEndpoint(typeof(ICalculator), Binding, string.Empty); ServiceMetadataBehavior behavior = calculatorServiceHost.Description.Behaviors.Find<ServiceMetadataBehavior>(); if (behavior == null) { behavior = new ServiceMetadataBehavior(); behavior.HttpGetEnabled = true; behavior.HttpGetUrl = baseUri; calculatorServiceHost.Description.Behaviors.Add(behavior); } else { behavior.HttpGetEnabled = true; behavior.HttpGetUrl = baseUri; } calculatorServiceHost.Opened += delegate { Console.WriteLine("Calculator Service begin to listen via the Address:{0}", calculatorServiceHost.BaseAddresses[0].ToString()); }; calculatorServiceHost.Open(); Console.Read(); } } } }
当calculatorServiceHost.Open();被调用之后,系统会查看calculatorServiceHost相应的Service的类型。结果发现它的类型是Artech.WCFService.Service.CalculatorService(在ServiceHost被创建时最为第一个传入參数);然后会在config文件里Services部分中找name属性和这个Service type相匹配。找到之后。把它host进来。然后加入它在配置文件里定义的全部的Endpoint,并设置在配置文件里定义的全部Binding 以及 Behavior。
如今我们执行Hosting。将会得到同上面一样的结果。相同,在IE中输入Metadata Endpoint的Address,也会看到上解图一样的显示。
创建Client
到如今为止。Service端的工作已经完毕。当你启动Hosting的时候,一个可用的Service就已经存在了。如今所做的事情是怎样创建我们的客户段程序去使用这个Service。差点儿全部的WCF的书,当中包含MSDN都是叫你怎样使用Service Utility这种一个工具来帮你生成client代码和配置信息。为了让我们可以清晰的Client的总体内容,我们如今选择手工的方式来编写这种部分代码。
差点儿全部分布式的调用都有这种一个概念。调用的详细实现被封装在Server端。Client不可能也不须要了解这个详细实现,它所关心的就是我怎样去调用。也就是说Cient须要的不是Service的实现。而是一个interface。
WCF也是一样,Client不须要了解Service的详细实现,它仅仅须要获得Service 的Contract,已经怎样与Service通信就足够了。
说到Contract和通信。我们非常自然地会想到Endpoint,不错。Endpoint恰恰给我们提供这两个方面的内容。所以到如今我们能够这样说,这样在Client建立和Serivce端相匹配的Endpoint,Client就能够调用它所希望的Service。
前面提到Endpoint包含三个部分。 Address, Binding,Contract那我们如今来看看Client怎样获得这3要素的信息。
在System。Service。Model 命名空间里,定义了一个类abstractclass ClientBase,给我们调用Service提供极大的便利。
我们仅仅要是我们的Client继承这样一个类,并为它指定Endpoint的三要素就一切OK了,以下我们来看看我们能够以那些方式来指定这些内容
1、Conract:我们看到了ClientBase是一个Generic的类,我们在创建一个继承自这个类的时候必须给它指定特定的TChannel.我们能够把Contract相应的类型作为Client的generic类型。
2、Binding和Address:和Service端的Endpoint一样。我们能够把相关的信息放在我们的Client端代码里面,也能够放在Client的Config里面。
那个这些数据怎样应用要我们创建的派生自ClientBase的类的对象上呢。事实上非常easy。ClientBase给我们定义了若干重载的构造函数。我们仅仅要定义我们对应的构造函数应简单地调用基类的构造函数。以下列出了ClientBase定义的所有的构造函数。
protectedClientBase():这个构造函数没有不论什么的參数。它用于Endpoint的信息所有存放于Config
protectedClientBase(InstanceContext callbackInstance):指定一个Callbackinstance用于Service回调Client代码。这用Deplex Communication。
protectedClientBase(string EndpointConfigurationName):指定一个ID。它标识configuration 文件里定义的某一个Endpoint。这种方法在使用不同的Endpoint调用同一个Service的情况下用到的比較多。
ClientBase(Binding Binding, EndpointAddress remoteAddress);显示的指定Binding 和Address ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName) ClientBase(string EndpointConfigurationName, EndpointAddress remoteAddress) ClientBase(string EndpointConfigurationName, string remoteAddress) ClientBase(InstanceContext callbackInstance, Binding Binding, EndpointAddress remoteAddress) ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName, EndpointAddress remoteAddress) ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName, string remoteAddress)
介绍完ClientBase后, 我们来创建我们自己的CalculatorClient。以下的对应的Code
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels; using Contract; namespace Client { class CalculatorClient : ClientBase, ICalculator { internal CalculatorClient(): base(){ } public double Add(double x, double y) { return this.Channel.Add(x, y); } } }
上面的样例中我们只定义了一个无參的构造函数。由于我们会把全部的Endpoint信息放在Config文件中面:
http://localhost:8080/WCFService/CalculatorService " Binding="basicHttpBinding" contract="Artech.WCFService. Contract.ICalculator" />
然后我们再Client Project中的Program class里面通过这种方式调用CalculatorService。
using System; using System.Collections.Generic; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels; using Contract; namespace Client { class Program { static void Main(string[] args) { try { using (CalculatorClient caluclator = new CalculatorClient()) { Console.WriteLine("Begin to invocate the calculator Service"); Console.WriteLine("x + y = {2} where x = {0} and y = {1}", 1, 2, caluclator.Add(1, 2)); Console.Read(); } } } catch (Exception ex) { Console.WriteLine("StackTrace:{0}", ex.StackTrace); Console.WriteLine("Message:{0}", ex.Message); Console.Read(); } } }