在我向程序集添加强命名后 BinaryFormatter.Deserialize 出现 TargetInvocationException
TargetInvocationException on BinaryFormatter.Deserialize after i added strong naming to my assembly
我用 Save()
和 Load()
方法编写了一个 DLL,使用 BinaryFormatter.Serialize()
和 BinaryFormatter.Deserialize()
将 List<MyObject>
保存或加载到我的文件中计算机。
在我决定为我的程序集添加强命名之前,这一直运行良好。一旦我在编译过程中添加了一个具有强名称的密钥,我的程序就不再能够加载预先强命名的文件。我得到一个 TargetInvocationException
和一个 FileLoadException
作为 InnerException
表示找不到 MyAssembly, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null
。
当我用十六进制编辑器打开文件时,我可以在文件中看到几个对我的程序集的引用:MyAssembly, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null
。
当我使用新的强命名程序集保存文件时,有一件事发生了变化:PublicKeyToken=123456789
。当然,123456789 是另一个值。
现在我想到了几个问题:
- 为什么保存文件的 Assembly 版本很重要?
- 如果我想编写另一个程序来访问这些文件中的数据...这可能吗?
- 我可以转换预强命名文件以便我可以使用强命名程序集读取它们吗?
我已经尝试将 PublicKeyToken=null
更改为 PublicKeyToken=123456789
,但这只是抛出一个 SerializationException
表示没有有效的 BinaryHeader
或对象版本已更改。
强命名程序集是 不同的 程序集(因为具有不同 public 键的程序集实际上是不同的程序集)。
鉴于此,您不能加载它们 而不是 预期的更合理,因为 BinaryFormatter
将默认尝试加载完全需要的程序集。
你可以做的是创建你的 SerializationBinder
,你用当前执行程序集的 PublicKey
替换 null PublicKey
。您的自定义序列化活页夹可用于分配 BinaryFormatter.Binder
属性.
var formatter = new BinaryFormatter { Binder = new MyCustomBinder() };
其中 MyCustomBinder
是(只是相关部分):
sealed class MyCustomBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
var name = new AssemblyName(assemblyName);
if (name.GetPublicKeyToken() == null) // Better check here...
{
var publicKeyToken = Assembly.GetExecutingAssembly()
.GetName().GetPublicKeyToken();
name.SetPublicKeyToken(publicKeyToken);
}
// Now let's create required type using name and typeName
}
// Other code
}
当然你应该只为你自己的程序集做这件事(而不是你不知道的任何其他类型)并且你应该仔细检查这不会破坏你的应用程序安全性。
编辑:请注意某些类型使用序列化代理项(例如 MemberInfo
到 MemberInfoSerializationHolder
)。由于此代理项而加载的类型将不会通过您的自定义序列化活页夹。值得注意的案例是代表。是的,序列化委托并不是什么好主意,我倾向于避免它(另见 .NET 4.5 MethodInfo serialization breaking change)但如果你不得不忍受它,你还需要处理 AppDomain.CurrentDomain.AssemblyResolve
事件(具有相同的逻辑如上所述):在反序列化之前附加一个处理程序,并在完成后将其删除;理想情况下,您可以在序列化活页夹中执行所有操作:
sealed class MyCustomBinder : SerializationBinder, IDisposable
{
public MyCustomBinder()
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}
void IDisposable.Dispose()
{
AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
}
// Your code here
}
像这样使用:
using (var binder = new MyCustomBinder())
{
var formatter = new BinaryFormatter { Binder = new MyCustomBinder() };
}
注意:我没试过,但如果你需要使用 AppDomain.AssemblyResolve
那么你 可能 甚至不需要自定义活页夹,一切都可以在那里完成...
我用 Save()
和 Load()
方法编写了一个 DLL,使用 BinaryFormatter.Serialize()
和 BinaryFormatter.Deserialize()
将 List<MyObject>
保存或加载到我的文件中计算机。
在我决定为我的程序集添加强命名之前,这一直运行良好。一旦我在编译过程中添加了一个具有强名称的密钥,我的程序就不再能够加载预先强命名的文件。我得到一个 TargetInvocationException
和一个 FileLoadException
作为 InnerException
表示找不到 MyAssembly, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null
。
当我用十六进制编辑器打开文件时,我可以在文件中看到几个对我的程序集的引用:MyAssembly, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null
。
当我使用新的强命名程序集保存文件时,有一件事发生了变化:PublicKeyToken=123456789
。当然,123456789 是另一个值。
现在我想到了几个问题:
- 为什么保存文件的 Assembly 版本很重要?
- 如果我想编写另一个程序来访问这些文件中的数据...这可能吗?
- 我可以转换预强命名文件以便我可以使用强命名程序集读取它们吗?
我已经尝试将 PublicKeyToken=null
更改为 PublicKeyToken=123456789
,但这只是抛出一个 SerializationException
表示没有有效的 BinaryHeader
或对象版本已更改。
强命名程序集是 不同的 程序集(因为具有不同 public 键的程序集实际上是不同的程序集)。
鉴于此,您不能加载它们 而不是 预期的更合理,因为 BinaryFormatter
将默认尝试加载完全需要的程序集。
你可以做的是创建你的 SerializationBinder
,你用当前执行程序集的 PublicKey
替换 null PublicKey
。您的自定义序列化活页夹可用于分配 BinaryFormatter.Binder
属性.
var formatter = new BinaryFormatter { Binder = new MyCustomBinder() };
其中 MyCustomBinder
是(只是相关部分):
sealed class MyCustomBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
var name = new AssemblyName(assemblyName);
if (name.GetPublicKeyToken() == null) // Better check here...
{
var publicKeyToken = Assembly.GetExecutingAssembly()
.GetName().GetPublicKeyToken();
name.SetPublicKeyToken(publicKeyToken);
}
// Now let's create required type using name and typeName
}
// Other code
}
当然你应该只为你自己的程序集做这件事(而不是你不知道的任何其他类型)并且你应该仔细检查这不会破坏你的应用程序安全性。
编辑:请注意某些类型使用序列化代理项(例如 MemberInfo
到 MemberInfoSerializationHolder
)。由于此代理项而加载的类型将不会通过您的自定义序列化活页夹。值得注意的案例是代表。是的,序列化委托并不是什么好主意,我倾向于避免它(另见 .NET 4.5 MethodInfo serialization breaking change)但如果你不得不忍受它,你还需要处理 AppDomain.CurrentDomain.AssemblyResolve
事件(具有相同的逻辑如上所述):在反序列化之前附加一个处理程序,并在完成后将其删除;理想情况下,您可以在序列化活页夹中执行所有操作:
sealed class MyCustomBinder : SerializationBinder, IDisposable
{
public MyCustomBinder()
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}
void IDisposable.Dispose()
{
AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
}
// Your code here
}
像这样使用:
using (var binder = new MyCustomBinder())
{
var formatter = new BinaryFormatter { Binder = new MyCustomBinder() };
}
注意:我没试过,但如果你需要使用 AppDomain.AssemblyResolve
那么你 可能 甚至不需要自定义活页夹,一切都可以在那里完成...