【WCF】为终结点地址应用地址头

记得不久前,老周写过博文,探讨过在ContextScope以一定的范内向发出的消息中插入消息头,scope只能为特定的某一次服务操作的调用而添加SOAP头,要是需要在每次调用操作协定的时候都插上Header,一种方法可以自定义实现消息拦截器,拦截服务传输的消息,并向消息添加header,关于拦截器,老周后面会介绍。另一种方法,就是本文的主题——地址头。

我们都知道,服务对外公开后,客户端可以把消息传输到终结点指定的监听URI上,终结点收到消息后,通过Dispatcher把消息调度到对应用的通道(channel)上,最后找到对应的服务协定,并将收到的输入参数传给服务操作,进而调用服务。服务调用后,操作结果会沿着相反的顺序传回客户端,即返回的内容被包装为SOAP消息,放入通道栈,再输送回调度程序,最后发回客户端。

终结点以Binding确定通信协议和方式,并必须指定一个有效的地址来接收调用消息,为了能够调用服务操作,还得指定一个服务协定。如果为终结点的地址加上header,那么,只要服务是通过该终结点调用的,则每一次调用都会自动把地址头插入到传出的消息中。这样我们就不必要每次调用都去添加消息头了。

不知不觉就说了一堆F话,下面,咱们来做一个例子,这个例子是通过代码来指定地址头。

首先,当然是声明服务协定。

    [ServiceContract]
    public interface IDemo
    {
        [OperationContract]
        string Hello(string name);
    }

然后,实现服务类。

    class MyService : IDemo
    {
        public string Hello(string name)
        {
            return $"Hello, {name}.";
        }
    }

服务的实现比较简单,我们的重点不是这些,所以随意弄弄。

接下来就是重点了,咱们用代码来添加地址头。地址头可以添加任意内容,最终会被序列化为XML元素,插入到消息头列表中。假设,我们定义一个名为AddressInfo的数据协定。

    [DataContract(Namespace = "addr-info", Name = "addr_info")]
    public class AddressInfo
    {
        /// <summary>
        /// 省
        /// </summary>
        [DataMember(Name = "province")]
        public string Province { get; set; }

        /// <summary>
        /// 市
        /// </summary>
        [DataMember(Name = "city")]
        public string City { get; set; }

        /// <summary>
        /// 详细地址
        /// </summary>
        [DataMember(Name = "details")]
        public string DetailAddress { get; set; }

        /// <summary>
        /// 邮政编号
        /// </summary>
        [DataMember(Name = "zipcode")]
        public string ZipCode { get; set; }
    }

