BinaryFormatter deserialize gives SerializationException
Asked Answered
F

6

13

I'm getting an:

System.Runtime.Serialization.SerializationException: Unable to find assembly 'myNameSpace, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

When trying to deserialize some data in another program than the program I serialized it with.

After some googling I've found out that apparently this can only be done using a shared assembly.

However, my database is full with this serialized objects, and I need a utility program to get them out. Is there a way to override this behavior and just feed it the exact same class and force it do deserialize?


I already found this snippet, but I don't understand how and where I should put/use this.

   static constructor() {
        AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
   }

    static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
        Assembly ayResult = null;
        string sShortAssemblyName = args.Name.Split(',')[0];
         Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
         foreach (Assembly ayAssembly in ayAssemblies) {
            if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0]) {
                 ayResult = ayAssembly;
                 break;
            }
         }
         return ayResult;
    }
Faker answered 22/1, 2010 at 19:53 Comment(3)
Referencing the original assembly in your utility app isn't an option?Hennessey
@eric: some are just not answered yet...so I can not accept them. But I'll review them again to see if new answers/comments have been addedFaker
any final solutionwith full source code sample?Mil
A
6

You will need to provide a reference to the original type somehow so that the utility program knows how to deserialize it.

The easy way is just to add the DLL the types were originally defined in as a reference to the utility project.

The code you posted allows you to dynamically load that same DLL when the deserializer determines it can't find the type. This is a more difficult approach (but not that difficult), but in both cases you will need a DLL that defines the types... so probably easiest just to statically link by adding the reference.

If your types are not currently in a DLL (e.g. if they are in an EXE), I suggest you pull the classes out of the EXE into a new DLL, and reference that DLL both from the original project and from the util project.

Appellant answered 22/1, 2010 at 20:2 Comment(1)
Great! I've actually just referenced the entire .exe just to see if it worked, and behold I could reference the namespace and the classes. Thanks for the tip!Faker
I
13

You can get around this issue without needing the DLL if you know the object...

http://spazzarama.com/2009/06/25/binary-deserialize-unable-to-find-assembly/

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.serializationbinder(VS.71).aspx

Use the “System.Runtime.Serialization.SerializationBinder” class. By inheriting from this class it is possible to redirect all the requests for types from the binary formatter to the types of your choice.

Here is a sample that will allow the types to be found in the current assembly regardless of which version of the assembly originally created the serialized stream:

sealed class AllowAllAssemblyVersionsDeserializationBinder : System.Runtime.Serialization.SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {     
        String currentAssembly = Assembly.GetExecutingAssembly().FullName;

        // In this case we are always using the current assembly
        assemblyName = currentAssembly;

        // Get the type using the typeName and assemblyName
        Type typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
            typeName, assemblyName));

        return typeToDeserialize;
    }
}

public static MyRequestObject Deserialize(byte[] b)
{
    MyRequestObject mro = null;
    var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
    using (var ms = new System.IO.MemoryStream(b))
    {
       // To prevent errors serializing between version number differences (e.g. Version 1 serializes, and Version 2 deserializes)
       formatter.Binder = new AllowAllAssemblyVersionsDeserializationBinder();

       // Allow the exceptions to bubble up
       // System.ArgumentNullException
       // System.Runtime.Serialization.SerializationException
       // System.Security.SecurityException
       mro = (MyRequestObject)formatter.Deserialize(ms);
       ms.Close();
       return mro;
    }
}
Indefinable answered 2/12, 2010 at 22:56 Comment(3)
Thanks! I started with this, but had to heavily modify the code for my use case. See my answer here: https://mcmap.net/q/854124/-binaryformatter-deserialize-gives-serializationexceptionGenethlialogy
Why the ms.Close()? Doesn't the using statement take care of that?Slicker
yeah... that's definitely redundant, guessing copied over from a non using statement and never removedIndefinable
A
6

You will need to provide a reference to the original type somehow so that the utility program knows how to deserialize it.

The easy way is just to add the DLL the types were originally defined in as a reference to the utility project.

The code you posted allows you to dynamically load that same DLL when the deserializer determines it can't find the type. This is a more difficult approach (but not that difficult), but in both cases you will need a DLL that defines the types... so probably easiest just to statically link by adding the reference.

If your types are not currently in a DLL (e.g. if they are in an EXE), I suggest you pull the classes out of the EXE into a new DLL, and reference that DLL both from the original project and from the util project.

