Reputation: 470
I wrotea DLL with a Save()
and a Load()
method using BinaryFormatter.Serialize()
and BinaryFormatter.Deserialize()
to save or load a List<MyObject>
to a file on my computer.
This has worked fine until i decided to add strong naming to my assembly. As soon as i add a key with a strong name to the compile process my program is no longer able to load a pre-strong-naming file. I get a TargetInvocationException
with a FileLoadException
as the InnerException
saying that MyAssembly, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null
is not found.
When i open the file with a HEX editor i can see several references to my assembly in the the file: MyAssembly, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null
.
When i save a file with my new strong named assembly one thing changes: PublicKeyToken=123456789
. Where, of course, 123456789 is another value.
Now several questions came to my mind:
I already tried changing PublicKeyToken=null
into PublicKeyToken=123456789
but that just throws a SerializationException
saying that there is no valid BinaryHeader
or that the object version has changed.
Upvotes: 1
Views: 321
Reputation: 67128
Strong named assemblies are different assemblies (as assemblies with a different public key are effectively different assemblies).
Given this they it's more reasonable you can't load them instead of expected ones because BinaryFormatter
will try by default to load exactly required assembly.
What you can do is to create your SerializationBinder
where you replace null PublicKey
with PublicKey
from current executing assembly. Your custom serialization binder can be used assigning BinaryFormatter.Binder
property.
var formatter = new BinaryFormatter { Binder = new MyCustomBinder() };
Where MyCustomBinder
is (just relevant parts):
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
}
Of course you should do it only for your own assemblies (not any other type you don't know about) and you should carefully check this won't break your application security.
Edit: be aware that some types use a serialization surrogate (for example MemberInfo
through MemberInfoSerializationHolder
). Types loaded because of this surrogate will not go through your custom serialization binder. Notable case is for delegates. Yes, it's not such great idea to serialize delegates and I'd tend to avoid it (see also .NET 4.5 MethodInfo serialization breaking change) but if you have to live with it you also need to handle AppDomain.CurrentDomain.AssemblyResolve
event (with same logic as above): attach an handler before deserialization and remove it when you're done; ideally you may do everything inside your serialization binder:
sealed class MyCustomBinder : SerializationBinder, IDisposable
{
public MyCustomBinder()
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}
void IDisposable.Dispose()
{
AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
}
// Your code here
}
Used like:
using (var binder = new MyCustomBinder())
{
var formatter = new BinaryFormatter { Binder = new MyCustomBinder() };
}
Note: I didn't try but if you need to use AppDomain.AssemblyResolve
then you maybe don't even need a custom binder and everything can be done there...
Upvotes: 2