首页 > 代码库 > WCF之初体验
WCF之初体验
什么是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的方法很简单,就是把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,很简单。
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来帮我们生成相应的客户端代码和配置文件。当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=http://www.mamicode.com/"localhost"/>>
然后我们相应地改变我们的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的整体内容,我们现在选择手工的方式来编写这样的部分代码。
几乎所有分布式的调用都有这样的一个概念,调用的具体实现被封装在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的类的对象上呢。其实很简单,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。下面的相应的Codeusing 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(); } } }