BinaryFormatter deserialize дает исключение SerializationException

Я получаю:

System.Runtime.Serialization.SerializationException: невозможно найти assembly 'myNameSpace, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = нуль

При попытке десериализации некоторых данных в другой программе, чем программа, я сериализую ее с помощью.

После некоторых поисковых запросов я выяснил, что, по-видимому, это можно сделать только с помощью общей сборки.

Однако моя база данных заполнена этими сериализованными объектами, и мне нужна программа-утилита, чтобы вытащить их. Есть ли способ переопределить это поведение и просто передать ему тот же самый класс и заставить его десериализовать?


Я уже нашел этот фрагмент, но я не понимаю, как и где я должен его использовать/использовать.

   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;
    }

Ответ 1

Вам нужно будет каким-то образом указать ссылку на оригинальный тип, чтобы программа-утилита знала, как ее десериализовать.

Простым способом является просто добавить DLL, изначально изначально заданные в качестве ссылки на проект утилиты.

Код, который вы отправили, позволяет динамически загружать ту же самую DLL, когда десериализатор определяет, что он не может найти тип. Это более сложный подход (но это не так сложно), но в обоих случаях вам понадобится DLL, которая определяет типы... поэтому, вероятно, проще всего статически связать, добавив ссылку.

Если ваши типы в настоящее время не находятся в DLL (например, если они находятся в EXE), я предлагаю вам вывести классы из EXE в новую DLL и ссылаться на эту DLL как из исходного проекта, так и из утилиты проект.

Ответ 2

Вы можете обойти эту проблему, не требуя DLL, если знаете объект...

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

Используйте класс "System.Runtime.Serialization.SerializationBinder". От наследуя от этого класса, можно перенаправить все запросы для типов из двоичного форматирования для выбранных вами типов.

Вот пример, который позволит найти типы в текущей сборке независимо от того, какая версия сборки первоначально создала сериализованный поток:

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;
    }
}

Ответ 3

У меня возникла аналогичная проблема, и я получил ее работу с помощью следующей ссылки: BinaryFormatterDeserialize-not-finding-a-type

В основном вам нужно подписаться на событие AssemblyResolve ПЕРЕД десериализацией. Затем отпишитесь после десериализации.

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

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

Здесь метод, который я использовал для разрешения сборки:

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();
}

Ответ 4

Ответ JTtheGeek является правильным, что пользовательский SerializationBinder - это способ справиться с этой проблемой. Однако примерный код, приведенный в этом ответе, недостаточно для моего варианта использования. Мне нужно было что-то, что могло:

  • Обрабатывать несоответствующие номера версий и пространства имен.
  • Посмотрите на все сборки, а не только на текущую.
  • Быть достаточно быстрым, чтобы звонить 100 раз в секунду.

Вот что я придумал:

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;
        }
    }
}

Ответ 5

Если у вас нет доступа к исходной сборке, которая сериализовала данные, вы можете использовать SerializationBinder или SerializationSurrogate. Эти два интерфейса позволяют вам контролировать, как типы преобразуются между собой при десериализации.

Ответ 6

Я следил за решением, которое ответил JTtheGeek, и для того, чтобы заставить его работать для меня, мне пришлось добавить следующее непосредственно перед утверждением assemblyName = currentAssembly;:

typeName = "yourNamespace.yourClassName";

После этого он отлично поработает!