我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过程中,期间发送和接收的SOAP是什么样子。当然,也有人是通过借助其他工具来抓取数据包来查看。那,有没有办法让程序自己输出相应的SOAP信息呢?

要对SOAP消息进行拦截和修改,我们需要实现两个接口,它们都位于System.ServiceModel.Dispatcher (程序集System.ServiceModel)。下面分别价绍。

接口一:IClientMessageInspector

从名字中我们可以猜测,它是用来拦截客户消息的,而看看它的方法,你就更加肯定当初的猜测了。

  • BeforeSendRequest:向服务器发送请求前拦截或修改消息(事前控制)
  • AfterReceiveReply:接收到服务器的回复消息后,在调用返回之前拦截或修改消息(事后诸葛亮)

接口二:IDispatchMessageInspector

刚才那个接口是针对客户端的,而这个是针对服务器端的。

  • AfterReceiveRequest:接收客户端请求后,在进入操作处理代码之前拦截或修改消息(欺上)
  • BeforeSendReply:服务器向客户端发送回复消息之前拦截和修改消息(瞒下)。

虽然实现了这两个接口,但你会有新的疑问,怎么用?把它们放到哪儿才能拦截消息?因此,下一步就是要实现IEndpointBehavior按口(System.ServiceModel.Description命名空间,程序集System.ServiceModel),它有四个方法,而我们只需要处理两个就够了

新建一个类库应用,然后添加System.ServiceModel程序集的引用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
using System.ServiceModel.Channels; namespace MyLib
{
/// <summary>
/// 消息拦截器
/// </summary>
public class MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector
{
void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
{
Console.WriteLine("客户端接收到的回复:\n{0}", reply.ToString());
} object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
{
Console.WriteLine("客户端发送请求前的SOAP消息:\n{0}", request.ToString());
return null;
} object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
Console.WriteLine("服务器端:接收到的请求:\n{0}", request.ToString());
return null;
} void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
{
Console.WriteLine("服务器即将作出以下回复:\n{0}", reply.ToString());
}
} /// <summary>
/// 插入到终结点的Behavior
/// </summary>
public class MyEndPointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
// 不需要
return;
} public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
// 植入“偷听器”客户端
clientRuntime.ClientMessageInspectors.Add(new MyMessageInspector());
} public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// 植入“偷听器” 服务器端
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector());
} public void Validate(ServiceEndpoint endpoint)
{
// 不需要
return;
}
} }

这一步,我们先建立服务器端。

记得要引用我们刚才写的类库。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Runtime;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Description; namespace WCFServer
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof (Service)))
{
WSHttpBinding bingding = new WSHttpBinding(); host.AddServiceEndpoint(typeof (IService), bingding, "http://127.0.0.1:8888/service1"); foreach (var endpoint in host.Description.Endpoints)
{
endpoint.EndpointBehaviors.Add(new MyLib.MyEndPointBehavior());
} ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
behavior.HttpGetEnabled = true;
behavior.HttpGetUrl = new Uri("http://127.0.0.1:8888/service"); //httpGetUrl客户端引用的地址
host.Description.Behaviors.Add(behavior);
host.Opened += delegate
{
Console.WriteLine("服务已启动");
Console.ReadKey();
};
host.Open();
}
}
} [ServiceContract(Namespace = "MyNamespace")]
public interface IService
{
[OperationContract]
int AddInt(int a, int b);
[OperationContract]
Student GetStudent();
[OperationContract]
CalResultResponse ComputingNumbers(CalcultRequest inMsg);
} [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class MyService : IService
{
public int AddInt(int a, int b)
{
return a + b;
} public Student GetStudent()
{
Student stu = new Student();
stu.StudentName = "小明";
stu.StudentAge = ;
return stu;
} public CalResultResponse ComputingNumbers(CalcultRequest inMsg)
{
CalResultResponse rmsg = new CalResultResponse();
switch (inMsg.Operation)
{
case "加":
rmsg.ComputedResult = inMsg.NumberA + inMsg.NumberB;
break;
case "减":
rmsg.ComputedResult = inMsg.NumberA - inMsg.NumberB;
break;
case "乘":
rmsg.ComputedResult = inMsg.NumberA * inMsg.NumberB;
break;
case "除":
rmsg.ComputedResult = inMsg.NumberA / inMsg.NumberB;
break;
default:
throw new ArgumentException("运算操作只允许加、减、乘、除。");
break;
}
return rmsg;
}
} [DataContract]
public class Student
{
[DataMember]
public string StudentName;
[DataMember]
public int StudentAge;
} [MessageContract]
public class CalcultRequest
{
[MessageHeader]
public string Operation;
[MessageBodyMember]
public int NumberA;
[MessageBodyMember]
public int NumberB;
} [MessageContract]
public class CalResultResponse
{
[MessageBodyMember]
public int ComputedResult;
}
}

接下来,实现客户端。

a、引用刚才写的类库MyLib;

b、引用WCF服务。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace WCFClient
{
class Program
{
static void Main(string[] args)
{
WS.ServiceClient client = new WS.ServiceClient();
// 记得在客户端也要插入IEndPointBehavior
client.Endpoint.EndpointBehaviors.Add(new MyLib.MyEndPointBehavior());
try
{
// 1、调用带元数据参数和返回值的操作
Console.WriteLine("\n20和35相加的结果是:{0}", client.AddInt(, ));
// 2、调用带有数据协定的操作
WS.Student student = client.GetStudent();
Console.WriteLine("\n学生信息---------------------------");
Console.WriteLine("姓名:{0}\n年龄:{1}", student.StudentName, student.StudentAge);
// 3、调用带消息协定的操作
Console.WriteLine("\n15乘以70的结果是:{0}", client.ComputingNumbers("乘", , ));
}
catch (Exception ex)
{
Console.WriteLine("异常:{0}", ex.Message);
} client.Close();
Console.ReadKey();
}
}
}

结果:

知道了如何拦截消息,那么修改消息就不难了。

现在我们把前面写的类库MyLib。

将消息拦截器MyMessageInspector作如下修改:

    /// <summary>
/// 消息拦截器
/// </summary>
public class MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector
{
void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
{
//Console.WriteLine("客户端接收到的回复:\n{0}", reply.ToString());
return;
} object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
{
//Console.WriteLine("客户端发送请求前的SOAP消息:\n{0}", request.ToString());
// 插入验证信息
MessageHeader hdUserName = MessageHeader.CreateHeader("u", "fuck", "admin");
MessageHeader hdPassWord = MessageHeader.CreateHeader("p", "fuck", "");
request.Headers.Add(hdUserName);
request.Headers.Add(hdPassWord);
return null;
} object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
//Console.WriteLine("服务器端:接收到的请求:\n{0}", request.ToString());
// 栓查验证信息
string un = request.Headers.GetHeader<string>("u", "fuck");
string ps = request.Headers.GetHeader<string>("p", "fuck");
if (un == "admin" && ps == "abcd")
{
Console.WriteLine("用户名和密码正确。");
}
else
{
throw new Exception("验证失败,滚吧!");
}
return null;
} void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
{
//Console.WriteLine("服务器即将作出以下回复:\n{0}", reply.ToString());
return;
}
}

注意:添加对System.Runtime.Serialization的引用。

创建消息头时,第一个参数是名字,如上面的“u”,第二个参数是命名空间,这个可以自己来定义,比如上面的“fuck”,第三个参数就是消息头的内容。

现在重新生成一下项目,再试试。

前面我们说过,如果安装证书进行身份验证会相当TMD麻烦,而可以通过修改SOAP消息头来验证,但是,上次的做法会有一个麻烦,那就是每次调用操作协定都要手动修改一次,这一次,我们直接在终结点级别进行修改和验证,就省去了许多功夫。

