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/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


















































浙公網安備 33010602011771號