Jednoduché načítání externích assembly

Tomáš Holan       27. 3. 2011       Architektura, I/O operace       7311 zobrazení

Uvažujme následující scénář. Ve své aplikaci máme nějakou část, kterou potřebujeme umět customizovat nebo rozšiřovat pomoci externích knihoven. Tím myslím to, že bude nějaké výchozí chování (např. to, že daná funkcionalita nebude dostupná). Pokud ale k aplikaci umístíme jednu nebo více assembly (dále je budu označovat custom assembly), výchozí chování se změní nebo daná funkcionalita doplní. Například se může jednat o zákaznické (či uživatelské) knihovny pro tiskové sestavy, změnu výchozího worlflow za nějaké jiné navržené speciálně na zakázku (oba tyto scénáře jsem v praxi již řešil) či přímo variantu klasických addin doplňků.

Ok nějakou představu snad máme, dále uvedu, že se nebudeme zabývat tím, jak bude z aplikace prováděno samotné volání části implementované v custom assembly. Typicky to může být tak, že custom assembly i samotná aplikace budou referencovat assembly s definicí nějakého společného interface a custom assembly bude obsahovat třídu nebo třídy implementující tento interface. Tyto třídy se pak budou z aplikace volat pomoci třídy Activator.

To čím se ale zabývat budeme je jak custom assembly najít na file systému a následně načíst. Dále také budeme z důvodu obecnosti předpokládat, že těchto souboru může být více než pouze assembly jediná. Ještě je asi důležité zmínit, že jednou z možností, které se ale nyní věnovat nebudu, je použít řešení v rámci MEF (Managed Extensibility Framework).

Teď už k samotnému řešení, shrňme si co musíme vyřešit. Jednak musíme vyhledat příslušné soubory na disku. K tomu potřebujeme znát v jakém adresáři a jaké soubory budeme hledat. Pro náš příklad zvolíme adresář stejný jako adresář, ve kterém je umístěna vlastní aplikace (nepředpokládáme zde instalaci aplikace do GAC). Filtr pro hledání souboru je jednoduchý, protože chceme vyhledat soubory assembly, bude se jednat o *.dll “natvrdo”.

Dále musíme každou vyhledanou assembly načíst a potom nějakou podmínkou rozhodnout, zda je to knihovna, která nás zajímá. Touto podmínkou může být např. existence nějakého resource assembly, existence nějakého typu podle jména nebo existence typu, který implementuje nějaký interface. V našem příkladu si ukážeme variantu z hledáním resource.

Poslední problém k vyřešení je to, že celý tento proces nebude nejrychlejší a proto budeme chtít, aby se spouštěl pouze jedenkrát, a pak se již použili stejné výsledky. Důsledkem toho bude, že custom assembly nepůjde dynamicky vyměňovat za běhu aplikace, budeme předpokládat, že nám toto nevadí.

Po provedení předchozí analýzy je vytvořit výsledný kód již poměrně jednoduché.

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Collections.ObjectModel;

internal static class CustomAssemblyFinder
{
    #region constructors and destructors
    private const string cCustomAssemblyResourceName = "..."; //Resource name
    #endregion

    #region member varible and default property initialization
    private static readonly object s_SyncRoot = new object();
    private static ReadOnlyCollection<Assembly> s_CustomAssemblies;
    #endregion

    #region property getters/setters
    public static IEnumerable<Assembly> CustomAssemblies
    {
        get
        {
            if (s_CustomAssemblies == null)
            {
                lock (s_SyncRoot)
                {
                    if (s_CustomAssemblies == null)
                    {
                        s_CustomAssemblies = (from assembly in GetAssemblies(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
                                              where assembly.GetManifestResourceInfo(cCustomAssemblyResourceName) != null
                                              select assembly).ToList().AsReadOnly();
                    }
                }
            }

            return s_CustomAssemblies;
        }
    }
    #endregion

    #region private member functions
    private static IEnumerable<Assembly> GetAssemblies(string path)
    {
        foreach (string filename in Directory.EnumerateFiles(path, "*.dll"))
        {
            Assembly assembly = LoadAssemblyGuarded(filename);
            if (assembly != null)
            {
                yield return assembly;
            }
        }
    }

    private static Assembly LoadAssemblyGuarded(string assemblyFilePath)
    {
        var assemblyName = AssemblyName.GetAssemblyName(assemblyFilePath);

        try
        {
            return Assembly.Load(assemblyName);
        }
        catch (FileNotFoundException)
        {
            return null;
        }
        catch (FileLoadException)
        {
            return null;
        }
        catch (BadImageFormatException)
        {
            return null;
        }
        catch (ReflectionTypeLoadException)
        {
            return null;
        }
    }
    #endregion
}

Pouze upozorním na několik zajímavých skutečnosti: Pro vyhledávání souborů používám metodu Directory.EnumerateFiles, která je nově dostupná od .NET Frameworku 4.0. U načítání assembly je ošetřeno několik možných chyb pro ignorování souboru např. v případě kdyby se vůbec nejednalo o assembly nebo by assembly byla přeložená pro jinou platformu apod. Výslednou kolekci CustomAssemblies vracíme jako objekt typu ReadOnlyCollection<T>, aby jsme zabránili její případné modifikaci zvenku.

 

hodnocení článku

0 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback