本文将定义一个 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(绿底)。
同时使用多次序列化,虽然数据量会更小,但是序列化时间却增多。使用时,需要根据实际情况来调整。