【应用篇】WCF学习笔记(一):Host、Client、MetadataExchage
2009-10-24 20:27
309 查看
虽然已经在多个项目中成功应用过WCF,但是感觉自己对WCF的知识只知道一些皮毛而已。上次学习WCF也是为了项目需要,囫囵吞枣。这不是我学习方法的态度。所以时至今日,又重新拾卷,再仔细的将WCF一些细节知识,边边角角自己回顾一番。
<%@ ServiceHost Language=”C#” Debug=”false” CodeBehind=”~/App_Code/MyService.cs” Service=”MyService” %>
还记得Web Service的asmx文件么?是不是如出一辙!
WAS提供了很多Self-Host没有的优点,比如应用程序池、回收、身份管理等。
这三个方面实际上表达的是Where?How?What?的意思。
Address就是我到哪里(Where)寻找这个服务?
Binding就是我如何(How)与这个服务交互?
Contract就是这个服务是什么(What)?
每个Endpoint必须具有三个要素,缺一不可。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/36545964fc0091b780186111c0799c3c.png)
Service就驻留在ServiceHost实例中,每个ServiceHost实例只能托管一个服务。每个ServiceHost可以有多个Endpoint。一个进程里面可以有多个ServiceHost实例,同个宿主进程里不同的ServiceHost实例可以享用相同的BaseAddress。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/d87a501a63c1700f2bf687586703d9a3.jpg)
生成后的工程(经过修改):
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/b726c91d7ee379eff44114fc9bb89d27.jpg)
如果采用Selft-Host该怎么办呢?Visual Studio里还有一个WCF Service Library的项目模板,我们可以使用这个模板为Self-Host生成很多代码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/d9704e46a0db7a4f4146c1ed938619e1.jpg)
添加后生成的工程(经修改):
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/5bd1cd301761392a4e113d08ff9a799f.jpg)
但不管是WCF Service Application还是WCF Service Library,我觉得这种自动生成的方式都不太好。从上面几个图我们可以看出,这两种项目模板都将服务契约与服务的实现放在同一个项目中,最后编译出来服务契约与服务实现也在同一个程序集中,既然如此那为何又要将契约和服务分开?不是多次一举么?对于Best Practice来讲,我们应该永远都为每个服务创建一个接口,而将[ServiceContract]特性加在这些接口上,然后在另一个项目里编写服务的实现类,引用服务契约的项目,实现这些接口(契约)。所以无论从学习还是Best Practice来讲,我们都应该具有手动从头到尾编写服务契约、实现服务、服务托管的代码的能力:
代码示例:
[/code]
[/code]
[/code]
[/code]
使用配置的方式:
[/code]
WCF启用元数据交换有两种方式:
1、使用HttpGet
2、使用一个专门的Endpoint用来进行元数据交换
[/code]
既然是Http-Get的方式,顾名思义,肯定是使用http协议,所以必须有一个http的baseaddress。上面是使用代码的方式启用http-get,看看如何用配置打开http-get:
[/code]
[/code]
配置的方式添加
[/code]
从上面的代码我们基本上就知道了如何启用元数据交换了。使用http-get的方式简单,只需要一条设置就ok了,但是只能使用http或https,使用专用的endpoint则可以使用所有的协议。很多内容写在注释里了,仔细阅读。
服务已经Host了,Endpoint也已添加了,现在就要看看如何编写Client端。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/2989518d5d1385541ddc1788ed5e9045.jpg)
添加服务引用以后:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/12cda9c264c2729865794f6b6b25c3b5.jpg)
在这个窗口中,点击“Advanced”还可以对生成的代理做一些设置,在Namespace里可以设置生成服务的命名空间。这样做非常简便、高效,而且,当服务端修改了什么,我们只需要在客户端项目的Service Reference文件件下选择对应的服务,然后点击右键中的“Update Service Reference”,客户端代理就可以自动更新成新版本了。具有这样的优势很有诱惑力,但我们对Visual Studio生成了什么还是一无所知,不能自己完全的控制。所以你可以决定自己编写客户端代理。
[/code]
这样一个本地的代理就创建好了,如何去使用这个代理呢?也有两种方式,第一种,我们可以编写代码创建一个本地代理,第二种,我们可以将配置保存在配置文件中。
[/code]
编程的方式的优点是能得到编译器的检查,但是如果想修改一下,比如日后改为http协议访问就得修改源代码。我们还可以使用配置的方式,在上面代理的代码中,我们发现代理还有一个构造器接受一个“endpointName”的参数,这个参数就是指配置文件中Endpoint的名称:
[/code]
然后可以这样使用代理:
[/code]
这种方式虽然不能获得编译时的检查,配置文件如果写错了,只有等到运行时才可以发现,但是将配置保存在程序员可以带来非常大的灵活性。
[/code]
元数据除了协助Visual Studio发现服务,自动生成代码(客户端代理,数据契约)还有什么用?我们可以使用编程的方式访问元数据么?答案是肯定的,下一节我们看看如果使用编程方式访问元数据。
[/code]
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/e41d3b5c26a4392721fc6e952c829a25.png)
这个架构图对于日后的WCF扩展非常重要。
本文为学习WCF笔记,文章大部分内容“抄袭”自《Programming WCF Services》。
Host
三种Host的方式:IIS Host、WAS Host、Self-Host。IIS Host
IIS这种非常简单,但只支持HTTP协议。不过,你可以借助IIS来管理服务的生命周期。在IIS上发布WCF Service是极其简单的,只需要写一个后缀名为svc的文件就ok了:<%@ ServiceHost Language=”C#” Debug=”false” CodeBehind=”~/App_Code/MyService.cs” Service=”MyService” %>
还记得Web Service的asmx文件么?是不是如出一辙!
Self-Host
顾名思义,就是自托管了。开发人员完全控制,你可以将服务驻留在Console Application、WinForm、Windows Service。只需要保证服务先于客户端启动就Ok了。使用这种托管方式,开发人员可以完全的控制,可以使用任何协议,灵活性最大。WAS Host
WAS的全称是Windows Activation Service,是个系统服务,跟随Vista发布的,是IIS 7的一部分,但是也可以单独安装和配置。要使用WAS,和IIS Host一样,也需要提供一个svc文件。但是,WAS不仅仅可以使用HTTP,可以使用WCF可以使用的任何协议。WAS提供了很多Self-Host没有的优点,比如应用程序池、回收、身份管理等。
Endpoint
WCF中最重要的概念莫过于Endpoint了,Endpoint就是服务的接口。一个Endpoint包括三个要素:Address、Binding、Contract。这三个方面实际上表达的是Where?How?What?的意思。
Address就是我到哪里(Where)寻找这个服务?
Binding就是我如何(How)与这个服务交互?
Contract就是这个服务是什么(What)?
每个Endpoint必须具有三个要素,缺一不可。
ServiceHost
Host架构
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/36545964fc0091b780186111c0799c3c.png)
Service就驻留在ServiceHost实例中,每个ServiceHost实例只能托管一个服务。每个ServiceHost可以有多个Endpoint。一个进程里面可以有多个ServiceHost实例,同个宿主进程里不同的ServiceHost实例可以享用相同的BaseAddress。
使用Visual Studio自动生成服务端
Visual Studio的项目模板里已经为我们准备了WCF Service Application,使用Visual Studio创建的WCF Service Application项目默认是使用IIS托管的(WAS的托管方式与IIS的类似):![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/d87a501a63c1700f2bf687586703d9a3.jpg)
生成后的工程(经过修改):
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/b726c91d7ee379eff44114fc9bb89d27.jpg)
如果采用Selft-Host该怎么办呢?Visual Studio里还有一个WCF Service Library的项目模板,我们可以使用这个模板为Self-Host生成很多代码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/d9704e46a0db7a4f4146c1ed938619e1.jpg)
添加后生成的工程(经修改):
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/5bd1cd301761392a4e113d08ff9a799f.jpg)
但不管是WCF Service Application还是WCF Service Library,我觉得这种自动生成的方式都不太好。从上面几个图我们可以看出,这两种项目模板都将服务契约与服务的实现放在同一个项目中,最后编译出来服务契约与服务实现也在同一个程序集中,既然如此那为何又要将契约和服务分开?不是多次一举么?对于Best Practice来讲,我们应该永远都为每个服务创建一个接口,而将[ServiceContract]特性加在这些接口上,然后在另一个项目里编写服务的实现类,引用服务契约的项目,实现这些接口(契约)。所以无论从学习还是Best Practice来讲,我们都应该具有手动从头到尾编写服务契约、实现服务、服务托管的代码的能力:
代码示例:
[code]//订单项
[DataContract]
public class OrderItem
{
[DataMember]
public int Id{get;set;}
[DataMember]
public int ProductId{get;set;}
}
//订单
[DataContract]
public class Order
{
[DataMember]
public int OrderId{get;set;}
[DataMember]
public IList<OrderItem> OrderItems{get;set;}
}
[/code]
[code]//订单服务契约
[ServiceContract]
public interface IOrderService
{
[OperationContract]
bool CreateOrder(Order order);
[OperationContract]
bool DeleteOrder(int orderId);
[OperationContract]
bool CancelOrder(int orderId);
}
[/code]
[code]//订单服务
public class OrderService : IOrderService
{
public void CreateOrder(Order order)
{
return true;
}
public bool DeleteOrder(int orderId)
{
return false;
}
public bool CancelOrder(int orderId)
{
return false;
}
}
[/code]
服务托管(Self-Host)
[code] static void Main()
{
//binding,how?
Binding tcpBinding = new NetTcpBinding();
ServiceHost host = new ServiceHost(typeof(OrderService),new Uri("net.tcp://localhost:8000/"));
host.AddServiceEndpoint(typeof(IOrderService),tcpBinding,"OrderService");
host.Open();
Console.ReadLine();
host.Close();
}
[/code]
使用配置的方式:
[code] <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<!--注意,这里的name要与服务的类名是一致的-->
<service name="OrderService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8000/" />
</baseAddresses>
</host>
<endpoint contract="IOrderService" binding="netTcpBinding" address="OrderService" />
</service>
</system.serviceModel>
</configuration>
[/code]
MetadataExchange(元数据交换)
启用元数据交换有居多的好处,客户端可以使用SvcUtil工具自动的从服务元数据中生成客户端代理已经数据契约。WCF启用元数据交换有两种方式:
1、使用HttpGet
2、使用一个专门的Endpoint用来进行元数据交换
HttpGet
先来看实例,HttpGet:[code] Uri httpBaseAddress = new Uri("http://locahost/");
Uri tcpBaseAddress = new Uri("net.tcp://localhost:8000/");
ServiceHost host = new ServiceHost(typeof(OrderService),httpBaseAddress,tcpBaseAddress);
ServiceMetadataBehavior metadataBehavior;
metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
if(metadataBehavior == null)
{
metadataBehavior = new ServiceMetadataBehavior();
//启用http-get方式
metadataBehavior.HttpGetEnabled = true;
host.Description.Behaviors.Add(metadataBehavior);
}
host.AddServiceEndpoint(typeof(IOrderService),new NetTcpBinding(),"OrderService");
host.Open();
Console.ReadLine();
host.Close();
[/code]
既然是Http-Get的方式,顾名思义,肯定是使用http协议,所以必须有一个http的baseaddress。上面是使用代码的方式启用http-get,看看如何用配置打开http-get:
[code] <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="OrderService" behaviorConfiguration="EnableHttpGetBehavior">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8000/" />
<add baseAddress="http://localhost/" />
</baseAddresses>
</host>
<endpoint contract="IOrderService" binding="netTcpBinding" address="OrderService" />
</service>
<behaviors>
<serviceBehaviors>
<behavior name="EnableHttpGetBehavior">
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
[/code]
添加专用Endpoint
编程添加[code] Uri httpBaseAddress = new Uri("http://locahost/");
Uri tcpBaseAddress = new Uri("net.tcp://localhost:8000/");
ServiceHost host = new ServiceHost(typeof(OrderService),httpBaseAddress,tcpBaseAddress);
//虽然不使用http-get的方式,ServiceMetadataBehavior还是要加的,而且是先加这个Behavior,然后再添加
//专门用于元数据交换的Endpoint,否则会抛出异常
ServiceMetadataBehavior metadataBehavior;
metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
if(metadataBehavior == null)
{
metadataBehavior = new ServiceMetadataBehavior();
//使用这种方式,HttpGetEnabled是否为true都无所谓,HttpGetEnabled默认值是false
host.Description.Behaviors.Add(metadataBehavior);
}
host.AddServiceEndpoint(typeof(IOrderService),new NetTcpBinding(),"OrderService");
//注意这里
BindingElement bindingElement = new TcpTransportBindingElement();
Binding customBinding = new CustomBinding(bindingElement);
//添加一个专门用于元数据交换的Endpoint
host.AddServiceEndpoint(typeof(IMetadataExchange),customBinding,"MEX");
host.Open();
Console.ReadLine();
host.Close();
[/code]
配置的方式添加
[code] <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="OrderService" behaviorConfiguration="EnableHttpGetBehavior">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8000/" />
<add baseAddress="http://localhost/" />
</baseAddresses>
</host>
<endpoint contract="IOrderService" binding="netTcpBinding" address="OrderService" />
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="MEX" />
</service>
<behaviors>
<serviceBehaviors>
<behavior name="EnableHttpGetBehavior">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
[/code]
从上面的代码我们基本上就知道了如何启用元数据交换了。使用http-get的方式简单,只需要一条设置就ok了,但是只能使用http或https,使用专用的endpoint则可以使用所有的协议。很多内容写在注释里了,仔细阅读。
服务已经Host了,Endpoint也已添加了,现在就要看看如何编写Client端。
Client端编程
使用Visual Studio自动生成客户端
客户端是通过一个代理与服务端交互的,我们可以使用Visual Studio的Add Service Reference的功能添加远程服务,这样Visual Studio就会自动的帮我们生成客户端服务的代理。要使用Add Service Reference首先你得服务端必须启用了元数据交换。如果你是使用Visual Studio在同一个解决方案下创建的WCF Service Application或WCF Service Library,在Add Service Reference窗口中,还可以使用Discovery按钮自动的找到本解决方案下的所有服务:![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/2989518d5d1385541ddc1788ed5e9045.jpg)
添加服务引用以后:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/12cda9c264c2729865794f6b6b25c3b5.jpg)
在这个窗口中,点击“Advanced”还可以对生成的代理做一些设置,在Namespace里可以设置生成服务的命名空间。这样做非常简便、高效,而且,当服务端修改了什么,我们只需要在客户端项目的Service Reference文件件下选择对应的服务,然后点击右键中的“Update Service Reference”,客户端代理就可以自动更新成新版本了。具有这样的优势很有诱惑力,但我们对Visual Studio生成了什么还是一无所知,不能自己完全的控制。所以你可以决定自己编写客户端代理。
Client Proxy
[code]public class OrderServiceProxy : ClientBase<IOrderService>,IOrderService
{
public OrderServiceProxy(){}
public OrderServiceProxy(string endpointName):base(endpointName){}
public OrderServiceProxy(Binding binding,EndpointAddress remoteAddress):base(binding,remoteAddress){}
public bool DeleteOrder(int orderId)
{
return this.Channel.DeleteOrder(orderId);
}
public bool CancelOrder(int orderId)
{
return this.Channel.CancelOrder(orderId);
}
public bool CreateOrder(Order order)
{
return this.Channel.CreateOrder(order);
}
}
[/code]
这样一个本地的代理就创建好了,如何去使用这个代理呢?也有两种方式,第一种,我们可以编写代码创建一个本地代理,第二种,我们可以将配置保存在配置文件中。
使用代理
编程方式[code] static void Main()
{
Binding tcpBinding = new NetTcpBinding();
EndpointAddress address = new EndpointAddress("net.tcp://localhost:8000/OrderService");
OrderServiceProxy proxy = new OrderServiceProxy(tcpBinding,address);
//打开到远程服务的连接
proxy.Open();
//调用远程服务,是不是像调用本地方法一样
proxy.CancelOrder(5);
//关闭
proxy.Close();
}
[/code]
编程的方式的优点是能得到编译器的检查,但是如果想修改一下,比如日后改为http协议访问就得修改源代码。我们还可以使用配置的方式,在上面代理的代码中,我们发现代理还有一个构造器接受一个“endpointName”的参数,这个参数就是指配置文件中Endpoint的名称:
[code] <?xml version="1.0" encoding="utf-8" ?>
configuration>
<system.serviceModel>
<client>
<endpoint address="net.tcp://localhost:8000/OrderService"
binding="netTcpBinding"
contract="IOrderService" name="OrderService">
</endpoint>
</client>
</system.serviceModel>
</configuration>
[/code]
然后可以这样使用代理:
[code] OrderServiceProxy proxy = new OrderServiceProxy("OrderService");
proxy.Open();
proxy.CancelOrder(5);
proxy.Close();
[/code]
这种方式虽然不能获得编译时的检查,配置文件如果写错了,只有等到运行时才可以发现,但是将配置保存在程序员可以带来非常大的灵活性。
使用ChannelFactory创建代理
实际上,还有一种方式:[code] Binding binding = new NetTcpBinding();
EndpointAddress address = new EndpointAddress("net.tcp://localhost:8000/OrderService")
IOrderService proxy = ChannelFactory<IOrderService>.CreateChannel(binding,address );
//代理使用后一定要关闭,看看下面的方式
using(proxy as IDisposable)
{
proxy.Login();
}
//或者这种方式也可以
ICommunicationObject channel = proxy as ICommunicationObject;
channel.Close();
[/code]
元数据除了协助Visual Studio发现服务,自动生成代码(客户端代理,数据契约)还有什么用?我们可以使用编程的方式访问元数据么?答案是肯定的,下一节我们看看如果使用编程方式访问元数据。
元数据导入
我们可以编写代码,判断一个服务是否提供我们期望的Contract,这将怎么实现呢?比如我们要做一个小程序,遍历出某个指定address里暴露的所有Contract。WCF为我们提供了MetadataExchangeClient类。[code] //MetadataExchangeClient的构造器有几个重载
MetadataExchangeClient mexClient = new MetadataExchangeClient(new Uri("net.tcp://localhost:8000/MEX"),
MetadataExchangeClientMode.MetadataExchange);
//GetMetadata方法也有好几个重载
MetadataSet metadataSet = mexClient.GetMetadata();
WsdlImporter importer = new WsdlImporter(metadataSet);
ServiceEndpointCollection endpoints = importer.ImportAllEndpoints();
foreach(ServiceEndpoint endpoint in endpoints)
{
ContractDescription contract = endpoint.Contract;
Console.WriteLine("Namespace:{0},Name:{1}",contract.Namespace,contract.Name);
}
[/code]
WCF架构
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/23/e41d3b5c26a4392721fc6e952c829a25.png)
这个架构图对于日后的WCF扩展非常重要。
本文为学习WCF笔记,文章大部分内容“抄袭”自《Programming WCF Services》。
相关文章推荐
- 【应用篇】WCF学习笔记(一):Host、Client、MetadataExchage 【应用篇】WCF学习笔记(二):ServiceContract、DataContract
- WCF基础学习笔记--多种Client访问宿主
- WCF: Host、Client、MetadataExchage
- WCF: Host、Client、MetadataExchage
- 【应用篇】WCF学习笔记(二):ServiceContract、DataContract <第一部分>
- 转载来的wcf学习笔记01
- CUDA学习笔记 02 函数三种前缀device、global、host
- HTTPClient4.5.2学习笔记(六):HTTP 缓存
- STM32 USB Host Library 学习笔记 (1) USB_OTG_ReadPacket() USB_ReadPacket()
- JavaScript 学习笔记(五)广告、.ActiveXObject、scrollTop、.onscroll、clearTimeout()、return;、clientWidth
- wcf 学习笔记一 服务寄存到iis(个人写,非转载)
- redis 学习笔记(2)-client端示例代码
- wcf 学习笔记2
- Jakarta Commons HttpClient 学习笔记
- [Silverlight学习笔记]创建Silverlight 3与WCF之间的 双工通信
- 【学习】构建WCF面向服务的应用程序系列课程笔记:(3)契约版本处理
- HttpClient 4.1.3学习笔记之一
- Jakarta Commons HttpClient 学习笔记 (二)
- android菜鸟学习笔记24----与服务器端交互(一)使用HttpURLConnection和HttpClient请求服务端数据
- live555学习笔记8-RTSPClient分析