Bagaimana Anda melakukan loop melalui rakitan yang saat ini dimuat?

120

Saya mendapat halaman "diagnostik" di aplikasi ASP.NET saya yang melakukan hal-hal seperti memverifikasi koneksi database, menampilkan appSettings dan ConnectionStrings saat ini, dll. Sebuah bagian dari halaman ini menampilkan versi Assembly dari jenis penting yang digunakan di seluruh , tetapi saya tidak tahu cara secara efektif menampilkan versi SEMUA rakitan yang dimuat.

Apa cara paling efektif untuk mengetahui semua Sidang yang direferensikan dan / atau dimuat saat ini dalam aplikasi .NET?

Catatan: Saya tidak tertarik dengan metode berbasis file, seperti melakukan iterasi melalui * .dll di direktori tertentu. Saya tertarik dengan aplikasi yang sebenarnya digunakan sekarang.

Jess Chadwick
sumber

Jawaban:

24

Metode ekstensi ini mendapatkan semua rakitan yang direferensikan, secara rekursif, termasuk rakitan bersarang.

Saat digunakan ReflectionOnlyLoad, ia memuat rakitan di AppDomain terpisah, yang memiliki keuntungan karena tidak mengganggu proses JIT.

Anda akan melihat bahwa ada juga file MyGetMissingAssembliesRecursive. Anda dapat menggunakan ini untuk mendeteksi rakitan yang hilang yang direferensikan, tetapi tidak ada di direktori saat ini karena beberapa alasan. Ini sangat berguna saat menggunakan MEF . Daftar pengembalian akan memberi Anda rakitan yang hilang, dan siapa yang memilikinya (induknya).

/// <summary>
///     Intent: Get referenced assemblies, either recursively or flat. Not thread safe, if running in a multi
///     threaded environment must use locks.
/// </summary>
public static class GetReferencedAssemblies
{
    static void Demo()
    {
        var referencedAssemblies = Assembly.GetEntryAssembly().MyGetReferencedAssembliesRecursive();
        var missingAssemblies = Assembly.GetEntryAssembly().MyGetMissingAssembliesRecursive();
        // Can use this within a class.
        //var referencedAssemblies = this.MyGetReferencedAssembliesRecursive();
    }

    public class MissingAssembly
    {
        public MissingAssembly(string missingAssemblyName, string missingAssemblyNameParent)
        {
            MissingAssemblyName = missingAssemblyName;
            MissingAssemblyNameParent = missingAssemblyNameParent;
        }

        public string MissingAssemblyName { get; set; }
        public string MissingAssemblyNameParent { get; set; }
    }

    private static Dictionary<string, Assembly> _dependentAssemblyList;
    private static List<MissingAssembly> _missingAssemblyList;

    /// <summary>
    ///     Intent: Get assemblies referenced by entry assembly. Not recursive.
    /// </summary>
    public static List<string> MyGetReferencedAssembliesFlat(this Type type)
    {
        var results = type.Assembly.GetReferencedAssemblies();
        return results.Select(o => o.FullName).OrderBy(o => o).ToList();
    }

    /// <summary>
    ///     Intent: Get assemblies currently dependent on entry assembly. Recursive.
    /// </summary>
    public static Dictionary<string, Assembly> MyGetReferencedAssembliesRecursive(this Assembly assembly)
    {
        _dependentAssemblyList = new Dictionary<string, Assembly>();
        _missingAssemblyList = new List<MissingAssembly>();

        InternalGetDependentAssembliesRecursive(assembly);

        // Only include assemblies that we wrote ourselves (ignore ones from GAC).
        var keysToRemove = _dependentAssemblyList.Values.Where(
            o => o.GlobalAssemblyCache == true).ToList();

        foreach (var k in keysToRemove)
        {
            _dependentAssemblyList.Remove(k.FullName.MyToName());
        }

        return _dependentAssemblyList;
    }

    /// <summary>
    ///     Intent: Get missing assemblies.
    /// </summary>
    public static List<MissingAssembly> MyGetMissingAssembliesRecursive(this Assembly assembly)
    {
        _dependentAssemblyList = new Dictionary<string, Assembly>();
        _missingAssemblyList = new List<MissingAssembly>();
        InternalGetDependentAssembliesRecursive(assembly);

        return _missingAssemblyList;
    }

