MEF Teil 10 – Parts über ExportProvider und App.config in AppDomain laden

Der folgende Post behandelt eigentlich mehrere Themen. Zum einen geht es um das Erstellen eines eigenen ExportProviders, der über die App.config die zu ladenden Parts ausliest. Zum anderen sollen die geladenen Parts in einer eigenen AppDomain ausgeführt werden.

Das Managed Extensibility Framework (MEF) nutzt ein attributbasiertes Programmiermodell, um die einzelnen Exports an die Imports zu binden. Manchmal kann es aber von Vorteil sein, wenn über eine Konfigurationsdatei die Auswahl der Parts vorgegeben wird. Hierzu bietet sich die App.config an. In einem älteren Post wurde das Erstellen von benutzerdefinierten Konfigurationsabschnitten in der App.config schon vorgestellt. Der Focus soll mehr auf das Erstellen eines ExportProviders gelegt werden, der die Parts in eine eigene AppDomain lädt und dort mit eingeschränkten Rechten zur Ausführung bringt. Grundlagen zur Erstellung eigener ExportProvider findet ihr hier.

Die zu ladenen Parts sind in der App.config aufgelistet:

<configuration>
  <configSections>
    <section name="AddInSection" type="Sample01.AddInConfigurationSection, CarHost"/>
  </configSections>
  <AddInSection>
    <AddIns>
      <AddIn id="1" contract="CarContract.ICarContract"
                    assemblyName="BMW"
                    typeName="Sample01.BMW"/>
      <AddIn id="2" contract="CarContract.ICarContract"
                    assemblyName="Mercedes"
                    typeName="Sample01.Mercedes"/>
    </AddIns>
  </AddInSection>
</configuration>

Beide Parts sind identisch aufgebaut und befinden sich jeweils in separaten Assemblies. Wie auch zu erkennen ist, besitzen die Klassen BMW und Mercedes nicht das Attribut Export. Schließlich soll die App.config vorgeben, welche Assemblies zu laden sind, nicht das attributbasierte Programmiermodell von MEF. Da die Assemblies in einer eigenen AppDomain ausgeführt werden sollen und an die Methode Parameter übergeben werden, müssen die Klassen von MarshallByRefObject abgeleitet werden.

