可以通过代码或者配置文件寄宿WCF服务,在使用过程中的一些心得,记录一下,方便后续查阅。
预备知识,几个地址的作用
1、 behavior.HttpGetUrl 定义元数据的地址,如果不定义基地址,则必须定义HttpGetUrl ,是The absolute or relative location of the metadata.
2、baseAddresses 服务的地址,因为元数据(metadata)是属于一个host的,并不属于一个endpoint。此基地址就是客户端添加服务引用的地址,
所以baseAddress解决了Where to locate the WCF Service?
如果定义endpoint daaress="",则endpoint daaress就等于baseAddresses
3、终结点endpoint address ,用于和客户端通信,服务端与客户端endpoint的address必须严格对应。
4、在这三个地址中如果没有定义基地址则另外两个地址HttpGetUrl,endpoint address必须都不能为空
5、如果定义了baseaddress,则另外两个地址均可为空。此时endpoint address就是baseaddress定义的url
如服务端终结点配置为:
<endpoint address="http://127.0.0.1:8882/DBServer" binding="wsHttpBinding" contract="IContract.IContract" />
即服务端定义address为"http://127.0.0.1:8882/DBServer" ,那么客户端的终结点address也必须为"http://127.0.0.1:8882/DBServer" 才可以建立通讯。
当我们Host一个WCF Service的时候,我们必须给他定义一个或多个Endpoint,然后service通过这个定义的Endpoint进行监听来自Client端的请求。当我们的Application需要调用这个Service的时候,因为Client 和Service是通过Endpoint的进行通信的, 所以我们必须为我们的Application定义Client端的Endpoint。只有当Client的Endpoint和Service端某个Endpoint相互匹配(Service端可以为一个Service定义多个Endpoint),Client端的请求才能被Service端监听到。也就是说,我们只有在Client具有一个与Service端完全匹配的Endpoint,我们才能调用这个Service。而这种匹配是比较严格的,比如从匹配Address方面,Client端和Service端的Endpoint Address不仅仅在URI上要完全匹配Service, 他们的Headers也需要相互匹配。对于Binding, 一般地,Client需要有一个与Service端完全一样的Binding,他们之间才能通信。
4、如果定义了基地址,则endpoint address可以设为空,这样所有endpoint的address就是基地址所定义的url
以下是一个建立wcf服务及调用的例子
1、新建一个控制台项目,添加以下程序集引用
using System.ServiceModel;
using System.ServiceModel.Web;
2、添加类IContract,只是为了演示,Contract和Service都建在一个项目里面了
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Web; namespace IContract //注意:如果命名空间为WCFHost,则在采用配置文件寄宿服务的时候会不认配置文件,不知道为什么 { [ServiceContract] public interface IContract { [OperationContract] [WebInvoke(UriTemplate = "Add/{x}/{y}")] double Add(double x, double y); } }
3、添加类DBService
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DBService //注意:如果命名空间为WCFHost,则在采用配置文件寄宿服务的时候会不认配置文件,不知道为什么 { public class DBService:IContract.IContract { public double Add(double x, double y) { return x+y; } } }
4、使用代码方式配置服务并开启服务,三个地址都进行了定义
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Web; namespace WCFHost { class Program { static void Main(string[] args) { try { //Uri uri = new Uri("http://127.0.0.1:8881/DBServer"); //和下面一句等价 Uri uri = new Uri("http://localhost:8881/DBServer"); using (ServiceHost host = new ServiceHost(typeof(DBService.DBService), uri)) { //定义元数据发布方式,此处 通过在服务所在的URL后加“?wsdl”的方式公布WSDL,可直接通过HTTP访问得到。 System.ServiceModel.Description.ServiceMetadataBehavior behavior = new System.ServiceModel.Description.ServiceMetadataBehavior(); behavior.HttpGetEnabled = true; //此句也可不要,HttpGetUrl定义元数据的url behavior.HttpGetUrl = new Uri("http://localhost:8882/DBService"); host.Description.Behaviors.Add(behavior); Uri endpointAddress =new Uri("http://127.0.0.1:8883/DBServer"); //添加终结点 host.AddServiceEndpoint(typeof(IContract.IContract), new WSHttpBinding(), endpointAddress); ////定义终结点地址(如果定义了baseadress,终结点地址也可为空) //host.AddServiceEndpoint(typeof(IContract.IContract), new WSHttpBinding(), ""); host.Opened += host_Opened; host.Open(); Console.ReadLine(); } } catch (Exception e) { throw e; } } static void host_Opened(object sender, EventArgs e) { Console.WriteLine("DBService opened successful"); } } }
5、使用配置文件配置服务并开启服务,作用同4
5.1 添加配置文件,配置文件中定义了三个address,基地址(服务地址),元数据地址、与客户端的通讯地址
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="metadataBehavior"> <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8881/DBServer"></serviceMetadata> </behavior> </serviceBehaviors> </behaviors> <services> <!--注意此处name必须与第三步服务的命名空间一致--> <service behaviorConfiguration="metadataBehavior" name="DBService.DBService"> <endpoint address="http://127.0.0.1:8882/DBServer" binding="wsHttpBinding" contract="IContract.IContract" /> <host> <baseAddresses> <add baseAddress="http://127.0.0.1:8883/DBServer"/> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration>
5.2 开启服务
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Web; namespace WCFHost { class Program { static void Main(string[] args) { try { ServiceHost host1 = new ServiceHost(typeof(DBService.DBService)); host1.Opened += host_Opened; host1.Open(); Console.ReadLine(); } catch (Exception e) { throw e; } } static void host_Opened(object sender, EventArgs e) { Console.WriteLine("DBService opened successful"); } } }
6、在浏览器中打开服务地址:http://127.0.0.1:8883/DBServer,如下图
打开元数据地址:http://127.0.0.1:8881/DBServer,如下图
客户端添加服务,使用服务地址或者元数据地址均可找到服务。
而终结点地址:http://127.0.0.1:8882/DBServer,无法打开,
7、在客户端调用服务时,定义终结点address必须服务定义的终结点address匹配,如下
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Web; namespace WCFClient { class Program { static void Main(string[] args) { WSHttpBinding binding = new WSHttpBinding(); EndpointAddress address = new EndpointAddress(new Uri("http://127.0.0.1:8882/DBServer")); ServiceReference1.ContractClient client = new ServiceReference1.ContractClient(binding, address); double i = client.Add(1, 2); Console.WriteLine(i); Console.ReadLine(); } } }
8、只定义baseAddress,不定义其他两个address
8.1 配置文件定义
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="metadataBehavior"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <services> <!--注意此处name必须与第三步服务的命名空间一致--> <service behaviorConfiguration="metadataBehavior" name="DBService.DBService"> <endpoint address="" binding="wsHttpBinding" contract="IContract.IContract" /> <host> <baseAddresses> <add baseAddress="http://127.0.0.1:8883/DBServer"/> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration>
开启服务
ServiceHost host1 = new ServiceHost(typeof(DBService.DBService)); host1.Opened += host_Opened; host1.Open(); Console.ReadLine();
8.2 代码定义并开启服务
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Web; namespace WCFHost { class Program { static void Main(string[] args) { try { //Uri uri = new Uri("http://127.0.0.1:8881/DBServer"); //和下面一句等价 Uri uri = new Uri("http://localhost:8881/DBServer"); using (ServiceHost host = new ServiceHost(typeof(DBService.DBService), uri)) { //定义元数据发布方式,此处 通过在服务所在的URL后加“?wsdl”的方式公布WSDL,可直接通过HTTP访问得到。 System.ServiceModel.Description.ServiceMetadataBehavior behavior = new System.ServiceModel.Description.ServiceMetadataBehavior(); behavior.HttpGetEnabled = true; host.Description.Behaviors.Add(behavior); host.AddServiceEndpoint(typeof(IContract.IContract), new WSHttpBinding(), ""); host.Opened += host_Opened; host.Open(); Console.ReadLine(); } } catch (Exception e) { throw e; } } static void host_Opened(object sender, EventArgs e) { Console.WriteLine("DBService opened successful"); } } }
8.3 客户端,直接添加服务引用会自动生成配置文件,然后调用
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <wsHttpBinding> <binding name="WSHttpBinding_IContract" /> </wsHttpBinding> </bindings> <client> <endpoint address="http://localhost:8881/DBServer" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IContract" contract="ServiceReference1.IContract" name="WSHttpBinding_IContract" /> </client> </system.serviceModel> </configuration>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Web; namespace WCFClient { class Program { static void Main(string[] args) { ServiceReference1.ContractClient client = new ServiceReference1.ContractClient(); double i = client.Add(1, 2); Console.WriteLine(i); Console.ReadLine(); } } }
8.4 也可不用配置文件,通过代码直接调用
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Web; namespace WCFClient { class Program { static void Main(string[] args) { WSHttpBinding binding = new WSHttpBinding(); EndpointAddress address = new EndpointAddress(new Uri("http://127.0.0.1:8881/DBServer")); ServiceReference1.ContractClient client = new ServiceReference1.ContractClient(binding, address); client.Endpoint.Binding = binding; double i = client.Add(1, 2); Console.WriteLine(i); Console.ReadLine(); } } }
9、比较WCF发布元数据的两种方式,不管哪种方式ServiceMetadata节点必须存在。
元数据描述服务的细节,导出以及发布元数据,都是由一个服务中的ServiceMetadataBehavior实现的。
必须为服务配置ServiceMetadata行为,即配置节点,才能为发布元数据,才能再使用httpGetEnabled=true或者httpGetEnabled=false发布元数据。
<serviceBehaviors>
<behavior name="metadataBehavior">
<serviceMetadata />
</behavior>
</serviceBehaviors>
9.1 WSDL,此方式通过在服务的URL后加“?wsdl”的方式公布WSDL,可直接通过HTTP访问。
设置httpGetEnabled="true" 发布元数据(图片见上面步骤6),设置为false不发布元数据。以下为设置为false的情况:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="metadataBehavior"> <!--<serviceMetadata httpGetEnabled="true" />--> <!--设置为true公布元数据,服务可以开启,客户端也可以访问服务--> <serviceMetadata httpGetEnabled="false" /> <!--设置为false不公布元数据,服务可以开启,但是客户端无法访问服务--> </behavior> </serviceBehaviors> </behaviors> <services> <!--注意此处name必须与第三步服务的命名空间一致--> <service behaviorConfiguration="metadataBehavior" name="DBService.DBService"> <endpoint address="" binding="wsHttpBinding" contract="IContract.IContract" /> <host> <baseAddresses> <add baseAddress="http://127.0.0.1:8883/DBServer"/> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration>
设置为false的情况:
9.2 MEX:此方式以一般的终结点方式公布,支持各种协议:http、tcp、NamedPipe
注意:以终结点的方式公开的元数据,无法通过浏览器查看元数据内容
添加mex终结点发布元数据,此时httpGetEnabled设置为true和false均可访问服务,只是元数据发布方式不同。
有mex终结点,httpGetEnabled="true",此时服务可以开启,客户端也可以访问服务,元数据以wsdl方式发布。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="metadataBehavior"> <serviceMetadata httpGetEnabled="true" /> <!--设置true,则以wsdl方式发布--> <!--<serviceMetadata httpGetEnabled="false" />--> <!--设置false,则以mex方式发布--> </behavior> </serviceBehaviors> </behaviors> <services> <!--注意此处name必须与第三步服务的命名空间一致--> <service behaviorConfiguration="metadataBehavior" name="DBService.DBService"> <endpoint address="" binding="wsHttpBinding" contract="IContract.IContract" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> <host> <baseAddresses> <add baseAddress="http://127.0.0.1:8883/DBServer"/> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration>
有mex终结点,httpGetEnabled="false",此时服务可以开启,客户端也可以访问服务,元数据以mex方式发布。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="metadataBehavior"> <!--<serviceMetadata httpGetEnabled="true" />--> <!--设置true,则以wsdl方式发布--> <serviceMetadata httpGetEnabled="false" /> <!--设置false,则以mex方式发布--> </behavior> </serviceBehaviors> </behaviors> <services> <!--注意此处name必须与第三步服务的命名空间一致--> <service behaviorConfiguration="metadataBehavior" name="DBService.DBService"> <endpoint address="" binding="wsHttpBinding" contract="IContract.IContract" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> <host> <baseAddresses> <add baseAddress="http://127.0.0.1:8883/DBServer"/> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration>