Ошибка десериализации объекта, имеющего поле типа, объявленное в другой сборке, загруженной в AssemblyResolve

#c# #xml-serialization #xmlserializer #assembly-resolution

#c# #xml-сериализация #xmlserializer #сборка-разрешение

Вопрос:

У меня есть приложение, которое внедряет (через BuildAction: Embedded Resource) сборку, на которую ссылается (называемую ClassLibrary1), внутрь себя и загружает ее в AppDomain.CurrentDomain.Событие AssemblyResolve. Основная сборка определяет класс Class1:

 public class Class1
{        
    public Class2 MyField { get; set; }    
}
  

Оно имеет свойство типа Class2, определенное в ClassLibrary1.
Определение Class2:

 public class Class2
{
    public int A { get; set; }
}
  

В основном методе я создаю новый XmlSerializer(typeof(Class1)):

     static void Main()
    {
        SubscribeAssemblyResolver();
        MainMethod();
    }

    private static void MainMethod()
    {
        XmlSerializer xs2 = new XmlSerializer(typeof(Class1));
        Class1 cl = new Class1();
    }
  

При выполнении программы я получаю следующую ошибку:

Не удается сгенерировать временный класс (результат =1). ошибка CS0012: тип ‘ClassLibrary1.Class2’ определен в сборке, на которую нет ссылки. Необходимо добавить ссылку на сборку ‘ClassLibrary1, Version=1.0.0.0, Culture = neutral, PublicKeyToken=c06f123f2868e8c8’. ошибка CS0266: не удается неявно преобразовать тип ‘object’ в ‘ClassLibrary1.Class2’. Существует явное преобразование (вы пропускаете приведение?)

Есть идеи?

Остальная часть кода:

     private static void SubscribeAssemblyResolver()
    {
        AppDomain.CurrentDomain.AssemblyResolve  = new ResolveEventHandler(CurrentDomain_AssemblyResolve);            
    }

    static Dictionary<String, Assembly> _assemblies = new Dictionary<String, Assembly>(StringComparer.OrdinalIgnoreCase);

    static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        return ResolveAssembly(args.Name);
    }

    private static Assembly ResolveAssembly(string argsName)
    {
        Assembly dll;
        var name = "WindowsFormsApplication1.Libs."   new AssemblyName(argsName).Name   ".dll";
        if (!_assemblies.TryGetValue(name, out dll))
        {
            Assembly res = typeof(Program).Assembly;
            using (var input = res.GetManifestResourceStream(name))
            {
                if (input == null)
                {
                    //TODO: log
                    return null;
                }
                Byte[] assemblyData = new Byte[input.Length];
                input.Read(assemblyData, 0, assemblyData.Length);
                if (null == (dll = Assembly.Load(assemblyData)))
                {
                    //TODO: log
                    return null;
                }
                //TODO: log
                _assemblies[name] = dll;
                return dll;
            }
        }
        return dll;
    }
  

ОБНОВЛЕНИЕ: Создана ОШИБКА на сайте Microsoft Connect. Вы также можете загрузить образец решения visual stuido 2010 (просто разверните Details fieldgroup) оттуда, чтобы воспроизвести его.

Комментарии:

1. Эта ошибка не ограничивается динамически загружаемыми сборками. Смотрите Ошибку создания XML-сериализатора в конкретной ситуации в VB . К сожалению, ошибки XmlSerializer не исправляются, если они не критичны для безопасности.

2. Спасибо за ссылку, Джон! К сожалению, указанное решение в обходных путях для упомянутой ошибки в моем случае не работает: XmlSerializer xs2 = new XmlSerializer(typeof(Class1), new Type[] { typeof(Class2) }); Я все еще получаю те же ошибки.

3. хорошо, возможно, причина, по которой обходной путь не работает для вас, заключается в вашей динамической загрузке. В качестве эксперимента, можете ли вы попробовать это со статической загрузкой и 1) посмотреть, по-прежнему ли вы получаете ошибку, и 2) посмотреть, работает ли обходной путь для вас со статической загрузкой. Если проблема заключается в динамической загрузке, тогда я предлагаю вам ввести новую статью Connect и опубликовать URL здесь.

4. @John: проблем нет, если он загружен статически (например ClassLibrary1.dll находится в той же папке, что и ConsoleApplication1, и загружается с помощью стандартного механизма .net) Я создал новую ошибку подключения и добавил решение vs2010, ссылка на него есть в сообщении.

Ответ №1:

Я решил аналогичную проблему, сохранив сборку во временной папке

     public static byte[] ReadFully(Stream input)
    {
        var buffer = new byte[16 * 1024];
        using (var ms = new MemoryStream())
        {
            int read;
            while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                ms.Write(buffer, 0, read);
            }
            return ms.ToArray();
        }
    }

    public App()
    {
        AppDomain.CurrentDomain.AssemblyResolve  = (sender, args) =>
        {
            var assemblyName = new AssemblyName(args.Name);

            if (assemblyName.Name != "Omikad.Core")
                return null;

            var resourceName = "Terem."   assemblyName.Name   ".dll";

            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            {
                if (stream == null)
                    return null;

                var assemblyData = ReadFully(stream);
                var tmp = Path.Combine(Path.GetTempPath(), "Omikad.Core.dll");
                File.WriteAllBytes(tmp, assemblyData);
                return Assembly.LoadFrom(tmp);
            }
        };
    }
  

Ответ №2:

Попробуйте добавить атрибут:

 [XmlInclude(typeof(Class2))]
public class Class1
{        
   public Class2 MyField { get; set; }    
}
  

Комментарии:

1. Я уже пробовал это, и это не работает, но спасибо за ответ.

Ответ №3:

На данный момент я получил два несколько неудачных решения:

  1. Хотя вы не можете создать экземпляр XmlSerializer для типа Class1, вы все еще можете создать его экземпляр для типа Class2 из основной сборки. Это означает, что если вы переместите Class1 в ClassLibrary1 или Class2 в основную сборку — она десериализуется без ошибок. Это работает, но использовать это решение повсеместно невозможно, плюс это идеологически неверно.
  2. Используйте ILMerge, чтобы объединить эти сборки в одну. Но это работает только для материалов, отличных от wpf, плюс вы должны управлять ситуацией с атрибутами сборок (могут быть конфликты).

И одна очень плохая идея:

  1. Сгенерировать ClassLibrary1.XmlSerializer.dll с sgen.exe.
  2. Также внедрите его в основную сборку.
  3. Явно загрузите его в кэш XmlSerializer, вызывая один из его внутренних методов через отражение.

Хотя мне пришлось использовать решение номер один на данный момент, я им не удовлетворен, потому что оно слишком стесняет.

Ответ №4:

Я бы попробовал конструктор XmlSerializer(Type, Тип[]) и предоставил Class2 в качестве дополнительного типа, используя второй параметр. У меня мало опыта работы с XmlSerializer, но для DataContractSerializer это делает свое дело.