客户端发送请求给服务端,服务端根据请求消息把消息转发给对应的终结点。这里面有个消息筛选机制,如果请求消息中带有地址报头相关信息,则会用地址报头匹配当前的所有终结点。所以默认情况下客户端和服务端的地址报头信息一致才可以正常通信。
AddressHeader地址报头是一个抽象类,并且没有构造方法。创建AddressHeader对象需要用到内部静态方法CreateAddressHeader。
public static AddressHeader CreateAddressHeader(string name, string ns, object value);
在WCF请求消息序列化后,AddressHeader也会被列化,其对应的XML节点描述为:值:name,命名空间:ns,结点名称:value。
下面是一个示例演示:
服务端代码:
using System.ServiceModel; using System.ServiceModel.Description; using System.ServiceModel.Channels; namespace hostAddressHeader { class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(mywcf.CalculatorService))) { AddressHeader header1=AddressHeader.CreateAddressHeader("kk","www.kk.com","kkname"); EndpointAddress endpointaddress = new EndpointAddress(new Uri("http://localhost:8899"),header1); ServiceEndpoint endpoint = new ServiceEndpoint( ContractDescription.GetContract(typeof(mywcf.ICalculatorService)), new WSHttpBinding(), endpointaddress); host.AddServiceEndpoint(endpoint); host.Opened+=delegate{Console.WriteLine("Service Start!!");}; host.Open(); Console.ReadLine(); } } } }
上述代码首先创建了一个AddressHeader对象,并将AddressHeader对象放入EndpointAddress。EndpointAddress可以有多个地址报头信息,这里只放入一个。
客户端代码:
using System.ServiceModel; using System.ServiceModel.Description; using System.ServiceModel.Channels; namespace clientAddressHeader { class Program { static void Main(string[] args) { AddressHeader header1=AddressHeader.CreateAddressHeader("kk","www.kk.com","kkname"); ServiceEndpoint endpoint=new ServiceEndpoint( ContractDescription.GetContract(typeof(mywcf.ICalculatorService)), new WSHttpBinding(), new EndpointAddress(new Uri("http://localhost:8899"),header) ); ChannelFactory<mywcf.ICalculatorService> factory=new ChannelFactory<mywcf.ICalculatorService>(endpoint); mywcf.ICalculatorService client=factory.CreateChannel(); Console.WriteLine(client.Add(1, 2) + ""); } } }
客户端使用信道工厂进行创建信道,使用了与服务端相同的报头信息。服务端运行后,再运行客户端能正常访问。
客户端如果没有在EndpointAddress上指定报头,也可以通过OutgoingMessageHeaders直接在消息上添加报头信息。
static void Main(string[] args) { AddressHeader header = AddressHeader.CreateAddressHeader("kk", "www.kk.com", "kkname"); mywcf.ICalculatorService client = ChannelFactory<mywcf.ICalculatorService>.CreateChannel(new WSHttpBinding(), new EndpointAddress("http://localhost:8899")); using (OperationContextScope scope = new OperationContextScope(client as IContextChannel)) { OperationContext.Current.OutgoingMessageHeaders.Add(header.ToMessageHeader()); Console.WriteLine(client.Add(1, 2) + ""); } }
若将客户端的报头地址改成如下:
AddressHeader header1=AddressHeader.CreateAddressHeader("kd","www.kk.com","kkname");
会引发EndpointNotFoundException异常,因为服务端和客户端地址报头信息不一致,使用默认的消息筛选机制无法找到对应的终结点。
如果不想修改客户端地址报头,又想要避免这个错误,可以修改服务行为的消息筛选机制。筛选机制AddressFilterMode有3种,Exact,Predix,Any。
Exact是默认的筛选机制,要求客户端与服务端的地址报头精确匹配。Predix是前缀匹配,Any是任意匹配。将服务行为的AddressFilterMode 修改成任意匹配,即使客户端与服务端的地址报头不一致也可以访问了。AddressHeader与安全的作用不大,真正的作用是辅助寻址。因为客户端通过wsdl是可以获取到服务端完整的报头信息。
修改如下:
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)] public class CalculatorService : ICalculatorService { public int Add(int x, int y) { return x + y; } }