9

I am developing an application which references and uses some third party assemblies from a certain Vendor; in development box I have these 3 assemblies in a reference folder in my source tree and I can reference them and build the application, application builds but does not run because the whole server application is not installed, but this is fine.

On the server where I want to copy this custom application and run all assemblies I am referencing are in folder something like:

D:\ProgramFiles\VendorName\ProductName\Support\API\Bin64

and if I copy my small executable in that folder and run it, it works perfectly, but if I put my .exe in a more appropriate folder like I want:

D:\ProgramFiles\MyCompanyName\MyProduct\bin\...

it does not work because it cannot resolve those assemblies.

I know I can use probing in app.config to specify in which folders my exe has to look for references but imy case the assemblies are not in a subfolder, more in a completely different location.

I don't want to copy all vendor assemblies in my app folder and I cannot put there only the 3 ones I am referencing because they are also loading other assemblies and unless I have all of them (many...), it does not work.

I am not doing anything special, not creating app domains and not loading assemblies via reflection, just want the CLR to resolve the references as they are needed on application start or execution.

Thanks.

Edit: here the final working code

static System.Reflection.Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    Logger logger = new Logger();

    try
    {
        string RMSAssemblyFolder = ConfigurationManager.AppSettings["RMSAssemblyFolder"];

        Assembly MyAssembly = null;
        string strTempAssmbPath = string.Empty;

        Assembly objExecutingAssemblies = Assembly.GetExecutingAssembly();
        AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();

        AssemblyName myAssemblyName = Array.Find<AssemblyName>(arrReferencedAssmbNames, a => a.Name == args.Name);

        if (myAssemblyName != null)
        {
            MyAssembly = Assembly.LoadFrom(myAssemblyName.CodeBase);
        }
        else
        {
            strTempAssmbPath = Path.Combine(RMSAssemblyFolder, args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll");

            if (!string.IsNullOrEmpty(strTempAssmbPath))
            {
                if (File.Exists(strTempAssmbPath))
                {
                    logger.Information("Assembly to load: {0} - File was found in: {1}", args.Name, strTempAssmbPath);

                    // Loads the assembly from the specified path.                  
                    MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
                }
            }
        }

        // Returns the loaded assembly.
        return MyAssembly;
    }
    catch (Exception exc)
    {
        logger.Error(exc);
        return null;
    }
}
Davide Piras
  • 43,984
  • 10
  • 98
  • 147
  • possible duplicate of http://stackoverflow.com/questions/1373100/how-to-add-folder-to-assembly-search-path-at-runtime-in-net – nawfal Feb 20 '13 at 11:28

2 Answers2

14

You should first find the folder where theses dlls are installed then use AppDomain.AssemblyResolve to hook assembly resolution and try to load the requested assemblies from this folder.

It will look something like this (not tested, and you need to check what args.Name contain exactly, could contain the version and the strong name along with type name) :

var otherCompanyDlls = new DirectoryInfo(companyFolder).GetFiles("*.dll");

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
    var dll = otherCompanyDlls.FirstOrDefault(fi => fi.Name == args.Name);
    if (dll == null)
    {
        return null;
    }

    return Assembly.Load(dll.FullName);
};
Julien Roncaglia
  • 17,397
  • 4
  • 57
  • 75
  • Thanks, I have fixed using your idea and my code in the edited question and now works fine :) – Davide Piras Mar 10 '11 at 16:46
  • Surely my solution can be optimized, I am not sure if this method has to return null if an assembly is not found in the custom folder and if so, the CLR will still look for it in the GAC, because for example I have noticed this method is also called for assemblies like System.XML.Serializer... I am testing this in a console application which hosts a netTcp bound WCF Service, now it works and I can run the app from any folder, in the end I will create a WindowsService to host my WCF. Thanks Again .)) – Davide Piras Mar 10 '11 at 16:50
  • I've found that this approach doesn't work for me. Assembly.Load() with a full path fails; it calls the AssemblyResolve once again (since it cannot resolve the DLL). So I'm currently looking into using Assembly.LoadFrom() within the AssemblyResolve handler instead. – Per Lundberg Apr 26 '13 at 17:12
  • 'var dll = otherCompanyDlls.FirstOrDefault(fi => fi.Name == args.Name)' - causes the lienear scan of the list on every hit or miss. With a big number oaf requested and existing assemblies may be a performance issue. – George Mamaladze Jan 30 '14 at 11:32
  • 1
    It's a linear scan over a small set (number of dlls in the folder) each time some assembly doesn't resolve (Max once per unknown assembly per load context). This smell like premature optimization (_1. Don’t do it. 2. For experts only!: Don’t do it yet_). but anyway a simple `var otherCompanyDllsDict = otherCompanyDlls.ToDictionary(fi => fi.Name)` and then access by the dictionary should solve it if it happens. – Julien Roncaglia Jan 30 '14 at 16:39
  • `AssemblyResolve` is for CurrentDomain, not valid for another domain `AppDomain.CreateDomain` – Kiquenet Dec 01 '15 at 13:26
0

Use SN.exe : SN -T VendorAssembly.dll, this will return a hex number that is the public key token, then, reference the assembly from app.config. To get the version right click your vendor assembly and use that for the codeBase version value, the href=path you mentioned.

  <configuration>
       <runtime>
          <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
             <dependentAssembly>
                <assemblyIdentity name="VendorAssembly"  culture="neutral" publicKeyToken="307041694a995978"/>
                <codeBase version="1.1.1.1" href="FILE://D:/ProgramFiles/VendorName/ProductName/Support/API/Bin64/VendorAssembly.dll"/>
             </dependentAssembly>
          </assemblyBinding>
       </runtime>
    </configuration>
Ta01
  • 31,040
  • 13
  • 70
  • 99
  • Hi, wish it worked, I still get the FileNotFound exception... the assembly I need to reference is not strongly named I guess, SN -t did not work but I saw the PublicKeyToken from the exception message so I copied its value from there, still cannot be resolved... :( – Davide Piras Mar 10 '11 at 14:57