重温WCF之消息拦截与篡改(八)的更多相关文章

  1. 传说中的WCF:消息拦截与篡改

    我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过程中,期间发送和接收的SOAP是什么样子.当然,也有人是通过借 ...

  2. 传说中的WCF(10):消息拦截与篡改

    我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过 程中,期间发送和接收的SOAP是什么样子.当然,也有人是通过 ...

  3. 重温WCF之消息契约(MessageContract)(六)

    对于SOAP来说主要由两部分构成Header和Body,他们两个共同构成了SOAP的信封,通常来说Body保存具体的数据内容,Header保存一些上下文信息或关键信息.比如:在一些情况下,具有这样的要 ...

  4. WCF消息拦截,利用消息拦截做身份验证服务

    本文参考  http://blog.csdn.net/tcjiaan/article/details/8274493  博客而写 添加对信息处理的类 /// <summary> /// 消 ...

  5. 【原创经验分享】WCF之消息队列

    最近都在鼓捣这个WCF,因为看到说WCF比WebService功能要强大许多,另外也看了一些公司的招聘信息,貌似一些中.高级的程序员招聘,都有提及到WCF这一块,所以,自己也关心关心一下,虽然目前工作 ...

  6. WCF技术剖析之二十八:自己动手获取元数据[附源代码下载]

    原文:WCF技术剖析之二十八:自己动手获取元数据[附源代码下载] 元数据的发布方式决定了元数据的获取行为,WCF服务元数据架构体系通过ServiceMetadataBehavior实现了基于WS-ME ...

  7. 自定义HTTP消息拦截

    /// <summary> /// HTTP消息拦截器 /// </summary> public class RequestHandler : DelegatingHandl ...

  8. Windows消息拦截技术的应用

    Windows消息拦截技术的应用 民航合肥空管中心 周毅 一.前 言 众所周知,Windows程式的运行是依靠发生的事件来驱动.换句话说,程式不断等待一个消息的发生,然后对这个消息的类型进行判断,再做 ...

  9. Windows消息拦截技术的应用(作者博客里有许多相关文章)

    民航合肥空管中心 周毅 一.前 言 众所周知,Windows程式的运行是依靠发生的事件来驱动.换句话说,程式不断等待一个消息的发生,然后对这个消息的类型进行判断,再做适当的处理.处理完此次消息后又回到 ...

随机推荐

  1. 如何解决phpcms后台验证码不显示的问题

    方法一: 主要在于是否开启gd库 查看办法 找到php.ini文件 搜索extension=php_gd2.dll这段代码(windows) 然后把前面的;符号去掉即可. centOS6.5中可能需要 ...

  2. ip netns相关命令

    1.增加虚拟网络命名空间   ip netns add net0   2.显示所有的虚拟网络命名空间 EULER:~ # ip netns list net0 也可通过查看/var/run/netns ...

  3. dubbo main方法启动

    public static void main(String[] args) { com.alibaba.dubbo.container.Main.main(args); } 以上就可以简单本地启动了

  4. js控制精度的加减乘除:js浮点数计算问题

    //加法函数 function accAdd(arg1, arg2) { var r1, r2, m; try { r1 = arg1.toString().split(".")[ ...

  5. HTTP请求报文和HTTP响应报文

    HTTP报文是面向文本的,报文中的每一个字段都是一些ASCII码串,各个字段的长度是不确定的.HTTP有两类报文:请求报文和响应报文. HTTP请求报文 一个HTTP请求报文由请求行(request ...

  6. jenkins结合ansible用shell实现自动化部署和回滚

    最近用jenkins+gitlab+ansible做持续化集成,自动化部署和版本回滚.然而deploy plugin没能做到增量升级和回滚操作,折腾了很久决定自己写个脚本来简单实现. 环境: cent ...

  7. centos7下搭建git和gitlab版本库

    系统:centos7 安装依赖: yum -y install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-d ...

  8. ios 中NSDateFormater中的特殊字符

    今天要把一个字符串转化为日期格式,这个字符串是服务器传过来的,如下: 2015-02-28T14:40:15 我开始使用这个格式来转化  yyyy-MM-ddThh:mm:ss ,一直返回nil,原来 ...

  9. Java for LeetCode 230 Kth Smallest Element in a BST

    解题思路: 直接修改中序遍历函数即可,JAVA实现如下: int res = 0; int k = 0; public int kthSmallest(TreeNode root, int k) { ...

  10. 【python】编码

    来源:廖雪峰 看到一篇很不错的讲python编码的文章,转过来 划重点: unicode是一种统一的编码方式,它将所有的编码方式都统一到了同一套规范中,避免了乱码问题. encode() 表示从 un ...