Jak przeglądać aktualnie załadowane zestawy?

120

Mam stronę "diagnostyki" w mojej aplikacji ASP.NET, która wykonuje takie rzeczy, jak weryfikowanie połączeń z bazą danych, wyświetlanie bieżących ustawień appSettings i ConnectionStrings, itp. Sekcja tej strony wyświetla wersje Assembly ważnych typów używanych w całym tekście , ale nie mogłem wymyślić, jak skutecznie pokazać wersje WSZYSTKICH załadowanych zestawów.

Jaki jest najskuteczniejszy sposób ustalenia wszystkich aktualnie przywoływanych i / lub załadowanych zestawów w aplikacji .NET?

Uwaga: nie interesują mnie metody oparte na plikach, takie jak iteracja po * .dll w określonym katalogu. Interesuje mnie, czego ta aplikacja faktycznie używa w tej chwili.

Jess Chadwick
źródło

Odpowiedzi:

24

Ta metoda rozszerzenia pobiera wszystkie zestawy, do których istnieją odwołania, rekurencyjnie, w tym zestawy zagnieżdżone.

Jak używa ReflectionOnlyLoad, ładuje zestawy w oddzielnej domenie AppDomain, co ma tę zaletę, że nie koliduje z procesem JIT.

Zauważysz, że istnieje również plik MyGetMissingAssembliesRecursive. Możesz użyć tego do wykrycia brakujących zestawów, do których istnieją odwołania, ale z jakiegoś powodu nie ma ich w bieżącym katalogu. Jest to niezwykle przydatne podczas korzystania z MEF . Lista zwrotów zawiera informacje o brakującym zestawie oraz o tym, kto jest jego właścicielem (jego rodzicem).

/// <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];
    }
}

Aktualizacja

Aby zabezpieczyć ten wątek kodu, umieść lockgo wokół. Obecnie nie jest domyślnie bezpieczny dla wątków, ponieważ odwołuje się do udostępnionej statycznej zmiennej globalnej, aby wykonać swoją magię.

Contango
źródło
Po prostu przepisałem to, aby był bezpieczny dla wątków, więc można go wywoływać z wielu różnych wątków jednocześnie (nie wiem, dlaczego chcesz tego, ale hej, jest bezpieczniejszy). Daj mi znać, jeśli chcesz, żebym wysłał kod.
Contango
2
@Contango Czy możesz opublikować bezpieczną wersję swojego wątku, a jeśli napisałeś o tym na blogu, opublikować to?
Robert
2
Naiwnym sposobem zabezpieczenia tego wątku jest umieszczenie lockwokół niego całości. Druga metoda, której użyłem, wyeliminowała zależność od globalnej statycznej listy „_dependentAssemblyList”, dzięki czemu staje się bezpieczna dla wątków bez potrzeby a lock, co ma pewne niewielkie zalety szybkości, jeśli wiele wątków próbuje jednocześnie określić, których zestawów brakuje (jest to trochę skrzynka narożna).
Contango
3
dodanie a locknie doda zbyt wiele na drodze do „bezpiecznego wątku”. Jasne, to sprawia, że ​​ten blok kodu jest wykonywany tylko jeden naraz; ale inne wątki mogą ładować zestawy w dowolnym momencie, co może powodować problemy z niektórymi foreachpętlami.
Peter Ritchie
1
@Peter Ritchie Istnieje wspólna statyczna zmienna globalna, która jest używana podczas rekurencji, więc dodanie blokady wokół wszystkich dostępów spowoduje, że ta część wątku będzie bezpieczna. To po prostu dobra praktyka programistyczna. Zwykle wszystkie wymagane zestawy są ładowane podczas uruchamiania, chyba że jest używane coś takiego jak MEF, więc bezpieczeństwo wątków nie jest tak naprawdę problemem w praktyce.
Contango,
193

Pobieranie załadowanych zestawów dla bieżącego AppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

Pobieranie zestawów, do których odwołuje się inny zestaw:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

Należy zauważyć, że jeśli zestaw A odwołuje się do zestawu B, a zestaw A jest załadowany, nie oznacza to, że zestaw B również jest załadowany. Zespół B zostanie załadowany tylko wtedy, gdy jest to potrzebne. Z tego powodu GetReferencedAssemblies()zwraca AssemblyNameraczej Assemblyinstancje niż instancje.

Kent Boogaart
źródło
2
Cóż, potrzebuję czegoś takiego - biorąc pod uwagę rozwiązanie .net, chcę znaleźć wszystkie zestawy, do których istnieją odniesienia we wszystkich projektach. Pomysły?
Kumar Vaibhav
Należy pamiętać, że obie metody wymieniają tylko faktycznie używane biblioteki DLL. Oczywiście nie ma sensu umieszczanie odniesień w rozwiązaniach, które nie są używane, ale może to być mylące, gdy ktoś próbuje spekulacyjnie przeskanować WSZYSTKIE zestawy. Wszystkie zestawy mogą po prostu się nie pojawić.
Pompair
3
OP pyta o aktualnie załadowane zestawy, do których nie istnieją odniesienia. To odpowiada na pytanie. Dokładnie to, czego szukałem.
MikeJansen
aby wiedzieć, kiedy zestaw B jest ładowany?
Kiquenet