WCF进阶:将消息正文Base64编码

   大家好,很久没有写博文了,平时也是在用WCF做着项目,但不写文,总是感觉对技术的把握不够清楚全面。同时更主要的是和大家缺少很多沟通,有愧疚感呀。好了,闲话少叙,从今天起,我将推出WCF进阶系列博文。和大家一起来继续学习WCF。欢迎板砖!

   WCF有一套标准的安全机制,但使用过的朋友都清楚,这些机制在编程和配置上都非常麻烦,重要的是还需要证书,这也不太容易部署,本文讲述如何通过自定义Formatter将消息正文Base64编码传输,虽然安全级别不是太高,但也能够防止消息正文已明文形式传输。保护消息的秘密性。而且使用起来非常方便。通过学习本文,您能了解如下内容:1) 如何自定义消息记录 2) 如何修改消息 3) 如何实现自定义编码器。在WCF中消息是在通道堆栈中传递的,每个通道都可以对消息进行加工处理,上一个通道的输出为下一个通道的输入,而在消息通道中,WCF还开放很多Inspector,比如MessageInspector和ParamterInspector,通过这些拦截器,我们能实现自定义的消息记录和修改。WCF还提供了自定义Formatter的接口来实现自定义的编码器。我们就是利用WCF的上述接口来自定义编码,将消息正文编码为Base64的字符串,并且通过自定义的MessageInspector来记录消息内容。

先看一下如何实现自定义MessageInspector,在WCF中提供了两个接口: IClientMessageInspector和IDispatchMessageInspector,分别用于截取客户端消息和服务端消息。这两个接口的定义如下:

public interface IClientMessageInspector

{

// Methods

void AfterReceiveReply(ref Message reply, object correlationState);

object BeforeSendRequest(ref Message request, IClientChannel channel);

} 

 

public interface IDispatchMessageInspector

{

// Methods

object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext);

void BeforeSendReply(ref Message reply, object correlationState);

} 

 

实现了上述两个接口的类即是自定义的MessageInspector。客户端ClientRuntime对象和服务端的DispatchRumtime对象中管理中MessageInspectors集合,我们将自定义的MessageInspector实例化对象添加到集合中,MessageInspector即可生效。这个过程需要实现自定义的IEndpointBehavior。该接口的定义为:

public interface IEndpointBehavior

{

// Methods

void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters);

void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime);

void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher);

void Validate(ServiceEndpoint endpoint);

} 

 

下面有两个个代码片段,分别实现了自定义MessageInspector和EndpointBehavior

1 . 自定义的MessageInspector:

 

 

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;

namespace RobinLib
{
    
public class MessageInspector : IClientMessageInspector,IDispatchMessageInspector
    {
        
#region IClientMessageInspector 成员

        
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            Console.WriteLine(reply.ToString());
        }

        
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            Console.WriteLine(request.ToString());
            
return null;
        }

        
#endregion

        
#region IDispatchMessageInspector 成员

        
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            Console.WriteLine(request.ToString());
            
return null;
        }

        
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            Console.WriteLine(reply.ToString());
        }

        
#endregion
    }
}

 

 

2 . 用于自定义消息记录的自定义EndpointBehavior

 

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace RobinLib
{
    
public class OutputMessageBehavior : IEndpointBehavior
    {

        
#region IEndpointBehavior 成员

        
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {

        }

        
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(
new MessageInspector());
        }

        
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
new MessageInspector());
        }

        
public void Validate(ServiceEndpoint endpoint)
        {

        }

        
#endregion
    }
}

 

 

实现了上述两个类,我们就基本搞定了第一个问题:如何自定义消息记录,下面我们来探讨如何实现自定义Formatter。WCF提供了IClientMessageFormatter和IDispatchMessageFormatter两个接口类分别用于客户端和服务端的编解码。

 

public interface IClientMessageFormatter

