支持加密和压缩的可扩展强类型(反)序列化器基础






3.40/5 (8投票s)
用 3 行代码即可将您的对象加密和/或压缩后(反)序列化
引言
序列化和反序列化一个对象,或一个连接对象的整个图,是一项常见的任务。序列化作为一种概念,深深地构建在 .NET Framework 中。具体的格式化器有不同的风格,专为不同的环境及其需求而设计。
这就是为什么 XmlSerializer
不实现与 BinaryFormatter
相同的接口,反之亦然。当人们开始探索不同的格式化器及其相关的属性和接口时,乐趣就开始了。我个人花费了相当多的时间来探索 System.Runtime.Serialization
和 System.Xml.Serialization
命名空间。提供的格式化器不幸地有其局限性。例如,XmlSerializer
不支持集合,BinaryFormatter
和 SoapFormatter
只序列化标记为可序列化的类。这使得学习和理解序列化变得更加困难,因为它取决于当前使用的格式化器。
背景
在一次又一次地编写不同风格的样板序列化代码,基本上做着相同的事情但只是使用不同的格式化器或类似的东西之后,我萌生了设计一个可扩展的序列化“框架”的想法。
必备组件
首先,让我介绍一下在以下代码示例和提供的演示控制台项目中使用的 DataStore
类。
没什么特别的,只是一个带有两个 string
属性的类,除了用于二进制和 soap 格式化器的 Serializable
属性以及 ISerializerCallback
接口实现。
[Serializable]
public class DataStore :
Raccoom.Runtime.Serialization.ComponentModel.ISerializerCallback
{
public string Name { get; set; }
public string Location { get; set; }
#region ISerializeableObject Members
void Raccoom.Runtime.Serialization.ComponentModel.ISerializerCallback.Serializing()
{
Console.WriteLine("Serializing");
}
void Raccoom.Runtime.Serialization.ComponentModel.ISerializerCallback.Serialized()
{
Console.WriteLine("Serialized");
}
void Raccoom.Runtime.Serialization.ComponentModel.ISerializerCallback.Deserialized()
{
Console.WriteLine("Deserialized");
}
#endregion
}
原始如寿司
这是将 DataStore
类实例刷新到磁盘所需的代码,作为 Rijndael 加密和 Deflate 压缩的 XML 内容。
只是大量我们可能会复制粘贴的样板代码,下次我们必须把它写到别处的时候。
System.Security.Cryptography.SymmetricAlgorithm symAlgo =
System.Security.Cryptography.Rijndael.Create();
//
System.Xml.Serialization.XmlSerializer xmlSerializer =
new System.Xml.Serialization.XmlSerializer(typeof(DataStore));
DataStore ds = new DataStore();
ds.Location = "HDD";
ds.Name = "IsolatedStorage";
//
using (Stream stream =
new System.IO.FileStream("test.xml", FileMode.Create, FileAccess.Write))
using (DeflateStream compressStream =
new DeflateStream(stream, CompressionMode.Compress))
using (CryptoStream cryptoStream = new CryptoStream(compressStream,
symAlgo.CreateEncryptor(),
System.Security.Cryptography.CryptoStreamMode.Write))
{
xmlSerializer.Serialize(cryptoStream, ds);
}
//
using (System.IO.Stream stream =
new System.IO.FileStream("test.xml", FileMode.Open, FileAccess.Read))
using (System.IO.Compression.DeflateStream compressStream =
new DeflateStream(stream, CompressionMode.Decompress))
using (CryptoStream cryptoStream = new CryptoStream(compressStream,
symAlgo.CreateDecryptor(),
System.Security.Cryptography.CryptoStreamMode.Read))
{
ds = xmlSerializer.Deserialize(cryptoStream) as DataStore;
}
上面的例子是将加密和压缩的 XML 写入文件 test.xml。到目前为止一切都很好,代码运行正常。但是,想象一下,如果你必须将格式从 XML 更改为二进制或 soap?此外,你不再需要压缩(1TB 硬盘正在向我们走来)。现在重要的是你必须应用多少代码更改,以及由此产生的更改将如何可预测和可靠地工作,对吧?
序列化器基础
最初,我创建了一个类来处理使用不同可用格式化器(此处在 The Code Project 上的自定义格式化器 [^])以统一方式进行序列化,以简化序列化,我意识到这个类应该涵盖更多相关主题。我以前编写过专门的序列化类来加密导出数据集或压缩导出图像。
幸运的是,加密和压缩功能在 .NET Framework 中被实现为 System.IO.Stream
继承类。序列化过程本身也使用流,所以只需链接这些流就可以得到一个支持压缩和/或加密的 serializer
类。
主要特点
- 将格式化器与序列化器解耦
- 客户端代码针对稳定的
ISerializer
接口进行编程,而格式化器可以更改 - 你可以实现回调接口
ISerializerCallback
来通知类实例关于序列化过程(用例:修复 BindingList<t> 反序列化 [^]) - 工厂使创建随时可用的序列化器实例变得容易
- 可扩展的设计方法,使用接口和工厂,开闭原则
- 支持 Deflate 和 GZip 压缩
- 加密功能支持
SymmetricAlgorithm
- 仅用三行代码即可完成整个往返(序列化/反序列化)
备注
尽管进行了统一处理,但此序列化器类为 .NET 世界提出的任何类型的格式化器都提供了服务,你的类类型仍然需要实现底层的格式化器特定的要求,如属性、接口和特殊构造函数,然后才能成功地进行序列化/反序列化。
架构

完整的技术参考可以在这里浏览 [^]。
构建块
构建块是嵌套在 Raccoom.Runtime.Serialization.ComponentModel
命名空间中的接口。
名称 | 描述 | 模式 |
ISerializer<TType> |
解耦并隐藏序列化器实现的复杂性。客户端代码使用的外观。支持创建给定 TType 的新实例,并将 TType 的实例(反)序列化到流或文件中。 |
外观 |
ISerializerCallback |
好莱坞式“别找我们,我们找你”,实现这个接口到你的类型中,以便在(反)序列化过程的特定阶段运行自定义代码。 |
策略 |
ISerializerFormatter |
将格式化器类与序列化器和客户端代码解耦。负责将给定的对象图(反)序列化到流中。 | 适配器 |
SerializerException |
包装在(反)序列化执行过程中发生的异常。允许以统一的方式处理不同类型的格式化器特定异常。 |
实现
嵌套在 Raccoom.Runtime.Serialization
和 Raccoom.Runtime.Serialization.Formatter
命名空间中的实现
名称 | 描述 | 模式 |
Serializer<TType, TSerializerFormatter> |
以强类型方式(TType )实现针对给定 ISerializerFormatter 实现进行(反)序列化类型的特性。 |
策略 |
SerializerFactory |
负责为任何给定的操作创建随时可用的 ISerializer 实例 |
Factory |
Formatter |
嵌套在 Raccoom.Runtime.Serialization.Formatter 命名空间中的 XmlFormatter 、BinaryFormatter 和 SoapFormatter 完成了完成工作所需的功能。 |
适配器 |
Using the Code
以下代码部分展示了如何使用序列化器。DataStore
类以不同的格式与压缩和加密结合进行序列化。除了工厂方法调用外,没有什么特别的变化,与上面的几行相比,你可以在几秒钟内获得一个缩略图(可维护性)。
安全第一...
System.Security.Cryptography.SymmetricAlgorithm symAlgo
= System.Security.Cryptography.Rijndael.Create();
创建一个用于 GZip 压缩 XML 的 Serializer
,创建一个 DataStore
实例并赋值,然后在序列化/反序列化之前设置一些值
ISerializer<DataStore> xmlGzip = SerializerFactory.CreateXmlSerializerGZip<DataStore>();
Console.WriteLine(xmlGzip);
//
DataStore dataStore = xmlGzip.CreateInstance();
dataStore.Name = "HDD";
dataStore.Location = "IsolatedStorage";
//
xmlGzip.Serialize("datastore.gip", dataStore, symAlgo);
dataStore = xmlGzip.Deserialize("datastore.gip", true, symAlgo);
创建一个用于 Deflate 压缩 XML 的 serializer
并进行序列化/反序列化
ISerializer<DataStore> xmlDeflate =
SerializerFactory.CreateXmlSerializerDeflate<DataStore>();
Console.WriteLine(xmlDeflate);
//
xmlDeflate.Serialize("datastore.def", dataStore, symAlgo);
dataStore = xmlDeflate.Deserialize("datastore.def", true, symAlgo);
创建一个不支持压缩或加密的二进制序列化器并进行序列化/反序列化
ISerializer<DataStore> binary = SerializerFactory.CreateBinarySerializer<DataStore>();
Console.WriteLine(binary);
//
binary.Serialize("datastore.bin", dataStore);
dataStore = binary.Deserialize("datastore.bin", true);
创建一个用于 GZip 压缩 soap 的序列化器并进行序列化/反序列化
ISerializer<DataStore> soapDeflate =
SerializerFactory.CreateSoapSerializerGZip<DataStore>();
Console.WriteLine(soapDeflate);
//
soapDeflate.Serialize("datastore.soap", dataStore, symAlgo);
dataStore = soapDeflate.Deserialize("datastore.soap", true, symAlgo);
是否仍然有预期的值?
System.Diagnostics.Debug.Assert(dataStore.Location == "IsolatedStorage");
System.DiSystem.Diagnostics.Debug.Assert(dataStore.Name == "HDD");
事实上,上面的代码比表面上看起来做的更多。
可预测、可靠、可维护
- 回调接口方法会独立于底层格式化器被调用,现在它们会通知
DataStore
实例关于序列化过程,允许类在反序列化后抑制事件或重新附加事件处理程序。 - 客户端代码独立于当前使用的
Formatter
接口 - 随时可用、灵活、可扩展且经过测试的基础架构
- 你不需要“复制粘贴”你的样板序列化代码(-> 错误、更改)
- 三行代码不太可能包含超过一个错误
可扩展性
作为一个概念证明,我在控制台演示程序集中实现了一个由 Patrick Boom [^] 编写的自定义格式化器。原始类签名如下所示
public sealed class XmlFormatter : IFormatter{}
该类必须实现 ISerializerFormatter
接口,并且必须提供一个 public
构造函数。编译器会检查一个 public
无参数构造函数(泛型约束),该构造函数必须存在,以便序列化器能够实例化格式化器。
public sealed class XmlFormatter :
IFormatter,Raccoom.Runtime.Serialization.ComponentModel.ISerializerFormatter
{
#region ISerializerFormatter Members
public XmlFormatter() { }
void ISerializerFormatter.Serialize(Stream stream, object instance)
{
this.Serialize(stream, instance);
}
object ISerializerFormatter.Deserialize(Stream stream, Type[] extraTypes)
{
return this.Deserialize(stream, extraTypes[0]);
}
#endregion
}
基本上,格式化器可以工作,但为了方便起见,我们应该提供一个工厂。
public sealed class XmlFormatterFactory
{
public static ISerializer<ttype> CreateXmlSerializer<ttype>()
where TType : new()
{
return new Serializer<ttype,>(CompressionAlgorithm.None);
}
public static ISerializer<ttype> CreateXmlSerializerDeflate<ttype>()
where TType : new()
{
return new Serializer<ttype,>(CompressionAlgorithm.Deflate);
}
public static ISerializer<ttype> CreateXmlSerializerZip<ttype>()
where TType : new()
{
return new Serializer<ttype,>(CompressionAlgorithm.GZip);
}
}
关注点
部署
该软件也可以通过 ClickOnce 设置安装。技术参考可以在 这里 浏览。
幕后
这里展示的整个项目是在 Visual Studio 2008 Beta 2 中完成的。就编码而言,它确实像 RTM 一样工作,从未崩溃,尽管它是一个功能完备的 Beta 版本。只有编写这篇文章的 HTML 编辑器有时会崩溃。