Bagaimana cara menambahkan folder ke jalur pencarian perakitan saat runtime di .NET?

130

DLL saya dimuat oleh aplikasi pihak ketiga, yang tidak dapat kami sesuaikan. Majelis saya harus ditempatkan di folder mereka sendiri. Saya tidak bisa memasukkannya ke GAC (aplikasi saya memiliki persyaratan untuk digunakan menggunakan XCOPY). Ketika root DLL mencoba memuat sumber atau ketik dari DLL lain (dalam folder yang sama), pemuatan gagal (FileNotFound). Apakah mungkin untuk menambahkan folder tempat DLL saya berada ke jalur pencarian perakitan secara terprogram (dari DLL root)? Saya tidak diizinkan mengubah file konfigurasi aplikasi.

isobretatel
sumber

Jawaban:

155

Kedengarannya seperti Anda bisa menggunakan acara AppDomain.AssemblyResolve dan memuat dependensi secara manual dari direktori DLL Anda.

Edit (dari komentar):

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder);

static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
{
    string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
    if (!File.Exists(assemblyPath)) return null;
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    return assembly;
}
Mattias S
sumber
4
Terima kasih, Mattias! Ini berfungsi: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = new ResolveEventHandler (LoadFromSameFolderResolveEventHandler); static Assembly LoadFromSameFolderResolveEventHandler (pengirim objek, ResolveEventArgs args) {string folderPath = Path.GetDirectoryName (Assembly.GetExecutingAssembly (). Location); string assemblyPath = Path.Combine (folderPath, args.Name + ".dll"); Assembly assembly = Assembly.LoadFrom (assemblyPath); perakitan kembali; }
isobretatel
1
Apa yang akan Anda lakukan jika Anda ingin "mundur" ke Penyelesai dasar. misalnyaif (!File.Exists(asmPath)) return searchInGAC(...);
Tomer W
Ini berhasil dan saya belum dapat menemukan alternatif. Terima kasih
TByte
57

Anda bisa menambahkan jalur probing ke file .config aplikasi Anda, tetapi itu hanya akan berfungsi jika path probing terdapat di dalam direktori basis aplikasi Anda.

Mark Seemann
sumber
3
Terima kasih telah menambahkan ini. Saya telah melihat AssemblyResolvesolusinya berkali-kali, bagus untuk memiliki pilihan lain (dan lebih mudah).
Samuel Neff
1
Jangan lupa untuk memindahkan file App.config dengan aplikasi Anda jika Anda menyalin aplikasi Anda di tempat lain ..
Maxter
12

Pembaruan untuk Kerangka 4

Karena Kerangka 4 meningkatkan acara AssemblyResolve juga untuk sumber daya sebenarnya penangan ini bekerja lebih baik. Ini didasarkan pada konsep bahwa pelokalan berada dalam subdirektori aplikasi (satu untuk pelokalan dengan nama budaya yaitu C: \ MyApp \ it untuk Italia) Di dalam ada file sumber daya. Pawang bekerja juga jika pelokalannya adalah negara-wilayah yaitu IT-IT atau pt-BR. Dalam hal ini pawang "mungkin dipanggil beberapa kali: satu kali untuk setiap budaya dalam rantai fallback" [dari MSDN]. Ini berarti bahwa jika kita mengembalikan null untuk file sumber daya "it-IT" kerangka kerja memunculkan acara yang meminta "it".

Acara kait

        AppDomain currentDomain = AppDomain.CurrentDomain;
        currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);

Penangan acara

    Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        //This handler is called only when the common language runtime tries to bind to the assembly and fails.

        Assembly executingAssembly = Assembly.GetExecutingAssembly();

        string applicationDirectory = Path.GetDirectoryName(executingAssembly.Location);

        string[] fields = args.Name.Split(',');
        string assemblyName = fields[0];
        string assemblyCulture;
        if (fields.Length < 2)
            assemblyCulture = null;
        else
            assemblyCulture = fields[2].Substring(fields[2].IndexOf('=') + 1);


        string assemblyFileName = assemblyName + ".dll";
        string assemblyPath;

        if (assemblyName.EndsWith(".resources"))
        {
            // Specific resources are located in app subdirectories
            string resourceDirectory = Path.Combine(applicationDirectory, assemblyCulture);

            assemblyPath = Path.Combine(resourceDirectory, assemblyFileName);
        }
        else
        {
            assemblyPath = Path.Combine(applicationDirectory, assemblyFileName);
        }



        if (File.Exists(assemblyPath))
        {
            //Load the assembly from the specified path.                    
            Assembly loadingAssembly = Assembly.LoadFrom(assemblyPath);

            //Return the loaded assembly.
            return loadingAssembly;
        }
        else
        {
            return null;
        }

    }
bubi
sumber
Anda dapat menggunakan AssemblyNamekonstruktor untuk memecahkan kode nama rakitan alih-alih mengandalkan penguraian string rakitan.
Sebazzz
10

Penjelasan terbaik dari MS itu sendiri :

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);