{

// Methods

object DeserializeReply(Message message, object[] parameters);

Message SerializeRequest(MessageVersion messageVersion, object[] parameters);

} 

 

public interface IDispatchMessageFormatter

{

// Methods

void DeserializeRequest(Message message, object[] parameters);

Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result);

} 

 

实现这两个接口,就实现了自定义的编码器,但说句实在的,要实现完整通用的编码器并不是容易的事情。偷偷通过.Net Reflactor查看了下.Net Framework源码,缺省的实现代码量至少也有上千行了。我这次实现自定义的Formatter,其实是修改缺省Formatter编码好的消息,将消息的正文<S:Body></S:Body>中的内容转变成Base64字符串,然后在编码到另外的消息中的形式来实现自定义消息编码的。这个过程有三个主要的难点:

  1. 如何创建自定义的Message对象

 

  1. 如何将原始消息转换为Base64正文的消息
  2. 如何从Base64正文消息转换为原始消息。

 

我通过三个函数,分别实现了这三个难点:

 

1. 自定义的BodyWriter 

 

代码
using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.ServiceModel.Channels; 

using System.Xml; 

 

namespace RobinLib 



public class MyBodyWriter : BodyWriter 



string body = ""

XmlDocument doc; 

public MyBodyWriter(string body) 

base(true



this.body = body; 



 

public MyBodyWriter(XmlDocument doc) 

base(true



this.doc = doc; 



 

protected override void OnWriteBodyContents(XmlDictionaryWriter writer) 



XmlWriterSettings setting 
= new XmlWriterSettings(); 

setting.NewLineHandling 
= NewLineHandling.Entitize; 

setting.CheckCharacters 
= false

if (!string.IsNullOrEmpty(body)) 



writer.WriteRaw(body); 



if (doc != null



doc.WriteContentTo(writer); 

writer.Flush(); 









 

2. 转换原始消息为Base64正文的消息

 

代码
private Message Base64BodyMessage(Message ora_msg) 



MemoryStream ms 
= new MemoryStream(); 

XmlDictionaryWriter writer 
= XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8); 

ora_msg.WriteBodyContents(writer); 

writer.Flush(); 

string body = System.Text.Encoding.UTF8.GetString(ms.GetBuffer()); 

byte[] buffer = System.Text.Encoding.UTF8.GetBytes(body); 

body 
= Convert.ToBase64String(buffer); 

Message msg 
= Message.CreateMessage(ora_msg.Version, ora_msg.Headers.Action, new MyBodyWriter(body)); 

ora_msg.Close(); 

return msg; 



 

 

3. 转换Base64消息为原始消息

 

 

代码
private Message RestoreMessageFormBase64Message(Message message) 



Message msg 
= null

MemoryStream ms 
= new MemoryStream(); 

XmlDictionaryWriter writer 
= XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8); 

message.WriteBody(writer); 

writer.Flush(); 

string body = System.Text.Encoding.UTF8.GetString(ms.GetBuffer()); 

int index = body.IndexOf(">"); 

int index2 = body.IndexOf("</"); 

body 
= body.Substring(index + 1, index2 - index - 1); 

byte[] buffer2 = Convert.FromBase64String(body); 

body 
= System.Text.Encoding.UTF8.GetString(buffer2); 

XmlDocument doc 
= new XmlDocument(); 

doc.LoadXml(body); 

msg 
= Message.CreateMessage(message.Version, message.Headers.Action, new MyBodyWriter(doc)); 

msg.Headers.FaultTo 
= message.Headers.FaultTo; 

msg.Headers.From 
= message.Headers.From; 

msg.Headers.MessageId 
= message.Headers.MessageId; 

msg.Headers.RelatesTo 
= message.Headers.RelatesTo; 

msg.Headers.ReplyTo 
= message.Headers.ReplyTo; 

msg.Headers.To 
= message.Headers.To; 

foreach (var mh in message.Headers) 



if (mh is MessageHeader) 



if (mh.Name != "Action" && mh.Name != "FaultTo" && mh.Name != "From" && mh.Name != "MessageID" && mh.Name != "RelatesTo" && mh.Name != "ReplyTo" && mh.Name != "To"



msg.Headers.Add(mh 
as MessageHeader); 







msg.Properties.Clear(); 

foreach (var p in message.Properties) 



msg.Properties[p.Key] 
= p.Value; 



return msg; 



 

完整的代码见附件。实现了自定义的Formatter,我们还需要通过扩展OperationBehavior,将应用Formatter,因此我们创建了Base64BodyBehavior,代码为:

 

代码
using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.ServiceModel.Description; 

 

namespace RobinLib 



/// <summary> 

/// 将消息主题序列为base64的OperationBehavior 

/// </summary> 

public class Base64BodyBehavior : IOperationBehavior 



#region IOperationBehavior 成员 

 

public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) 



 



 

public void ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation) 



clientOperation.SerializeRequest 
= true

clientOperation.DeserializeReply 
= true

clientOperation.Formatter 
= new Base64BodyFormatter(clientOperation.Formatter); 



 

public void ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation) 



dispatchOperation.DeserializeRequest 
= true

dispatchOperation.SerializeReply 
= true

dispatchOperation.Formatter 
= new Base64BodyFormatter(dispatchOperation.Formatter); 



 

public void Validate(OperationDescription operationDescription) 



 



 

#endregion 





 

 

好了,目前我们已经将自定义消息记录和自定义编码器基本实现了,现在就涉及到如何在服务器代码和客户端代码中添加EndpointBehavior和OperationBehavior,我们用纯代码的形式做了一下实现, 

 

1. 在服务器托管代码中:

 

 

代码
using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.ServiceModel; 

 

namespace Robin_Wcf_Host_Console 



class Program 



static void Main(string[] args) 



//服务地址 

Uri baseAddress 
= new Uri("net.tcp://127.0.0.1:8081/Robin_Wcf_Formatter"); 

ServiceHost host 
= new ServiceHost(typeof(Robin_Wcf_SvcLib.Service1), new Uri[] { baseAddress }); 

//服务绑定 

NetTcpBinding bind 
= new NetTcpBinding(); 

host.AddServiceEndpoint(
typeof(Robin_Wcf_SvcLib.IService1), bind, ""); 

