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.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.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字符串,然后在编码到另外的消息中的形式来实现自定义消息编码的。这个过程有三个主要的难点:
- 如何创建自定义的Message对象
- 如何将原始消息转换为Base64正文的消息
- 如何从Base64正文消息转换为原始消息。
我通过三个函数,分别实现了这三个难点:
1. 自定义的BodyWriter
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正文的消息
{
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消息为原始消息
{
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.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.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.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的一些基础知识可以访问如下链接:
|
|
项目文件: /Files/jillzhang/Robin_Wcf_Formatter.rar
出处:http://jillzhang.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。