类型修改时强制二进制反序列化失败
Force binary deserialization to fail when type modified
我正在寻找一种非侵入式的方法来强制反序列化在以下情况下失败:
- 该类型未在强命名程序集中定义。
- 使用了 BinaryFormatter。
- 自序列化以来,类型已被修改(例如添加了 属性)。
下面是 illustration/repro 问题的一个失败的 NUnit 测试形式。我正在寻找一种通用的方法来通过而不修改 Data
class,最好是在序列化 and/or 反序列化期间设置 BinaryFormatter
。我也不想涉及序列化代理,因为这可能需要每个受影响类型的特定知识。
虽然在 MSDN 文档中找不到任何对我有帮助的内容。
[Serializable]
public class Data
{
public string S { get; set; }
}
public class DataSerializationTests
{
/// <summary>
/// This string contains a Base64 encoded serialized instance of the
/// original version of the Data class with no members:
/// [Serializable]
/// public class Data
/// { }
/// </summary>
private const string Base64EncodedEmptyDataVersion =
"AAEAAAD/////AQAAAAAAAAAMAgAAAEtTc2MuU3Rvcm0uRGF0YS5UZXN0cywgV"+
"mVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2"+
"VuPW51bGwFAQAAABlTc2MuU3Rvcm0uRGF0YS5UZXN0cy5EYXRhAAAAAAIAAAAL";
[Test]
public void Deserialize_FromOriginalEmptyVersionFails()
{
var binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream(Convert.FromBase64String(Base64EncodedEmptyDataVersion));
memoryStream.Seek(0L, SeekOrigin.Begin);
Assert.That(
() => binaryFormatter.Deserialize(memoryStream),
Throws.Exception
);
}
}
我在这里推荐一种 "Java" 方法——在每个可序列化的 class 中声明 int 字段,例如 private int _Serializable = 0;
并检查您的当前版本和序列化版本是否匹配;更改属性时手动增加。如果您坚持使用自动化方式,则必须存储大量元数据并检查当前元数据和持久元数据是否匹配(序列化数据 performance/size 的额外负担)。
这是自动描述符。基本上,您必须将 TypeDescriptor
实例存储为二进制数据的一部分,并在检索时检查持久化 TypeDescriptor
是否对当前 TypeDescriptor
的序列化 (IsValidForSerialization
) 有效。
var persistedDescriptor = ...;
var currentDescriptor = Describe(typeof(Foo));
bool isValid = persistedDescriptor.IsValidForSerialization(currentDescriptor);
[Serializable]
[DataContract]
public class TypeDescriptor
{
[DataMember]
public string TypeName { get; set; }
[DataMember]
public IList<FieldDescriptor> Fields { get; set; }
public TypeDescriptor()
{
Fields = new List<FieldDescriptor>();
}
public bool IsValidForSerialization(TypeDescriptor currentType)
{
if (!string.Equals(TypeName, currentType.TypeName, StringComparison.Ordinal))
{
return false;
}
foreach(var field in Fields)
{
var mirrorField = currentType.Fields.FirstOrDefault(f => string.Equals(f.FieldName, field.FieldName, StringComparison.Ordinal));
if (mirrorField == null)
{
return false;
}
if (!field.Type.IsValidForSerialization(mirrorField.Type))
{
return false;
}
}
return true;
}
}
[Serializable]
[DataContract]
public class FieldDescriptor
{
[DataMember]
public TypeDescriptor Type { get; set; }
[DataMember]
public string FieldName { get; set; }
}
private static TypeDescriptor Describe(Type type, IDictionary<Type, TypeDescriptor> knownTypes)
{
if (knownTypes.ContainsKey(type))
{
return knownTypes[type];
}
var descriptor = new TypeDescriptor { TypeName = type.FullName, Fields = new List<FieldDescriptor>() };
knownTypes.Add(type, descriptor);
if (!type.IsPrimitive && type != typeof(string))
{
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).OrderBy(f => f.Name))
{
var attributes = field.GetCustomAttributes(typeof(NonSerializedAttribute), false);
if (attributes != null && attributes.Length > 0)
{
continue;
}
descriptor.Fields.Add(new FieldDescriptor { FieldName = field.Name, Type = Describe(field.FieldType, knownTypes) });
}
}
return descriptor;
}
public static TypeDescriptor Describe(Type type)
{
return Describe(type, new Dictionary<Type, TypeDescriptor>());
}
我还考虑了一些缩短持久元数据大小的机制——比如从 xml-序列化或 json-序列化 TypeDescriptor 计算 MD5;但在这种情况下,new property/field 会将您的对象标记为不兼容序列化。
我正在寻找一种非侵入式的方法来强制反序列化在以下情况下失败:
- 该类型未在强命名程序集中定义。
- 使用了 BinaryFormatter。
- 自序列化以来,类型已被修改(例如添加了 属性)。
下面是 illustration/repro 问题的一个失败的 NUnit 测试形式。我正在寻找一种通用的方法来通过而不修改 Data
class,最好是在序列化 and/or 反序列化期间设置 BinaryFormatter
。我也不想涉及序列化代理,因为这可能需要每个受影响类型的特定知识。
虽然在 MSDN 文档中找不到任何对我有帮助的内容。
[Serializable]
public class Data
{
public string S { get; set; }
}
public class DataSerializationTests
{
/// <summary>
/// This string contains a Base64 encoded serialized instance of the
/// original version of the Data class with no members:
/// [Serializable]
/// public class Data
/// { }
/// </summary>
private const string Base64EncodedEmptyDataVersion =
"AAEAAAD/////AQAAAAAAAAAMAgAAAEtTc2MuU3Rvcm0uRGF0YS5UZXN0cywgV"+
"mVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2"+
"VuPW51bGwFAQAAABlTc2MuU3Rvcm0uRGF0YS5UZXN0cy5EYXRhAAAAAAIAAAAL";
[Test]
public void Deserialize_FromOriginalEmptyVersionFails()
{
var binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream(Convert.FromBase64String(Base64EncodedEmptyDataVersion));
memoryStream.Seek(0L, SeekOrigin.Begin);
Assert.That(
() => binaryFormatter.Deserialize(memoryStream),
Throws.Exception
);
}
}
我在这里推荐一种 "Java" 方法——在每个可序列化的 class 中声明 int 字段,例如 private int _Serializable = 0;
并检查您的当前版本和序列化版本是否匹配;更改属性时手动增加。如果您坚持使用自动化方式,则必须存储大量元数据并检查当前元数据和持久元数据是否匹配(序列化数据 performance/size 的额外负担)。
这是自动描述符。基本上,您必须将 TypeDescriptor
实例存储为二进制数据的一部分,并在检索时检查持久化 TypeDescriptor
是否对当前 TypeDescriptor
的序列化 (IsValidForSerialization
) 有效。
var persistedDescriptor = ...;
var currentDescriptor = Describe(typeof(Foo));
bool isValid = persistedDescriptor.IsValidForSerialization(currentDescriptor);
[Serializable]
[DataContract]
public class TypeDescriptor
{
[DataMember]
public string TypeName { get; set; }
[DataMember]
public IList<FieldDescriptor> Fields { get; set; }
public TypeDescriptor()
{
Fields = new List<FieldDescriptor>();
}
public bool IsValidForSerialization(TypeDescriptor currentType)
{
if (!string.Equals(TypeName, currentType.TypeName, StringComparison.Ordinal))
{
return false;
}
foreach(var field in Fields)
{
var mirrorField = currentType.Fields.FirstOrDefault(f => string.Equals(f.FieldName, field.FieldName, StringComparison.Ordinal));
if (mirrorField == null)
{
return false;
}
if (!field.Type.IsValidForSerialization(mirrorField.Type))
{
return false;
}
}
return true;
}
}
[Serializable]
[DataContract]
public class FieldDescriptor
{
[DataMember]
public TypeDescriptor Type { get; set; }
[DataMember]
public string FieldName { get; set; }
}
private static TypeDescriptor Describe(Type type, IDictionary<Type, TypeDescriptor> knownTypes)
{
if (knownTypes.ContainsKey(type))
{
return knownTypes[type];
}
var descriptor = new TypeDescriptor { TypeName = type.FullName, Fields = new List<FieldDescriptor>() };
knownTypes.Add(type, descriptor);
if (!type.IsPrimitive && type != typeof(string))
{
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).OrderBy(f => f.Name))
{
var attributes = field.GetCustomAttributes(typeof(NonSerializedAttribute), false);
if (attributes != null && attributes.Length > 0)
{
continue;
}
descriptor.Fields.Add(new FieldDescriptor { FieldName = field.Name, Type = Describe(field.FieldType, knownTypes) });
}
}
return descriptor;
}
public static TypeDescriptor Describe(Type type)
{
return Describe(type, new Dictionary<Type, TypeDescriptor>());
}
我还考虑了一些缩短持久元数据大小的机制——比如从 xml-序列化或 json-序列化 TypeDescriptor 计算 MD5;但在这种情况下,new property/field 会将您的对象标记为不兼容序列化。