namespace Sample01
{
    public class BMW : MarshalByRefObject, ICarContract
    {
        public void WriteString(string name)
        {
            try
            {
                Console.WriteLine("BMW: WriteString()");
                File.WriteAllText(@"C:\Test.txt", name);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

namespace Sample01
{
    public class Mercedes : MarshalByRefObject, ICarContract
    {
        public void WriteString(string name)
        {
            try
            {
                Console.WriteLine("Mercedes: WriteString()");
                File.WriteAllText(@"C:\Test.txt", name);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Die Methode WriteString() soll schreibend auf eine Datei zugreifen. Im Falle einer Exception wird die Fehlermeldung auf die Konsole ausgegeben.

Die Schnittstelle ICarContract liegt ebenfalls in einer separaten Assembly und beinhaltet die gemeinsam genutzte Methode.

namespace CarContract
{
    public interface ICarContract
    {
        void WriteString(string name);
    }
}

Die Hauptanwendung ist eher unspektakulär. Es wird eine Instanz des eigenen ExportProviders angelegt. Dieser wird an den Container übergeben. Die Methode ComposeParts() triggert das Laden und Binden der einzelnen Komponenten an. In der Foreach-Schleife wird zum Schluss von jedem Part die enthaltene Methode WriteString() aufgerufen.

namespace Sample01
{
    public class Program
    {
        [ImportMany(typeof(ICarContract))]
        private IEnumerable<Lazy<ICarContract>> CarParts { get; set; }

        static void Main(string[] args)
        {
            new Program().Run();
        }

        void Run()
        {
            CompositionContainer container = null;

            var provider = new ConfigExportProvider();
            container = new CompositionContainer(provider);
            container.ComposeParts(this);

            foreach (Lazy<ICarContract> carPart in CarParts)
                carPart.Value.WriteString("Test");

            container.Dispose();
        }
    }
}

Spannend ist die Implementierung des ExportProviders. Im Konstruktor wird der Inhalt der App.config durchlaufen und für jeden Eintrag eine Instanz der Klasse Export angelegt.

Wichtig ist die Methode CreatePart() die im Konstruktor übergeben wird (Zeile 23). Diese erzeugt die einzelnen Parts. Ausgeführt wird CreatePart() sobald MEF die einzelnen Parts benötigt. Da die Imports über die Klasse Lazy<T> gespeichert werden, geschieht dieses bei dem ersten Zugriff auf das Objekt, also bei Aufruf der Methode WriteString().

Zuvor ruft MEF noch die Methode GetExportsCore() auf. Dort wird die Liste der Exports zurückgegeben, die zu dem Import passen.

namespace Sample01
{
    public class ConfigExportProvider : ExportProvider
    {
        private List<Export> Exports { get; set; }

        public ConfigExportProvider()
        {
            this.Exports = new List<Export>();

            AddInConfigurationSection myConfigurationSection =
              (AddInConfigurationSection)ConfigurationManager.GetSection("AddInSection");
            foreach (AddInSectionValue sectionValue in myConfigurationSection.SectionValues)
            {
                var metadata = new Dictionary<string, object>();
                metadata.Add(
                        CompositionConstants.ExportTypeIdentityMetadataName,
                        sectionValue.Contract);
                var exportDefinition = new ExportDefinition(sectionValue.Contract, metadata);
                string assemblyName = sectionValue.AssemblyName;
                string typeName = sectionValue.TypeName;
                var export = new Export(exportDefinition,
                                         () => CreatePart(assemblyName, typeName));
                this.Exports.Add(export);
            }
        }

        public object CreatePart(string assemblyName, string typeName)
        {
            var info = new AppDomainSetup {
                   ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase };
            var grantSet = new PermissionSet(PermissionState.None);
            grantSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
            // soll der Dateizugriff erlaubt sein, so muss die folgende Zeile eingefügt werden
            // grantSet.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
            var appDomain = AppDomain.CreateDomain("AddIn AppDomain",
                                                    AppDomain.CurrentDomain.Evidence,
                                                    info,
                                                    grantSet);
            var instance = appDomain.CreateInstanceAndUnwrap(assemblyName, typeName);
            return instance;
        }

        protected override IEnumerable<Export> GetExportsCore(
                                        ImportDefinition definition,
                                        AtomicComposition atomicComposition)
        {
            return this.Exports.Where(x => definition.IsConstraintSatisfiedBy(x.Definition));
        }
    }
}

In der Methode CreatePart() wird eine separate AppDomain mit stark eingeschränkten Rechten angelegt. In dieser wird die Assembly geladen. Die Rechte sind soweit eingeschränkt, dass Dateizugriffe nicht erlaubt sind.

Wird das Programm ausgeführt, so endet der Versuch, in eine Datei zu schreiben, in einer SecurityException.

CommandWindowSample01

Beispiel (Visual Studio 2010) auf GitHub

Author: Stefan Henneken

I’m Stefan Henneken, a software developer based in Germany. This blog is just a collection of various articles I want to share, mostly related to Software Development.

8 thoughts on “MEF Teil 10 – Parts über ExportProvider und App.config in AppDomain laden”

  1. Die spitzen Klammern sollten jetzt vollzählig vorhanden sein.
    Ich habe bei WordPress immer das Problem, das im Quellcode Wörter in spitzen Klammern nicht dargestellt werden. Durch das Einfügen von Leerzeichen konnte ich mich immer behelfen.
    Auch wird die horizontale Scroll bar innerhalb der Quellcode Bereiche nicht mehr angezeigt; zumindest nicht im IE. Firefox hat damit keine Probleme. Vielleicht hat jemand ein Tipp für mich 😉

  2. HI,
    vielen Dank. Deine Artikel waren wirklich sehr Hilfreich.
    Wollte ein eigenes Plugin Framework entwickeln, MEF scheint hier aber die bessere Wahl zu sein.
    lg

  3. Vielen Dank für den super Artikel. Ich versuche gerade es mit WPF und mit der MVVM Struktur einzubinden. Hast du vielleicht einige Ansätze (Beispiele) die du schon gemacht hast? Ich komme nämlich an einige Stellen nicht weiter. Vielen lieben Dank

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: