BinaryFormatter десериализации дает исключение SerializationException - PullRequest
13 голосов
/ 22 января 2010

Я получаю:

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 ]

13 голосов
/ 03 декабря 2010

Вы можете обойти эту проблему, не нуждаясь в 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;
    }
}
6 голосов
/ 22 января 2010

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

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

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

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

4 голосов
/ 20 августа 2014

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;
        }
    }
}
4 голосов
/ 02 декабря 2011

Я столкнулся с подобной проблемой, и я получил ее работать по следующей ссылке: BinaryFormatterDeserialize-не-вывод-а-типа

По сути, вам нужно подписаться на событие 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();
}
1 голос
/ 05 ноября 2011

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

0 голосов
/ 27 ноября 2013

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

typeName = "yourNamespace.yourClassName";

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

...