Come si esegue il ciclo tra gli assembly attualmente caricati?


120

Ho una pagina di "diagnostica" nella mia applicazione ASP.NET che fa cose come verificare le connessioni al database, visualizzare le impostazioni di app e le stringhe di connessione correnti, ecc. Una sezione di questa pagina mostra le versioni di assembly di tipi importanti utilizzati in tutto , ma non sono riuscito a capire come mostrare in modo efficace le versioni di TUTTI gli assembly caricati.

Qual è il modo più efficace per capire tutti gli assembly attualmente referenziati e / o caricati in un'applicazione .NET?

Nota: non sono interessato ai metodi basati su file, come l'iterazione di * .dll in una determinata directory. Sono interessato a ciò che l'applicazione sta effettivamente utilizzando in questo momento.

Risposte:


24

Questo metodo di estensione ottiene tutti gli assembly a cui si fa riferimento, in modo ricorsivo, inclusi gli assembly annidati.

Durante l'utilizzo ReflectionOnlyLoad, carica gli assembly in un AppDomain separato, che ha il vantaggio di non interferire con il processo JIT.

Noterai che c'è anche un file MyGetMissingAssembliesRecursive. È possibile utilizzarlo per rilevare eventuali assembly mancanti a cui viene fatto riferimento, ma non presenti nella directory corrente per qualche motivo. Questo è incredibilmente utile quando si utilizza MEF . La lista di restituzione ti darà sia l'assembly mancante, sia chi lo possiede (il suo genitore).

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

Aggiornare

Per rendere sicuro questo thread di codice, mettici un lockintorno. Attualmente non è thread-safe per impostazione predefinita, poiché fa riferimento a una variabile globale statica condivisa per fare la sua magia.


L'ho appena riscritto per essere thread-safe, quindi può essere chiamato da molti thread diversi contemporaneamente (non sono sicuro del motivo per cui lo vorresti, ma hey, è più sicuro). Fammi sapere se vuoi che inserisca il codice.
Contango

2
@Contango Potresti pubblicare la tua versione thread-safe, o se hai scritto un blog a riguardo, pubblicarla?
Robert

2
Il modo ingenuo per rendere questo thread sicuro è mettere un locktutto intorno. L'altro metodo che ho usato ha eliminato la dipendenza dalla statica globale "_dependentAssemblyList", quindi diventa thread-safe senza bisogno di un lock, che ha alcuni leggeri vantaggi in termini di velocità se più thread stanno cercando di determinare simultaneamente quali assembly mancano (questo è un po 'di un caso d'angolo).
Contango

3
l'aggiunta di un locknon aggiungerà molto in termini di "thread safe". Certo, questo fa sì che quel blocco di codice venga eseguito solo uno alla volta; ma altri thread possono caricare gli assembly ogni volta che vogliono e questo potrebbe causare problemi con alcuni foreachloop.
Peter Ritchie

1
@Peter Ritchie Esiste una variabile globale statica condivisa che viene utilizzata durante la ricorsione, quindi l'aggiunta di un blocco attorno a tutti gli accessi renderà quella parte thread sicura. È solo una buona pratica di programmazione. Di solito tutti gli assembly richiesti vengono caricati all'avvio, a meno che non venga utilizzato qualcosa come MEF, quindi la sicurezza dei thread non è davvero un problema nella pratica.
Contango

193

Ottenere gli assembly caricati per la corrente AppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

Ottenere gli assembly a cui fa riferimento un altro assembly:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

Si noti che se l'assembly A fa riferimento all'assembly B e l'assembly A è caricato, ciò non implica che l'assembly B sia caricato anche. L'assieme B verrà caricato solo se e quando necessario. Per questo motivo, GetReferencedAssemblies()restituisce le AssemblyNameistanze anzichéAssembly istanze.


2
Bene, ho bisogno di qualcosa del genere: data una soluzione .net, voglio scoprire tutti gli assembly a cui si fa riferimento in tutti i progetti. Idee?
Kumar Vaibhav

Tieni presente che entrambi i metodi elencano solo le DLL effettivamente utilizzate. Ovviamente non ha senso avere riferimenti in soluzioni che non vengono utilizzate, ma questo potrebbe creare confusione quando qualcuno sta cercando di scansionare speculativamente tutti gli assembly. Tutti gli assembly potrebbero non essere visualizzati.
Pompair

3
OP chiede agli assembly attualmente caricati non referenziati. Questo risponde alla domanda. Esattamente quello che stavo cercando.
MikeJansen

evento per sapere quando viene caricato l'assembly B?
Kiquenet
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.