BinaryFormatter.Deserialize "unable to find assembly" after ILMerge

26.9k views Asked by At

I have a C# solution with a referenced dll (also C# with the same .Net version). When I build the solution and run the resulting exe, without merging the exe and the referenced dll, everything works fine.

Now I want to merge these into one exe. I run ILMerge and everything appears to work ok. I try to execute the exe and it seems to run just fine until it tries to deserialize an object defined in the referenced dll.

using (Stream fstream = new FileStream(file_path, FileMode.Open))
{
    BinaryFormatter bf = new BinaryFormatter();
    return bf.Deserialize(fstream) as ControlledRuleCollection; 
    // throws unable to find assembly exception
}

Is there maybe some ILMerge option I'm missing here?

10

There are 10 answers

0
JaredPar On BEST ANSWER

It sounds like you've serialized an object inside a DLL, then merged all of the assemblies with ILMerge and are now trying to deserialize that object. This simply won't work. The deserialization process for binary serialization will attempt to load the object's type from the original DLL. This DLL doesn't exist post ILMerge and hence the deserialization will fail.

The serialization and deserialization process need to both operate pre or post merge. It can't be mixed

0
Peter Kelly On

You may have serialized that from the separate assembly and then tried to deserialize it with another assembly (or a newer version of the same assembly).

Some discussion here

0
Phil On

You can do this by creating and adding a SerializationBinder sub class that will change the assembly name before the deserialization happens.

sealed class PreMergeToMergedDeserializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        Type typeToDeserialize = null;

        // For each assemblyName/typeName that you want to deserialize to
        // a different type, set typeToDeserialize to the desired type.
        String exeAssembly = Assembly.GetExecutingAssembly().FullName;


        // The following line of code returns the type.
        typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
            typeName, exeAssembly));

        return typeToDeserialize;
    }
}

Then when deserializating add this to the BinaryFormatter:

BinaryFormatter bf = new BinaryFormatter();
bf.Binder = new PreMergeToMergedDeserializationBinder();
object obj = bf.Deserialize(ms);
0
sandeepmaurya On

i got the solution

   sealed class VersionDeserializationBinder : SerializationBinder
  {
     public override Type BindToType(string assemblyName, string typeName)
    {
    Type typeToDeserialize = null;
    string currentAssemblyInfo = Assembly.GetExecutingAssembly().FullName;

    //my modification
    string currentAssemblyName = currentAssemblyInfo.Split(',')[0];
    if (assemblyName.StartsWith(currentAssemblyName))assemblyName = currentAssemblyInfo;

    typeToDeserialize = Type.GetType(string.Format("{0}, {1}", typeName, assemblyName));
    return typeToDeserialize;
}

}

Deserialization problem: Error when deserializing from a different program version

0
user3697222 On
    public sealed class DeserializationBinder : SerializationBinder
{
    private readonly string _typeName;
    private readonly Assembly _assembly;
    public DeserializationBinder(Assembly assembly, string typeName)
    {
        _typeName = typeName;
        _assembly = assembly;
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        Type typeToDeserialize = null;
        if (!assemblyName.Contains("System") && !assemblyName.Contains("mscorlib"))
        {
            String currentAssembly = _assembly.FullName;
            assemblyName = currentAssembly;
            typeName = _typeName;
        }
        typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
            typeName, assemblyName));
        return typeToDeserialize;
    }
}
1
Scott Jasin On

For anyone having this issue, trying to deserialize from a different assembly, I found this solution which seems to great for me using a small "BindChanger" class with a shared namespace for the Object type in question. https://www.daniweb.com/programming/software-development/threads/339638/deserializing-in-a-different-assembly

0
ValiRossi On

I had a situation where serialized data was being stored in SQL server by an old .NET service that had been in place for years. I needed to get the data out of SQL and ran into this also. I was able to refer to the .exe and make it work until I used the solution mentioned above. However my assembly names were different.