Appellant answered 22/1, 2010 at 20:2 Comment(1)
Great! I've actually just referenced the entire .exe just to see if it worked, and behold I could reference the namespace and the classes. Thanks for the tip!Faker
A
4

I ran into a similar problem and I got it working with help of the following link: BinaryFormatterDeserialize-not-finding-a-type

Basically what you need to do is subscribe to the AssemblyResolve event BEFORE deserializing. Then unsubscribe after deserialization..

AppDomain.CurrentDomain.AssemblyResolve +=
                new ResolveEventHandler(CurrentDomain_AssemblyResolve);
// CODE TO DESERIALIZE HERE

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

Here the method I used to resolve the Assembly:

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    try
    {
        if(args.Name == "MY ASSEMBLY NAME"))
        {
            //Load my Assembly 
            Assembly assem = Assembly.LoadFrom("MY ASSEMBLY PATH");
            if(assem != null)
                return assem;
        }
    }
    catch { ;}

    return Assembly.GetExecutingAssembly();
}
Avrilavrit answered 2/12, 2011 at 15:44 Comment(1)
this technique worked for me when I didn't have access to the specific version of the assembly for deserialization but I knew the current version was compatible with it.Mailer
G
4

JTtheGeek's answer is correct that a custom SerializationBinder is the way to handle this problem. The example code given in that answer isn't sufficient for my use case, though. I needed something that could:

  1. Handle mismatched version numbers and namespaces.
  2. Look in all assemblies, not just the current one.
  3. Still be fast enough to call 100 times per second.

This is what I came up with:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Company.Product.Common.Serialize
{
    /// <summary>
    /// A Serialization Binder that allows inexact matches (version number or namespace).
    /// </summary>
    public sealed class AllowInexactMatchSerializationBinder : System.Runtime.Serialization.SerializationBinder
    {
        static private Dictionary<string, Type> typeBindings = new Dictionary<string, Type>();

        /// <summary>
        /// When overridden in a derived class, controls the binding of a serialized object to a type.
        /// </summary>
        /// <param name="assemblyName">Specifies the <see cref="T:System.Reflection.Assembly" /> name of the serialized object.</param>
        /// <param name="typeName">Specifies the <see cref="T:System.Type" /> name of the serialized object.</param>
        /// <returns>
        /// The type of the object the formatter creates a new instance of.
        /// </returns>
        public override Type BindToType(string assemblyName, string typeName)
        {
            Type type;
            var assemblyQualifiedTypeName = String.Format("{0}, {1}", typeName, assemblyName);

            // use cached result if it exists
            if (typeBindings.TryGetValue(assemblyQualifiedTypeName, out type))
            {
                return type;
            }

            // try the fully qualified name
            try { type = Type.GetType(assemblyQualifiedTypeName); }
            catch { type = null; }

            if (type == null)
            {
                // allow any assembly version
                var assemblyNameWithoutVersion = assemblyName.Remove(assemblyName.IndexOf(','));
                var assemblyQualifiedTypeNameWithoutVersion = String.Format("{0}, {1}", typeName, assemblyNameWithoutVersion);
                try { type = Type.GetType(assemblyQualifiedTypeNameWithoutVersion); }
                catch { type = null; }
            }

            if (type == null)
            {
                // check all assemblies for type full name
                try
                {
                    type = AppDomain.CurrentDomain.GetAssemblies()
                        .SelectMany(a => a.ExportedTypes)
                        .Where(a => a.FullName == typeName)
                        .FirstOrDefault();
                }
                catch { type = null; }
            }

            if (type == null)
            {
                // check all assemblies for type name
                var name = typeName.Split('.').Last();
                try
                {
                    type = AppDomain.CurrentDomain.GetAssemblies()
                        .SelectMany(a => a.ExportedTypes)
                        .Where(a => a.Name == name)
                        .FirstOrDefault();
                }
                catch { type = null; }
            }

            typeBindings[assemblyQualifiedTypeName] = type;
            return type;
        }
    }
}
Genethlialogy answered 20/8, 2014 at 19:1 Comment(0)
P
1

If you don't have access to the original assembly that serialized the data, then you can use a SerializationBinder or a SerializationSurrogate. These two interfaces allow you to control how types are converted between one another when deserializing.

Preconceive answered 4/11, 2011 at 20:21 Comment(0)
T
0

I followed the solution answered by JTtheGeek and in order to get it work for me I had to add the following just before the statement assemblyName = currentAssembly;:

typeName = "yourNamespace.yourClassName";

After that it worked great!

Telemark answered 27/11, 2013 at 11:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.