if (host.Description.Behaviors.Find<System.ServiceModel.Description.ServiceMetadataBehavior>() == null



System.ServiceModel.Description.ServiceMetadataBehavior svcMetaBehavior 
= new System.ServiceModel.Description.ServiceMetadataBehavior(); 

svcMetaBehavior.HttpGetEnabled 
= true

svcMetaBehavior.HttpGetUrl 
= new Uri("http://127.0.0.1:8001/Mex"); 

host.Description.Behaviors.Add(svcMetaBehavior); 



host.Opened
+=new EventHandler(delegate(object obj,EventArgs e){ 

Console.WriteLine(
"服务已经启动!"); 

}); 

foreach (var sep in host.Description.Endpoints) 



sep.Behaviors.Add(
new RobinLib.OutputMessageBehavior()); 

foreach (var op in sep.Contract.Operations) 



op.Behaviors.Add(
new RobinLib.Base64BodyBehavior()); 





host.Open(); 

Console.Read(); 







 

 

2.  在客户端代理代码中:

 

 

代码
[System.Diagnostics.DebuggerStepThroughAttribute()] 

[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.ServiceModel""3.0.0.0")] 

public partial class Service1Client : System.ServiceModel.ClientBase<Robin_Wcf_ClientApp.Robin_Wcf_Formatter_Svc.IService1>, Robin_Wcf_ClientApp.Robin_Wcf_Formatter_Svc.IService1 



 

public Service1Client() 



base.Endpoint.Behaviors.Add(new RobinLib.OutputMessageBehavior()); 

foreach (var op in base.Endpoint.Contract.Operations) 



op.Behaviors.Add(
new RobinLib.Base64BodyBehavior()); 





 

public Service1Client(string endpointConfigurationName) : 

base(endpointConfigurationName) 



base.Endpoint.Behaviors.Add(new RobinLib.OutputMessageBehavior()); 

foreach (var op in base.Endpoint.Contract.Operations) 



op.Behaviors.Add(
new RobinLib.Base64BodyBehavior()); 





 

public Service1Client(string endpointConfigurationName, string remoteAddress) : 

base(endpointConfigurationName, remoteAddress) 



base.Endpoint.Behaviors.Add(new RobinLib.OutputMessageBehavior()); 

foreach (var op in base.Endpoint.Contract.Operations) 



op.Behaviors.Add(
new RobinLib.Base64BodyBehavior()); 





 

public Service1Client(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 

base(endpointConfigurationName, remoteAddress) 



base.Endpoint.Behaviors.Add(new RobinLib.OutputMessageBehavior()); 

foreach (var op in base.Endpoint.Contract.Operations) 



op.Behaviors.Add(
new RobinLib.Base64BodyBehavior()); 





 

public Service1Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 

base(binding, remoteAddress) 



base.Endpoint.Behaviors.Add(new RobinLib.OutputMessageBehavior()); 

foreach (var op in base.Endpoint.Contract.Operations) 



op.Behaviors.Add(
new RobinLib.Base64BodyBehavior()); 





 

public string GetData(int value) 



return base.Channel.GetData(value); 



 

public Robin_Wcf_ClientApp.Robin_Wcf_Formatter_Svc.CompositeType GetDataUsingDataContract(Robin_Wcf_ClientApp.Robin_Wcf_Formatter_Svc.CompositeType composite) 



return base.Channel.GetDataUsingDataContract(composite); 





 

 

最后,我们比较一下记录到的原始消息和经过Base64 正文消息的对比

 

客户端请求原始消息

客户端Base64正文后消息:

 

服务器端原始消息:

 

 

服务端Base64正文后消息:

 

 

想要学习WCF的一些基础知识可以访问如下链接:

 

   

   

WCF从理论到实践一:揭开神秘面纱

 

   

WCF从理论到实践二:决战紫禁之巅

 

   

WCF从理论到实践三:八号当铺之黑色契约

 

   

WCF从理论到实践四:路在何方

 

   

WCF从理论到实践:Binding细解

 

   

WCF从理论到实践:WCF架构

 

   

WCF从理论到实践:消息交换模式

 

   

WCF从理论到实践:事件广播

 

   

WCF从理论到实践:实例模式和对象生命周期

 

   

WCF从理论到实践:异常处理

 

   

WCF从理论到实践-异步

 

   

WCF从理论到实践:事务

 

   

WCF从理论到实践:事务投票

 

   

WCF从理论到实践(14):WCF解决方案模板

 

   

WCF从理论到实践(15):响应变化

 

   

WCF从理论到实践(16):操作重载(带视频+ppt+源码)

 

   

WCF从理论到实践(17):OO大背离(带视频+ppt+源码)

 

   

Ajax访问Xml Web Service的安全问题以及解决方案

 

   

AjaxWCF交互-WCF之美

 

   

AjaxWcf交互-JSON

 

   

ExtJsWCF交互:生成树

 

   

ExtJs+Linq+Wcf打造简单grid

 
 

ExtJs+WCF+LINQ实现分页Grid

 

   

ExtJsWCF之间的跨域访问

 

   

异步调用RestfulWCF服务

 

   

利用WCFRestful进行上传下载

  

项目文件: /Files/jillzhang/Robin_Wcf_Formatter.rar

 

posted @ 2010-04-11 00:11  Robin Zhang  阅读(5184)  评论(20编辑  收藏  举报