sealed class Version1ToVersion2DeserializationBinder : SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            Type typeToDeserialize = null;

            // For each assemblyName/typeName that you want to deserialize to a different type, set typeToDeserialize to the desired type.
            String assemVer1 = assemblyName;
            String typeVer1 = typeName;

            if (assemblyName == assemVer1 && typeName == typeVer1)
            {
                // To use a type from a different assembly version, change the version number.
                assemblyName = Assembly.GetExecutingAssembly().FullName;
                // To use a different type from the same assembly, change the type name.
                typeName = "projectname.typename";
            }

            // The following line of code returns the type.
            typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
            return typeToDeserialize;
        }
    }
0
Suplanus On

SerializationBinder was also my solution. But I have the class in a DLL which is referenced. So i have to search in all load assemblies. I have modified the answers bevor with the parameter if the binder should search in dlls.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ibKastl.Helper
{
   public static class BinaryFormatterHelper
   {
      public static T Read<T>(string filename, Assembly currentAssembly)
      {
         T retunValue;
         FileStream fileStream = new FileStream(filename, FileMode.Open);

         try
         {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Binder = new SearchAssembliesBinder(currentAssembly,true);            
            retunValue = (T)binaryFormatter.Deserialize(fileStream);
         }
         finally
         {
            fileStream.Close();
         }

         return retunValue;
      }

      public static void Write<T>(T obj, string filename)
      {
         FileStream fileStream = new FileStream(filename, FileMode.Create);
         BinaryFormatter formatter = new BinaryFormatter();
         try
         {
            formatter.Serialize(fileStream, obj);
         }
         finally
         {
            fileStream.Close();
         }
      }
   }

   sealed class SearchAssembliesBinder : SerializationBinder
   {
      private readonly bool _searchInDlls;
      private readonly Assembly _currentAssembly;

      public SearchAssembliesBinder(Assembly currentAssembly, bool searchInDlls)
      {
         _currentAssembly = currentAssembly;
         _searchInDlls = searchInDlls;
      }

      public override Type BindToType(string assemblyName, string typeName)
      {
         List<AssemblyName> assemblyNames = new List<AssemblyName>();
         assemblyNames.Add(_currentAssembly.GetName()); // EXE

         if (_searchInDlls)
         {
            assemblyNames.AddRange(_currentAssembly.GetReferencedAssemblies()); // DLLs
         }

         foreach (AssemblyName an in assemblyNames)
         {
            var typeToDeserialize = GetTypeToDeserialize(typeName, an);
            if (typeToDeserialize != null)
            {
               return typeToDeserialize; // found
            }
         }

         return null; // not found
      }

      private static Type GetTypeToDeserialize(string typeName, AssemblyName an)
      {
         string fullTypeName = string.Format("{0}, {1}", typeName, an.FullName);
         var typeToDeserialize = Type.GetType(fullTypeName);
         return typeToDeserialize;
      }
   }

}

Usage:

const string FILENAME = @"MyObject.dat";

// Serialize
BinaryFormatterHelper.Write(myObject1,FILENAME);

// Deserialize
MyObject myObject2 = BinaryFormatterHelper.Read<MyObject>(FILENAME, Assembly.GetExecutingAssembly()); // Current Assembly where the dll is referenced
0
Lars Corneliussen On

In case you merge assemblies into a existing one (for-example all DLLs to the EXE) you can use the solution proposed in this answer:

// AssemblyInfo.cs for My.exe
[assembly: TypeForwardedTo(typeof(SomeTypeFromMergedDLL))]

This at least works for deserializing pre-merge. IL-Merge also still passes; even if you can't compile with a type-forward to a type of the same assembly...

I have not tried, if serializing works post-merge yet. But I'll keep my answer updated.

0
Johan Vermaak On

I found another solution for this problem. Mine problem was maybe a little different...

I was trying to deserialize from the same library that serialized, but it could not locate the correct assembly to do so. I tried all the solutions above, and none worked.

I found a solution on another website (Here)

In short, override the ResolveEventHandler of AppDomain.CurrentDomain.AssemblyResolve in the +tor

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

Then implement the method for "CurrentDomain_AssemblyResolve" as follows:

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
  System.Reflection.Assembly ayResult = null;
  string sShortAssemblyName = args.Name.Split(',')[0];
  System.Reflection.Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
  foreach (System.Reflection.Assembly ayAssembly in ayAssemblies)
  {
      if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0])
      {
          ayResult = ayAssembly;
          break;
      }
  }
  return ayResult;
}

This fixed the error for me which allowed me to deserialize.