BinaryFormatter десериализации дает исключение SerializationException

Я получаю:

System.Runtime.Serialization.SerializationException: невозможно найти сборку 'myNameSpace, версия =1.0.0.0, культура = нейтральная, PublicKeyToken= ноль

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

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

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


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

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

6 ответов

Решение

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

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

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

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

Вы можете обойти эту проблему без необходимости 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;
    }
}

Я столкнулся с подобной проблемой, и я получил ее работать с помощью следующей ссылки: BinaryFormatterDeserialize-not-Find-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();
}

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

  1. Обработка несовпадающих номеров версий и пространств имен.
  2. Посмотрите на все сборки, а не только на текущую.
  3. Все равно будьте достаточно быстры, чтобы звонить 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;
        }
    }
}

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

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

typeName = "yourNamespace.yourClassName";

После этого все заработало!

Другие вопросы по тегам