本文将定义一个 WCF 终结点行为扩展,以在 WCF 中使用更高效的 BinaryFormatter 进行二进制序列化,并实现对是否使用传统二进制序列化功能的可配置。
介绍
实现步骤
使用方法
效果
介绍
在 OEA 框架中,是使用 WCF 作为数据传输框架。但是使用 WCF 内部的二进制序列化,序列化后的数据大小,要比使用传统的 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 类进行序列化后的数据大小要大得多。作为使用 .NET 框架的系统内部互联,往往期望在使用 WCF 获取统一传输方案的同时,还能得到 BinaryFormatter 类的序列化性能。所以本篇文章将设计一个 WCF 终结点行为扩展,来配置是否使用 BinaryFormatter 进行数据的序列化。
只能在操作上添加二进制序列化的行为。这是因为 WCF 的扩展点中,只有操作才支持设置 IClientMessageFormatter 及 IDispatchMessageFormatter。
WCF 中,要实现替换操作的序列化器,最直接的方式应该是使用一个实现 IOperationBehavior 的特性(Attribute),并将该特性直接标记到操作方法上。但是,这样会导致该方法在所有的终结点都使用 BinaryFormatter 来进行序列化。这并不是我们所想要的,所以只能使用配置的方法来对 WCF 进行扩展。
实现步骤
封装 BinaryFormatter
首先,需要对 BinaryFormatter 进行一个简单的封装。该类使用 BinaryFormatter 来实现对象到二进制流的序列化及反序列化。
1: /// <summary> 2: /// 序列化门户 API 3: /// </summary> 4: public static class Serializer 5: { 6: /// <summary> 7: /// 使用二进制序列化对象。 8: /// </summary> 9: /// <param name="value"></param> 10: /// <returns></returns> 11: public static byte[] SerializeBytes(object value) 12: { 13: if (value == null) return null; 14: 15: var stream = new MemoryStream(); 16: new BinaryFormatter().Serialize(stream, value); 17: 18: //var dto = Encoding.UTF8.GetString(stream.GetBuffer()); 19: var bytes = stream.ToArray(); 20: return bytes; 21: } 22: 23: /// <summary> 24: /// 使用二进制反序列化对象。 25: /// </summary> 26: /// <param name="bytes"></param> 27: /// <returns></returns> 28: public static object DeserializeBytes(byte[] bytes) 29: { 30: if (bytes == null) return null; 31: 32: //var bytes = Encoding.UTF8.GetBytes(dto as string); 33: var stream = new MemoryStream(bytes); 34: 35: var result = new BinaryFormatter().Deserialize(stream); 36: 37: return result; 38: } 39: }
添加 BinaryFormatterAdapter
添加一个 BinaryFormatterAdapter 类型,该类实现了从 WCF 序列化器到 BinaryFormatter 的甜适配。它实现 IClientMessageFormatter 及 IDispatchMessageFormatter 两个接口,并调用 Serializer 来进行二进制序列化。
1: namespace OEA.WCF 2: { 3: /// <summary> 4: /// 在内部序列化器的基础上添加 Remoting 二进制序列化的功能。 5: /// </summary> 6: internal class BinaryFormatterAdapter : IClientMessageFormatter, IDispatchMessageFormatter 7: { 8: private IClientMessageFormatter _innerClientFormatter; 9: private IDispatchMessageFormatter _innerDispatchFormatter; 10: private ParameterInfo[] _parameterInfos; 11: private string _operationName; 12: private string _action; 13: 14: /// <summary> 15: /// for client 16: /// </summary> 17: /// <param name="operationName"></param> 18: /// <param name="parameterInfos"></param> 19: /// <param name="innerClientFormatter"></param> 20: /// <param name="action"></param> 21: public BinaryFormatterAdapter( 22: string operationName, 23: ParameterInfo[] parameterInfos, 24: IClientMessageFormatter innerClientFormatter, 25: string action 26: ) 27: { 28: if (operationName == null) throw new ArgumentNullException("methodName"); 29: if (parameterInfos == null) throw new ArgumentNullException("parameterInfos"); 30: if (innerClientFormatter == null) throw new ArgumentNullException("innerClientFormatter"); 31: if (action == null) throw new ArgumentNullException("action"); 32: 33: this._innerClientFormatter = innerClientFormatter; 34: this._parameterInfos = parameterInfos; 35: this._operationName = operationName; 36: this._action = action; 37: } 38: 39: /// <summary> 40: /// for server 41: /// </summary> 42: /// <param name="operationName"></param> 43: /// <param name="parameterInfos"></param> 44: /// <param name="innerDispatchFormatter"></param> 45: public BinaryFormatterAdapter( 46: string operationName, 47: ParameterInfo[] parameterInfos, 48: IDispatchMessageFormatter innerDispatchFormatter 49: ) 50: { 51: if (operationName == null) throw new ArgumentNullException("operationName"); 52: if (parameterInfos == null) throw new ArgumentNullException("parameterInfos"); 53: if (innerDispatchFormatter == null) throw new ArgumentNullException("innerDispatchFormatter"); 54: 55: this._innerDispatchFormatter = innerDispatchFormatter; 56: this._operationName = operationName; 57: this._parameterInfos = parameterInfos; 58: } 59: 60: Message IClientMessageFormatter.SerializeRequest(MessageVersion messageVersion, object[] parameters) 61: { 62: var result = new object[parameters.Length]; 63: for (int i = 0; i < parameters.Length; i++) { result[i] = Serializer.SerializeBytes(parameters[i]); } 64: 65: return _innerClientFormatter.SerializeRequest(messageVersion, result); 66: } 67: 68: object IClientMessageFormatter.DeserializeReply(Message message, object[] parameters) 69: { 70: var result = _innerClientFormatter.DeserializeReply(message, parameters); 71: 72: for (int i = 0; i < parameters.Length; i++) { parameters[i] = Serializer.DeserializeBytes(parameters[i] as byte[]); } 73: result = Serializer.DeserializeBytes(result as byte[]); 74: 75: return result; 76: } 77: 78: void IDispatchMessageFormatter.DeserializeRequest(Message message, object[] parameters) 79: { 80: _innerDispatchFormatter.DeserializeRequest(message, parameters); 81: 82: for (int i = 0; i < parameters.Length; i++) { parameters[i] = Serializer.DeserializeBytes(parameters[i] as byte[]); } 83: } 84: 85: Message IDispatchMessageFormatter.SerializeReply(MessageVersion messageVersion, object[] parameters, object result) 86: { 87: var seralizedParameters = new object[parameters.Length]; 88: for (int i = 0; i < parameters.Length; i++) { seralizedParameters[i] = Serializer.SerializeBytes(parameters[i]); } 89: var serialzedResult = Serializer.SerializeBytes(result); 90: 91: return _innerDispatchFormatter.SerializeReply(messageVersion, seralizedParameters, serialzedResult); 92: } 93: } 94: }
添加 BinaryFormatterOperationBehavior
添加 BinaryFormatterOperationBehavior 操作行为类。这个类会设置客户端、服务端的操作的序列化器。
1: namespace OEA.WCF 2: { 3: /// <summary> 4: /// 在原始 Formatter 的基础上装饰 BinaryFormatterAdapter 5: /// <remarks> 6: /// BinaryFormatterOperationBehavior 为什么要实现为操作的行为: 7: /// 因为只有当操作的 DataContractSerializerBehavior 行为应用功能后,才能拿到 DataContractSerializerFormatter 并包装到 BinaryFormatterAdapter 中。 8: /// 9: /// 由于一个操作的操作契约在系统中只有一份。而我们期望序列化的行为只影响指定的终结点,所以这个行为在应用时,会检查是否传入的运行时,即是添加时的运行时。 10: /// </remarks> 11: /// </summary> 12: internal class BinaryFormatterOperationBehavior : IOperationBehavior 13: { 14: private object _runtime; 15: 16: internal BinaryFormatterOperationBehavior(object runtime) 17: { 18: _runtime = runtime; 19: } 20: 21: /// <summary> 22: /// 本行为只为这个运行时起作用。 23: /// </summary> 24: public object ParentRuntime 25: { 26: get { return _runtime; } 27: } 28: 29: public void ApplyClientBehavior(OperationDescription description, ClientOperation runtime) 30: { 31: if (_runtime == runtime.Parent) 32: { 33: //在之前的创建的 Formatter 的基础上,装饰新的 Formatter 34: runtime.Formatter = new BinaryFormatterAdapter(description.Name, runtime.SyncMethod.GetParameters(), runtime.Formatter, runtime.Action); 35: } 36: } 37: 38: public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation runtime) 39: { 40: if (_runtime == runtime.Parent) 41: { 42: runtime.Formatter = new BinaryFormatterAdapter(description.Name, description.SyncMethod.GetParameters(), runtime.Formatter); 43: } 44: } 45: 46: public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters) { } 47: 48: public void Validate(OperationDescription description) { } 49: } 50: }
添加终结点行为 EnableBinaryFormatterBehavior
添加终结点行为 EnableBinaryFormatterBehavior,实现为该终结点下的所有操作添加 BinaryFormatterOperationBehavior 的逻辑。
1: namespace OEA.WCF 2: { 3: class EnableBinaryFormatterBehavior : IEndpointBehavior 4: { 5: public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) 6: { 7: foreach (var operation in endpoint.Contract.Operations) 8: { 9: DecorateFormatterBehavior(operation, clientRuntime); 10: } 11: } 12: 13: public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) 14: { 15: foreach (var operation in endpoint.Contract.Operations) 16: { 17: DecorateFormatterBehavior(operation, endpointDispatcher.DispatchRuntime); 18: } 19: } 20: 21: public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } 22: 23: public void Validate(ServiceEndpoint endpoint) { } 24: 25: private static void DecorateFormatterBehavior(OperationDescription operation, object runtime) 26: { 27: //这个行为附加一次。 28: var dfBehavior = operation.Behaviors.Find<BinaryFormatterOperationBehavior>(); 29: if (dfBehavior == null) 30: { 31: //装饰新的操作行为 32: //这个行为是操作的行为,但是我们期望只为当前终结点做操作的序列化,所以传入 runtime 进行区分。 33: dfBehavior = new BinaryFormatterOperationBehavior(runtime); 34: operation.Behaviors.Add(dfBehavior); 35: } 36: } 37: } 38: }
添加行为扩展元素 EnableBinaryFormatterBehaviorElement
添加终结点行为扩展类,使得该类在配置文件可以使用。它指定了对应的运行时行为类型是 EnableBinaryFormatterBehavior
1: namespace OEA.WCF 2: { 3: /// <summary> 4: /// 启用旧的 BinaryFormatter 来对数据进行序列化。 5: /// </summary> 6: public class EnableBinaryFormatterBehaviorElement : BehaviorExtensionElement 7: { 8: public override Type BehaviorType 9: { 10: get { return typeof(EnableBinaryFormatterBehavior); } 11: } 12: 13: protected override object CreateBehavior() 14: { 15: return new EnableBinaryFormatterBehavior(); 16: } 17: } 18: }
使用方法
要使用这个扩展,只需要在客户端、服务端做相应的配置即可:
服务端配置
在 system.serviceModel 中添加扩展及行为配置:
1: <system.serviceModel> 2: <behaviors> 3: <endpointBehaviors> 4: <behavior name="enableRemotingBinarySerialization"> 5: <remotingBinarySerialization/> 6: </behavior> 7: </endpointBehaviors> 8: </behaviors> 9: <extensions> 10: <behaviorExtensions> 11: <add name="remotingBinarySerialization" type="OEA.WCF.EnableBinaryFormatterBehaviorElement, OEA"/> 12: </behaviorExtensions> 13: </extensions> 14: </system.serviceModel>
为服务终结点添加行为配置 behaviorConfiguration="enableRemotingBinarySerialization"。
1: <system.serviceModel> 2: <services> 3: <service name="OEA.Server.Hosts.WcfPortal" behaviorConfiguration="includesException"> 4: <endpoint address="/Binary" binding="customBinding" bindingConfiguration="compactBindingConfig" 5: behaviorConfiguration="enableRemotingBinarySerialization" 6: contract="OEA.Server.Hosts.IWcfPortal"/> 7: </service> 8: </services> 9: </system.serviceModel>
客户端
客户端同样添加相应的扩展及行为配置,并添加到服务终结点上即可。
效果
效果图:
以上是使用公司目前正在开发的系统的数据量进行测试的结果。可以看到,使用 WCF 直接二进制序列化时,32000 行数据序列化后大小是 28.34M(黄底),而启用这个扩展进行序列化后大小是 13.89M(浅绿底)。当同时使用 WCF 二进制序列化及 BinaryFormatter 序列化后,数据大小是10.42 M(绿底)。
同时使用多次序列化,虽然数据量会更小,但是序列化时间却增多。使用时,需要根据实际情况来调整。
转载自 胡庆访[ http://zgynhqf.cnblogs.com/ ]

