Search code examples
c#xml-serializationxmlserializerassembly-resolution

Error deserializing object having a field of type declared in the other assembly loaded on AssemblyResolve


I have an application which embedes (via BuildAction: Embedded Resource) referenced assembly (called ClassLibrary1) inside itself and loads it on AppDomain.CurrentDomain.AssemblyResolve event. Main assembly defines a class Class1:

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

It has a property of type Class2 defined in ClassLibrary1. Definition of Class2:

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

In the main method I`m creating a new XmlSerializer(typeof(Class1)):

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

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

While executing a programm I get the following error:

Unable to generate a temporary class (result=1). error CS0012: The type 'ClassLibrary1.Class2' is defined in an assembly that is not referenced. You must add a reference to assembly 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c06f123f2868e8c8'. error CS0266: Cannot implicitly convert type 'object' to 'ClassLibrary1.Class2'. An explicit conversion exists (are you missing a cast?)

Any ideas?

The rest of the code:

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

UPDATE: Created a BUG on the microsoft Connect site. You can also download a sample visual stuido 2010 solution (just expand Details fieldgroup) from there to reproduce it.


Solution

  • I've solved similar problem by saving assembly in temporary folder

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