Binary Serialization behavior differs with Enterprise Library installed

Topics: Enterprise Library Core
Sep 15, 2008 at 10:52 PM
Hey guys, I've been discussing this issue with somebody over on the .Net Serialization forum, but I'd like to see if somebody on the EL team can give me some insight as to what is going on here. Basically, having EL 3.1 installed is fixing a binary deserialization problem I didn't know I had. Now that I am developing on a computer that doesn't have EL installed, the problem is manifesting itself. See the thread below:

http://social.msdn.microsoft.com/Forums/en-US/netfxremoting/thread/05f29ead-477c-4ee4-a1a1-93b8e1805c8e

What is EL doing to the behavior of assembly resolving??

-Ben
Sep 17, 2008 at 4:13 PM
Edited Sep 17, 2008 at 4:15 PM

Hi Ben,

The configuration editor for Visual Studio is causing this behavior by being overly eager to assist with assembly load failures.

Here's how it works: app domains have several load contexts, and when resolving assemblies the context in which the executing method's assembly resides determines how types will be resolved. When you load an assembly (say A) into the LoadFrom context (which happens when you use Assembly.LoadFrom), other assemblies that need to be resolved will use A's codebase as part of the probing path; however, assemblies in the Load context (like .NET's own assemblies) will be constrained to look for references in the AppDomain's probing path. Also, assemblies in the LoadFrom context can "find" assemblies already loaded in the LoadFrom and Load contexts, but assemblies in the Load context can only find assemblies already loaded in the Load context. This is a good thing because it makes apps more secure, but can also be the source of seemingly illogical behavior were a framework service invoked by one assembly cannot find the calling assembly. Most plug-in based architectures suffer from this, and need to deal with the situation by properly configuring assembly resolve events. And by "properly" I really mean help resolve only what's related to the code installing the handler and only while it's expected to be necessary; an approach that relies on using statements coupled with a disposable class is usually very helpful.

Fernando

Here's a sample showing this situation:

Assembly 1 - "plug in host"

namespace
ReproLoading

{

    class Program

    {

        static void Main(string[] args)

        {

            Assembly assembly = Assembly.LoadFrom(@"..\..\..\ClassLibrary1\bin\debug\ClassLibrary1.dll");

            Type type = assembly.GetType("ClassLibrary1.Class1");

            IFoo foo = (IFoo)Activator.CreateInstance(type);

            Console.WriteLine(foo.GetResult());

        }

    }

 

 

    public interface IFoo

    {

        int GetResult();

    }

}

Assembly 2 - "plug in"

namespace ClassLibrary1

{

    [Serializable]

    public class Class1 : IFoo

    {

        public int GetResult()

        {

            BinaryFormatter formatter = new BinaryFormatter();

 

            using (Stream memoryStream = new MemoryStream())

            {

                formatter.Serialize(memoryStream, this);

 

                memoryStream.Seek(0, SeekOrigin.Begin);

 

                object instance = formatter.Deserialize(memoryStream);  // throws, cannot deserialize itself

;            }

 

            return 0;

        }

    }

}

 

 

Sep 17, 2008 at 8:53 PM
Fernando, thank you for that very insightful answer. It was most helpful in understanding what was going on. A couple of questions back at you

"The configuration editor for Visual Studio is causing this behavior by being overly eager to assist with assembly load failures."

Why? That only leads to problems like mine showing up later. Is there any way to turn off its "eagerness"?

"Most plug-in based architectures suffer from this, and need to deal with the situation by properly configuring assembly resolve events. And by "properly" I really mean help resolve only what's related to the code installing the handler and only while it's expected to be necessary"

So, do you consider my handling of AssemblyResolve "proper", provided that I add the handler just before deserialization, and remove it right after deserialization?

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) 
       { 
          if (args.Name.StartsWith("MyAssembly")) 
          { 
             return this.GetType().Assembly; 
          } 
          return null
       }

For the record, I still think it is very, very strange that MyAssembly is looking for itself and can't find it during its own execution.

Thanks again for your great reply, as well as the link to the LoadContexts discussion.

-Ben

Sep 18, 2008 at 4:09 PM
Hi,

You can strengthen your test by comparing full assembly names. Also, this handler would only help you to resolve your assembly, not any other assembly that you may reference so you may need to improve it. 

About what the editor's handler gets in the way, I don't know the specifics but it's most likely related to the use of VisualStudio's property grid; when dealing with design time attributes, the property grid (which belongs to the Load context) may try to load custom attribute's assemblies and fail. In this case there is no well known entry point for setting up the assembly resolve handler like there is for serialization, and the assemblies that will need to be resolved are not known up front. However, I think it would be better for the handler to try to load the assembly from executing method's assembly instead of looking for the assemblies already loaded in the app domain; if the assembly is alreay loaded nothing would happen, and if it's not it would be resolved using the executing method's assembly's codebase. There's an additional complication in the case of EntLib's configuration editor though, because it is itself a plugin architecture: configuration sets allow for using the same editor to author files targeting different versions of EntLib.

Anyway, the technique used by EntLib's editor is not novel; the WPF editor for Visual Studio does something similar. You can peek at it with reflector from MS.Internal.Package.DesignerPackage.cctor() in Microsoft.VisualStudio.Xaml, Version=9.0.0.0.

Glad this helped,
Fernando