private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
    //This handler is called only when the common language runtime tries to bind to the assembly and fails.

    //Retrieve the list of referenced assemblies in an array of AssemblyName.
    Assembly MyAssembly, objExecutingAssembly;
    string strTempAssmbPath = "";

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

    //Loop through the array of referenced assembly names.
    foreach(AssemblyName strAssmbName in arrReferencedAssmbNames)
    {
        //Check for the assembly names that have raised the "AssemblyResolve" event.
        if(strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
        {
            //Build the path of the assembly from where it has to be loaded.                
            strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0,args.Name.IndexOf(","))+".dll";
            break;
        }

    }

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

    //Return the loaded assembly.
    return MyAssembly;          
}
nawfal
sumber
AssemblyResolveuntuk CurrentDomain, tidak berlaku untuk domain lainAppDomain.CreateDomain
Kiquenet
8

Untuk pengguna C ++ / CLI, inilah jawaban @Mattias S '(yang cocok untuk saya):

using namespace System;
using namespace System::IO;
using namespace System::Reflection;

static Assembly ^LoadFromSameFolder(Object ^sender, ResolveEventArgs ^args)
{
    String ^folderPath = Path::GetDirectoryName(Assembly::GetExecutingAssembly()->Location);
    String ^assemblyPath = Path::Combine(folderPath, (gcnew AssemblyName(args->Name))->Name + ".dll");
    if (File::Exists(assemblyPath) == false) return nullptr;
    Assembly ^assembly = Assembly::LoadFrom(assemblyPath);
    return assembly;
}

// put this somewhere you know it will run (early, when the DLL gets loaded)
System::AppDomain ^currentDomain = AppDomain::CurrentDomain;
currentDomain->AssemblyResolve += gcnew ResolveEventHandler(LoadFromSameFolder);
msarahan
sumber
6

Saya telah menggunakan solusi @Mattias S. Jika Anda benar-benar ingin menyelesaikan ketergantungan dari folder yang sama - Anda harus mencoba menggunakan Meminta lokasi perakitan , seperti yang ditunjukkan di bawah ini. args.RequestingAssembly harus diperiksa untuk membatalkan.

System.AppDomain.CurrentDomain.AssemblyResolve += (s, args) =>
{
    var loadedAssembly = System.AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName == args.Name).FirstOrDefault();
    if(loadedAssembly != null)
    {
        return loadedAssembly;
    }

    if (args.RequestingAssembly == null) return null;

    string folderPath = Path.GetDirectoryName(args.RequestingAssembly.Location);
    string rawAssemblyPath = Path.Combine(folderPath, new System.Reflection.AssemblyName(args.Name).Name);

    string assemblyPath = rawAssemblyPath + ".dll";

    if (!File.Exists(assemblyPath))
    {
        assemblyPath = rawAssemblyPath + ".exe";
        if (!File.Exists(assemblyPath)) return null;
    } 

    var assembly = System.Reflection.Assembly.LoadFrom(assemblyPath);
    return assembly;
 };
Aryéh Radlé
sumber
4

lihat AppDomain.AppendPrivatePath (usang) atau AppDomainSetup.PrivateBinPath

Vincent Lidou
sumber
11
Dari MSDN : Mengubah properti instance AppDomainSetup tidak memengaruhi AppDomain apa pun yang ada. Ini hanya dapat memengaruhi pembuatan AppDomain baru, ketika metode CreateDomain dipanggil dengan instance AppDomainSetup sebagai parameter.
Nathan
2
AppDomain.AppendPrivatePathDokumentasi tampaknya menyarankan bahwa ia harus mendukung perluasan AppDomainjalur pencarian secara dinamis , hanya saja fitur tersebut sudah tidak digunakan lagi. Jika berhasil, ini adalah solusi yang jauh lebih bersih daripada kelebihan beban AssemblyResolve.
binki
Untuk referensi, sepertinya AppDomain.AppendPrivatePath tidak melakukan apa-apa di .NET Core dan pembaruan .PrivateBinPathdalam kerangka penuh .
Kevinoid
3

Saya datang ke sini dari pertanyaan lain (duplikat bertanda) tentang menambahkan tag probing ke file App.Config.

Saya ingin menambahkan sidenote ke ini - Visual studio telah menghasilkan file App.config, namun menambahkan tag probing ke tag runtime yang dibuat sebelumnya tidak berfungsi! Anda memerlukan tag runtime terpisah dengan tag probing disertakan. Singkatnya, App.Config Anda akan terlihat seperti ini:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Text.Encoding.CodePages" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

  <!-- Discover assemblies in /lib -->
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="lib" />
    </assemblyBinding>
  </runtime>
</configuration>

Butuh beberapa waktu untuk mencari tahu jadi saya mempostingnya di sini. Juga menerima Paket The PrettyBin NuGet . Ini adalah paket yang memindahkan dll secara otomatis. Saya menyukai pendekatan yang lebih manual sehingga saya tidak menggunakannya.

Juga - di sini adalah skrip pembuatan pos yang menyalin semua .dll / .xml / .pdb ke / Lib. Ini membuka folder / debug (atau / release), apa yang saya pikir orang capai.

:: Moves files to a subdirectory, to unclutter the application folder
:: Note that the new subdirectory should be probed so the dlls can be found.
SET path=$(TargetDir)\lib
if not exist "%path%" mkdir "%path%"
del /S /Q "%path%"
move /Y $(TargetDir)*.dll "%path%"
move /Y $(TargetDir)*.xml "%path%"
move /Y $(TargetDir)*.pdb "%path%"
sommmen
sumber