AppDomain.CurrentDomain.AssemblyResolve asking for a <AppName>.resources assembly?

using the code How to embed a satellite assembly into the EXE file provided by csharptest.net, I've created a custom assembly resolver and embedded my assemblies in my resources.

I can successfully resolve my assemblies used in but somehow AppDomain.CurrentDomain.AssemblyResolve asks for an assembly called 'AppName.resources' specifically "MyProgram.resources, Version=0.15.3992.31638, Culture=en-US, PublicKeyToken=null" which i don't know how to resolve?

I've tried to disable loading my custom assemblies from resources (placed all my assembly dll's in program directory) and just enabled AppDomain.CurrentDomain.AssemblyResolve, but it was still asking for it.

I'm a bit confused about this, will appreciate a lot if you can help me on this.

Here's my code for interested ones;

static Assembly ResolveAssemblies(object sender, ResolveEventArgs args)
{
    Assembly assembly = null;
    string name = args.Name.Substring(0, args.Name.IndexOf(','));
    if (name == "MyProgram.resources") return null;
    else name = string.Format("MyProgram.Resources.Assemblies.{0}.dll", name);

    lock (_loadedAssemblies)
    {
        if (!_loadedAssemblies.TryGetValue(name, out assembly))
        {
            using (Stream io = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
            {
                if (io == null)
                {
                    MessageBox.Show("MyProgram can not load one of it's dependencies. Please re-install the program", string.Format("Missing Assembly: {0}", name), MessageBoxButtons.OK, MessageBoxIcon.Error);
                    Environment.Exit(-1);
                }
                using (BinaryReader binaryReader = new BinaryReader(io))
                {
                    assembly = Assembly.Load(binaryReader.ReadBytes((int)io.Length));
                    _loadedAssemblies.Add(name, assembly);
                }
            }
        }
    }

    return assembly;
}

Answering on my own;

Adding this line to AssemblyInfo.cs solves it and resolver will not get asked for resources any-more.

[assembly: NeutralResourcesLanguageAttribute("en-US", UltimateResourceFallbackLocation.MainAssembly)]

Though this is a work-around should be carefully considered multi-language applications.

More Info:

  • https://connect.microsoft.com/VisualStudio/feedback/details/526836/wpf-appdomain-assemblyresolve-being-called-when-it-shouldnt
  • http://blogs.msdn.com/b/kimhamil/archive/2008/11/11/what-does-the-neutralresourceslanguageattribute-do.aspx
  • http://forums.devshed.com/net-development-87/c-wpf-appdomain-assemblyresolve-being-called-when-it-shouldn-t-669567.html
  • http://blogs.msdn.com/b/microsoft_press/archive/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition.aspx

This approach fails for machines with non en-US cultures. A better approach is ignoring resources on assembly resolver;

public Assembly Resolver(object sender, ResolveEventArgs args)
        {
            lock (this)
            {
                Assembly assembly;
                AssemblyName askedAssembly = new AssemblyName(args.Name);

                string[] fields = args.Name.Split(',');
                string name = fields[0];
                string culture = fields[2];
                // failing to ignore queries for satellite resource assemblies or using [assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.MainAssembly)] 
                // in AssemblyInfo.cs will crash the program on non en-US based system cultures.
                if (name.EndsWith(".resources") && !culture.EndsWith("neutral")) return null;

                /* the actual assembly resolver */
                ...
            }
      }

My situation was a bit more complex and the above solution did not work for me. (That is changing the AssemblyInfo.cs file)

I have moved all my form and image resources to a seperate dll and the moment any of the images are used the 'filenotfoundexception' exception is thrown.

The important information is the following:
Beginning with the .NET Framework 4, the ResolveEventHandler event is raised for all assemblies, including resource assemblies. See the following reference

https://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve(v=vs.110).aspx

The solution turned out to be very simple. If a resource file is requested in the form 'dllname.resources.dll' always return null;

Here is the event code that I have adapted from other samples found. (I have commented the debugging lines - un-comment them if you have a problem using the code.

Add this line in your class. It is used to prevent loading a dll more than once

    readonly static Dictionary<string, Assembly> _libs = new Dictionary<string, Assembly>();

This is the event method.

private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly assembly = null;
            string keyName = new AssemblyName(args.Name).Name;
            if (keyName.Contains(".resources"))
            {
                return null;  // This line is what fixed the problem
            }
            if (_libs.ContainsKey(keyName))
            {
                assembly = _libs[keyName]; // If DLL is loaded then don't load it again just return
                return assembly;
            }

            string dllName = DllResourceName(keyName);
            //string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();   // Uncomment this line to debug the possible values for dllName
            using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(dllName))
            {
                if (stream == null)
                {
                    Debug.Print("Error! Unable to find '" + dllName + "'");
                    // Uncomment the next lines to show message the moment an assembly is not found. (This will also stop for .Net assemblies
                    //MessageBox.Show("Error! Unable to find '" + dllName + "'! Application will terminate.");
                    //Environment.Exit(0);
                    return null;
                }

                byte[] buffer = new BinaryReader(stream).ReadBytes((int) stream.Length);
                assembly = Assembly.Load(buffer);

                _libs[keyName] = assembly;
                return assembly;
            }
        }

        private static string DllResourceName(string ddlName)
        {
            if (ddlName.Contains(".dll") == false) ddlName += ".dll";

            foreach (string name in Assembly.GetExecutingAssembly().GetManifestResourceNames())
            {
                if (name.EndsWith(ddlName)) return name;
            }
            return ddlName;
        }