最后建立服务寄宿,并添加终结点。

            Uri endpointAddress = new Uri("http://localhost:2016/demo");
            using(ServiceHost host=new ServiceHost(typeof(MyService)))
            {
                // 添加终结点,必须在Open之前完成。
                // Open之后修改就不会生效了。

                // 1、准备地址头
                AddressInfo addrinfo = new AddressInfo
                {
                    Province = "广东省",
                    City = "东莞市",
                    ZipCode = "523xxx",
                    DetailAddress = "火星镇XX小区非人道主义研究大厦K008室"
                };
                // 创建Header
                AddressHeader hd = AddressHeader.CreateAddressHeader(addrinfo);

                // 2、创建EndpointAddress
                EndpointAddress epaddress = new EndpointAddress(endpointAddress, hd);

                // 3、创建终结点实例
                BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
                ServiceEndpoint svendp = new ServiceEndpoint(ContractDescription.GetContract(typeof(IDemo)), binding, epaddress);

                // 4、添加终结点到host中
                host.AddServiceEndpoint(svendp);

                // 5、打开服务
                try
                {
                    host.Open();
                    Console.WriteLine("服务已启动。");
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

                Console.Read();
            }

AddressHeader类表示一个消息头,它是抽象类,提供内部实现,使用时应调用它公开的CreateAddressHeader静态方法来得到一个AddressHeader实例,参数必须是一个可以XML序列化的对象,这里用的是我刚刚定义的数据协定,它当然可以XML序列化了。

如果是要创建带有一些基本类型值的头,可以这样用:

  AddressHeader hd3 = AddressHeader.CreateAddressHeader("element", "my-namespace", 300);

第一个参数是XML元素的名字,第二个参数是XML命名空间,一般以URI形式表示,当然你可以随便写,只要不带非法字符就行。第三个参数是Header中的内容,即value,这里用的是整数值300。

地址头可以在实例化EndpointAddress时通过构造函数传递。

    EndpointAddress epaddress = new EndpointAddress(endpointAddress, hd);

下面,我们来调用一下服务。

                // 1、准备地址头
                AddressInfo cl_addrinfo = new AddressInfo();
                cl_addrinfo.Province = "广东省";
                cl_addrinfo.City = "东莞市";
                cl_addrinfo.DetailAddress = "火星镇XX小区非人道主义研究大厦K008室";
                cl_addrinfo.ZipCode = "523xxx";
                AddressHeader hdclient = AddressHeader.CreateAddressHeader(cl_addrinfo);
                // 2、创建终结点地址,并附带地址头
                EndpointAddress clepaddr = new EndpointAddress(endpointAddress, hdclient);
                // 3、创建binding,必须与服务器的定义一致
                BasicHttpBinding clientbinding = new BasicHttpBinding(BasicHttpSecurityMode.None);
                // 4、创建通道工厂
                ChannelFactory<IDemo> factory = new ChannelFactory<IDemo>(clientbinding, clepaddr);
                // 创建通道,由于内部已实现,可以用协定直接作为通道对象
                IDemo channel = factory.CreateChannel();
                // 5、调用服务
                string response = channel.Hello("Jack");
                Console.WriteLine("调用结果:{0}", response);
                factory.Close();

插入到消息在的地址头如下图所示。

由于服务器在定义终结点地址时插入了地址头,所以,在客户端调用时,终结点地址和附带的地址头必须要与服务器所指定的一致,否则会调用失败。

比如,我们把上面客户端调用代码中的地址头信息改为“广州市”,然后再次调用服务。

                AddressInfo cl_addrinfo = new AddressInfo();
                cl_addrinfo.Province = "广东省";
                cl_addrinfo.City = "广州市";

这时候调用服务,你会看到以下精彩一幕。

所以说啊,客户端在调用时,必须提供与服务器指定完全相同的地址头。这是默认的地址筛选方案导致的,默认情况下,两者的地址头必须完全一样才能通过筛选。

那么,有朋友一定会问,能不能让服务器和客户端所指定的地址头不一样,但又可以让服务器匹配筛选呢?关于这个问题嘛,老周会在下一篇烂文中给大伙伴们介绍。

-----------------------------------------------------------------

上面说了用代码的方式来指定地址头,那么,对应地,肯定得看看如何在配置文件中指定地址头了。

由于配置文件本身就是一个XML文件,所以,在配置文件中指定就更好办了,直接写XML节点就行了。

比如,服务器可以这样:

        <endpoint address="http://localhost:1399/demo" binding="basicHttpBinding" contract="Test0160804.IDemo">
          <headers>
            <book xmlns="book-store">
              <name>三国演义</name>
              <date>2007-3-19</date>
              <price>39.88</price>
            </book>
          </headers>
        </endpoint>

<headers>节点下可以添加任意XML节点,只要XML元素是完整的就行了。在处理SOAP消息时,是通过XML命名空间和元素名称来查找消息头的,所以,老周建议大家不要添加元素名和命名空间相同的多个头。比如这样

          <headers>
            <book xmlns="book-store">
              <name>三国演义</name>
              <date>2007-3-19</date>
              <price>39.88</price>
            </book>
            <book xmlns="book-store">
              <name>红岩</name>
              <date>2003-10-5</date>
              <price>26.5</price>
            </book>
          </headers>

当然,这样添加地址头,只要客户端调用时提供相同的地址头,是不会出错的,不过,如果你希望在代码中通过GetHeader<T>或FindHeader方法来获取消息头的话,要是找到元素名和命名空间相同的头,就会发生异常。这时候,你就只好使用最笨的方法来读取消息头了——读取普通XML文档的方式。

所以,为了减少处理麻烦,最好不要在地址头中放置相同结构的XML元素,当然了,如果你不打算在代码中读取消息头,而仅仅是为了加强客户端与服务器的地址验证,就无所谓了。

在客户端,配置文件中指定的地址头必须与服务器的一致。

    <client>
      <endpoint name="clep" address="http://localhost:1399/demo" binding="basicHttpBinding" contract="Test0160804.IDemo">
        <headers>
          <book xmlns="book-store">
            <name>三国演义</name>
            <date>2007-3-19</date>
            <price>39.88</price>
          </book>
          <book xmlns="book-store">
            <name>红岩</name>
            <date>2003-10-5</date>
            <price>26.5</price>
          </book>
        </headers>
      </endpoint>
    </client>

这样做是为了增强对终结点地址的验证。

有关如何自行定义地址头筛选方案,而不是使用默认的完全匹配方案,下次再聊,88。

示例代码下载:http://files.cnblogs.com/files/tcjiaan/addressheaderSample.zip

时间: 2024-10-22 08:14:40

【WCF】为终结点地址应用地址头的相关文章

WCF 配置终结点并调用服务

wcf通过xml文件配置终结点什么的感觉有点小麻烦,个人还是觉得用代码形式配置比较好,当然在发布的时候可能会比较麻烦,需要重新编译... 下面将wcf service寄宿在控制台应用程序中并配置终结点: ? 1 2 3 4 5 6 7 8 9 10 11 using (var host = new ServiceHost(typeof(Service1),                                               new Uri("http://localhos

QT通过IP地址定位地址(用get方法取数据)

通过IP地址定位地址,是要通过查询数据库,如果自己做一个这样的数据库工作量就比较大,所以在网上找了一个查询IP地址的网址,通过调用这个网址查询来实现,但是这个有一定的弊端,如果没有网络或者这个网址不可用时,就无法查询.具体代码如下: QEventLoop loop; QNetworkAccessManager manager;    QNetworkReply *pReply = manager.get(QNetworkRequest(QUrl("http://www.ip38.com/&quo

Centos/Linux下如何查看网关地址/Gateway地址

Centos/Linux下如何查看网关地址/Gateway地址? Linux下查看网关的命令还是很多的,不过如果IP是DHCP获取,那么有些命令是不适用的,当然也有通用的查询网关命令. 1.ifconfig -a 和 cat /etc/resolv.conf  (主要查看ip/netmask和dns) 2.netstat -rn 3.cat /etc/sysconfig/network 4.cat /etc/sysconfig/network-scripts/ifcfg-eth0 5.trace

数组首地址取地址

一.问题来由 普通指针可被改动导致地址偏移: #include <iostream> using namespace std; int main(int argc,char *argv[]) { int a = 6; int *p = &a; //p存放一个地址.pp存放p的地址,上面的代码能够让p存放的地址偏移 cout<<&a<<endl; int *pp = (int *)&p; cout<<p<<endl; (*p

指针&amp;指针的指针,地址&amp;地址的地址

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 int main(int argc, char* argv[]) 5 { 6 char *s[]={"man","woman","girl","boy","sister"}; 7 char* *q=NULL; //指针的指针 8 int k; 9

Team Foundation Server (TFS)与Project Server集成,使用DNS(友好地址)地址注册PWA

问题描述: 当Team Foundation Server(TFS 2010/2012/2013)与Project Server高可用性的环境集成时,必然会使用Project Server (PWA)的DNS地址注册PWA站点(RegisterPWA),而不是使用Project Server的计算机名注册,这样就会出现如下图所示的问题: 下图是执行注册PWA命令时出错的截屏 注册命令:tfsadmin project server /RegisterPWA /tfs:http://tfs2013

第29篇ip地址,mac地址 IPV4 IPV6 TCP UDP协议

回顾 2018-12-31 或者 2018.12.31 或者 2018*12*31 的正则表达式: [1-9]\d{3}(?P<sep>.)(1[12]|0?[1-9])(?P=sep)([12]\d|3[01]|0?[1-9])内容总览: ip地址 mac地址 IPV4 IPV6 TCP UDP协议 同一台机器的两个程序通讯-->文件 两台机器的两个程序之间通讯 -->网络 mac 每一台计算机的网卡 上面会有一个mac地址,也就是相当于改计算机在网络上的唯一身份表示 xx-xx

各种变量在内存中的高地址低地址

先确认一下这里"低地址高地址"的定义,这里并不是指的大端小端中的地址高低,而是内存中的地址 1.全局变量 先定义的全局变量位于低地址,后定义的位于高地址. 2.栈中变量 (栈中变量指的是由编译器自动分配释放的变量) 由于栈是往低地址生长的,所以先声明的变量位于高地址. 3.堆中变量 (堆中变量指的是由程序员分配释放的变量,例如new,malloc) 结论:由于堆是往高地址生长的,所以先声明的变量位于低地址. 原文地址:https://www.cnblogs.com/pjl1119/p/

【WCF】终结点的监听地址

终结点主要作用是向客户端公开一些信息入口,通过这个入口,可以找到要调用的服务操作.通常,终结点会使用三个要素来表述,我记得老蒋(网名:Artech,在园子里可以找到他)在他有关WCF的书里,把这三要素称为“ABC”. A就是Address,就是终结点的地址:B是Binding,绑定,用于描述传输的协议.是否启用安全模式等:C是Contract,即服务协定. 一个服务协定可以由多个终结点公开,比如一个终结点可能使用HTTP协议,另一个则使用TCP等. WCF是否真的像某些人说的那么复杂难学呢?依老