sebingel
sebingel

Reputation: 470

TargetInvocationException on BinaryFormatter.Deserialize after i added strong naming to my assembly

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:

  1. Why is it important with which Assemblyversion the file was saved?
  2. If i want to write another program that can access the data in these files... is that even possible?
  3. Can i convert the pre-strong-naming-files so that i can read them with the strong named assembly?

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

Answers (1)

Adriano Repetti
Adriano Repetti

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

Related Questions