    /// <summary>
    ///     Intent: Internal recursive class to get all dependent assemblies, and all dependent assemblies of
    ///     dependent assemblies, etc.
    /// </summary>
    private static void InternalGetDependentAssembliesRecursive(Assembly assembly)
    {
        // Load assemblies with newest versions first. Omitting the ordering results in false positives on
        // _missingAssemblyList.
        var referencedAssemblies = assembly.GetReferencedAssemblies()
            .OrderByDescending(o => o.Version);

        foreach (var r in referencedAssemblies)
        {
            if (String.IsNullOrEmpty(assembly.FullName))
            {
                continue;
            }

            if (_dependentAssemblyList.ContainsKey(r.FullName.MyToName()) == false)
            {
                try
                {
                    var a = Assembly.ReflectionOnlyLoad(r.FullName);
                    _dependentAssemblyList[a.FullName.MyToName()] = a;
                    InternalGetDependentAssembliesRecursive(a);
                }
                catch (Exception ex)
                {
                    _missingAssemblyList.Add(new MissingAssembly(r.FullName.Split(',')[0], assembly.FullName.MyToName()));
                }
            }
        }
    }

    private static string MyToName(this string fullName)
    {
        return fullName.Split(',')[0];
    }
}

Memperbarui

Untuk membuat utas kode ini aman, letakkan lockdi sekitarnya. Saat ini tidak aman utas secara default, karena merujuk variabel global statis bersama untuk melakukan keajaibannya.

Contango
sumber
Saya baru saja menulis ulang ini agar aman untuk utas, sehingga dapat dipanggil dari banyak utas berbeda secara bersamaan (tidak yakin mengapa Anda menginginkannya, tapi hei, ini lebih aman). Beri tahu saya jika Anda ingin saya memposting kode.
Contango
2
@Contango Bisakah Anda memposting versi aman Thread, atau jika Anda telah menulis blog tentangnya, posting itu?
Robert
2
Cara naif untuk membuat utas ini aman adalah dengan meletakkan locksemuanya. Metode lain yang saya gunakan menghilangkan ketergantungan pada statis global "_dependentAssemblyList", sehingga menjadi thread aman tanpa memerlukan lock, yang memiliki sedikit keuntungan kecepatan jika beberapa thread mencoba untuk secara bersamaan menentukan rakitan apa yang hilang (ini sedikit kasus sudut).
Contango
3
menambahkan a locktidak akan menambah banyak cara "aman utas". Tentu, itu membuat blok kode itu hanya dieksekusi satu per satu; tetapi utas lain dapat memuat rakitan kapan saja mereka suka dan itu dapat menyebabkan masalah dengan beberapa foreachloop.
Peter Ritchie
1
@Peter Ritchie Ada variabel global statis bersama yang digunakan selama rekursi, jadi menambahkan kunci di sekitar semua akses akan membuat utas bagian itu aman. Itu hanya latihan pemrograman yang bagus. Biasanya semua rakitan yang diperlukan dimuat saat startup, kecuali sesuatu seperti MEF digunakan, jadi keamanan thread sebenarnya tidak menjadi masalah dalam praktiknya.
Contango
193

Mendapatkan rakitan yang dimuat untuk arus AppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

Mendapatkan majelis yang direferensikan oleh majelis lain:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

Perhatikan bahwa jika perakitan A referensi perakitan B dan perakitan A dimuat, itu tidak berarti bahwa perakitan B juga dimuat. Majelis B hanya akan dimuat jika dan saat dibutuhkan. Oleh karena itu, GetReferencedAssemblies()mengembalikan AssemblyNameinstance, bukan Assemblyinstance.

Kent Boogaart
sumber
2
Yah saya butuh sesuatu seperti ini - mengingat solusi bersih. Saya ingin mengetahui semua rakitan yang direferensikan di semua proyek. Ide ide?
Kumar Vaibhav
Harap dicatat bahwa kedua metode hanya mencantumkan dll yang benar-benar digunakan. Jelas tidak masuk akal untuk memiliki referensi dalam solusi yang tidak digunakan, tetapi ini mungkin membingungkan ketika seseorang mencoba untuk memindai secara spekulatif melalui SEMUA rakitan. Semua majelis mungkin tidak muncul begitu saja.
Pompair
3
OP meminta majelis yang dimuat saat ini bukan majelis yang direferensikan. Ini menjawab pertanyaan itu. Persis apa yang saya cari.
MikeJansen
acara untuk mengetahui kapan Assembly B dimuat?
Kiquenet