Announcement

Collapse
No announcement yet.

Modulcontainer ?!

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Modulcontainer ?!

    Hallo zusammen!

    Wie stelle ich es am schlausten an, wenn ich eine Art "Modulcontainer", der viele kleine Anwendungen für den Endbenutzer zentralisiert, erstellen möchte?
    Anfangs habe ich es mir so gedacht, das der Modulcontainer auf den Clients installiert wird und dieser sich über das iNet dynamisch neue, verfügbare Anwendungen in Form von DLL's zieht und diese dann dynamisch einbindet.
    Ist das eurer Meinung nach der richtige Weg?
    Um ein kleines Beispiel zur dynamischen Einbindung von DLL's oder Assembly's wäre ich sehr dankbar, da ich damit noch nicht so wirklich viel zu tun hatte.

    Gruß
    Sven

  • #2
    PlugIn

    Hallo,

    wenn ich den Vorhaben richtig verstanden habe willst du die PlugIn-Technik verwenden.

    Dynamisches Einbinden von Assemblies
    Dabei sind zwei Möglichkeiten der Einbindung zu unterscheiden:
    1. ausführbare Assembly (EXE - wobei in .net eine EXE auch als DLL verwendet werden kann)
    2. nicht ausführbare Assembly (DLL)

    Für beide Möglichkeiten gilt dass eine Liste mit den verfügbaren Assemblies (in Folge als PlugIn bezeichnet) verwaltet werden muss.

    Variante 1:
    Die erste Variante - das Einbinden von ausführbaren Assemblies - ist leicht umzusetzen denn die aus der Liste gewählte Assembly kann per System.Diagnostic.Process.Start(assemblyName) ausgeführt werden.

    Variante 2:
    Für das dynamische Einbinden eines PlugIns existieren wiederum zwei Varianten:
    Verwendung von
    1. Reflektion
    2. Schnittstellen

    um die Aktionen des PlugIns durchzuführen.

    Die erste Variante ist aufwändig umzusetzen denn alle Aktionen müssen per "später Bindung" durchgeführt werden. Dieser Weg wird in Folge nicht weiter betrachtet.

    Für die Schnittstellen-Variante will ich ein Beispiel für ein einfaches Berechungsprogramm liefern.

    Dabei definieren wir die Schnittstelle die jedes PlugIn implementieren muss und über diese Schnittstelle kann auf die verschiedenen PlugIns vom Client einfach zugegriffen werden.
    [highlight=c#]
    namespace gfoidl.Beispiel.PlugIn.PlugInDefinition
    {
    public interface IPlugInDefinition
    {
    double Calculate(double number1, double numer2);
    }
    }
    [/highlight]

    Ein PlugIn zur Addition:
    [highlight=c#]
    using gfoidl.Beispiel.PlugIn.PlugInDefinition;

    public class Add : IPlugInDefinition
    {
    public double Calculate(double number1, double number2)
    {
    return number1 + number2;
    }
    }
    [/highlight]

    Und ein PlugIn für die Subtraktion:
    [highlight=c#]
    using gfoidl.Beispiel.PlugIn.PlugInDefinition;

    public class Subtract : IPlugInDefinition
    {
    public double Calculate(double number1, double number2)
    {
    return number1 - number2;
    }
    }
    [/highlight]

    Die Liste mit der die PlugIns auf dem Client verwaltet werden ist folgendes XML:
    Code:
    <?xml version="1.0" encoding="utf-8" ?>
    <plugins>
    	<plugin path="d:\...\MyApp\PlugIns" name="Add" assembly="Add.dll" />
    	<plugin path="d:\...\MyApp\PlugIns" name="Subtract" assembly="Subtract.dll" />
    </plugins>
    Für die Verwaltung auf Client-Seite wird ein PlugIn-Manager verwendet:
    [highlight=c#]
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Runtime.Remoting;
    using System.Xml.Linq;
    using gfoidl.Beispiel.PlugIn.PlugInDefinition;

    namespace gfoidl.Beispiel.PlugIn.PlugInExample
    {
    public class PlugInManager
    {
    private Dictionary<string, string> _plugInRegistry =
    new Dictionary<string, string>();
    //---------------------------------------------------------------------
    public PlugInManager(string registryPath)
    {
    LoadRegisteredPlugIns(registryPath);
    }
    //---------------------------------------------------------------------
    private void LoadRegisteredPlugIns(string registryPath)
    {
    XDocument xml = XDocument.Load(registryPath);
    var plugIns =
    from plugIn in xml.Descendants("plugin")
    select new
    {
    File = Path.Combine(
    plugIn.Attribute("path").Value,
    plugIn.Attribute("assembly").Value),
    Name = plugIn.Attribute("name").Value
    };

    foreach (var plugIn in plugIns)
    _plugInRegistry.Add(plugIn.Name, plugIn.File);
    }
    //---------------------------------------------------------------------
    public IPlugInDefinition CreateObject(string plugInName)
    {
    string fileName = _plugInRegistry[plugInName];
    ObjectHandle obj = Activator.CreateInstanceFrom(
    fileName,
    plugInName);

    return (IPlugInDefinition)obj.Unwrap();
    }
    //---------------------------------------------------------------------
    public List<string> AvailablePlugIns()
    {
    return _plugInRegistry.Keys.ToList<string>();
    }
    }
    }
    [/highlight]

    Und zum Schluss noch der Client:
    [highlight=c#]
    using System;
    using gfoidl.Beispiel.PlugIn.PlugInDefinition;

    namespace gfoidl.Beispiel.PlugIn.PlugInExample
    {
    class Program
    {
    static void Main(string[] args)
    {
    Console.Write("1. Zahl ->\t");
    double number1 = double.Parse(Console.ReadLine());
    Console.Write("2. Zahl ->\t");
    double number2 = double.Parse(Console.ReadLine());

    PlugInManager plugInManager = new PlugInManager("plugins.xml");
    Console.WriteLine("\nWähle ein PlugIn:");
    int i = 0;
    foreach (string plugInName in plugInManager.AvailablePlugIns())
    Console.WriteLine(" {0}\t{1}", i++, plugInName);
    i = int.Parse(Console.ReadLine());

    // Fabrik-Methode:
    IPlugInDefinition plugIn =
    plugInManager.CreateObject(plugInManager.Available PlugIns()[i]);

    double result = plugIn.Calculate(number1, number2);
    Console.WriteLine("\nDas Ergebnis ist: {0}", result);

    Console.ReadKey();
    }
    }
    }
    [/highlight]
    Wie zu sehen ist kann der Client auf die PlugIns stark typisiert zugreifen da alle PlugIns die Schnittstelle implementieren.

    Wird wie von dir gewünscht ein PlugIn von einem Server (also vom Internet) nachinstalliert braucht dieses PlugIn nur in die Liste eingetragen werden - das Einfügen in eine XML-Datei ist trivial - und dem Anwender zur Auswahl angeboten werden.


    Hoffe das hilft dir weiter.

    mfG Gü
    "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

    Comment


    • #3
      Vielen Dank für deine schnelle Antwort.
      Ich werde es heute oder morgen ausprobieren.
      Aber das ist genau das, was ich wollte!
      Werde auf jeden Fall berichten was aus meinem Projekt geworden ist ...

      Comment


      • #4
        Hallo,

        ich habe gerade das Beispiel ausprobiert.
        Ich kann es immerhin schon mal starten.
        Aber beim Ausführen bekomme ich noch den folgenden Fehler:

        Der Typ "Add" in der Assembly "PluginAdd", Version 1.0.0.0, Culture=neutral, PublicKeyToken=null" konnte nicht geladen werden.

        Ich habe aus dem Plugin "Add", "Subtract" und der Schnittstelle jeweils ein extra Klassenprojekt gemacht und als DLL kompiliert. Anschließend habe ich ein Windows Forms-Projekt angelegt, in dem sich die plugins.xml Datei, sowie der PluginManager befindet.
        Ist das so richtig?
        Anbei habe ich mein komplettes Projekt hochgeladen ...
        Attached Files
        Zuletzt editiert von seversmeier; 04.08.2009, 15:26.

        Comment


        • #5
          Ich habe aus dem Plugin "Add", "Subtract" und der Schnittstelle jeweils ein extra Klassenprojekt gemacht und als DLL kompiliert. Anschließend habe ich ein Windows Forms-Projekt angelegt, in dem sich die plugins.xml Datei, sowie der PluginManager befindet.
          Ist das so richtig?
          Genau so ist.

          Es gibt:
          • IPuginDefinition.dll
          • Add.dll
          • Subtract.dll
          • Anwendung.dll

          Und wie du richtig erkannt hast gehört der PlugInManager zur Anwendung.

          Der Typ "Add" in der Assembly "PluginAdd", Version 1.0.0.0, Culture=neutral, PublicKeyToken=null" konnte nicht geladen werden.
          Das heißt es wurde die Assembly nicht gefunden.
          Ist die Pfadangabe in der XML korrekt? (der Wert soll auf den tatsächlichen Pfad der DLL zeigen).


          mfG Gü
          "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

          Comment


          • #6
            Ja. Die Datei wird gefunden.
            Den Fehler hatte ich als erstes ...
            Ich habe oben noch einmal mein komplettes Projekt hochgeladen.

            Comment


            • #7
              Ok, hab mir dein Projekt angeschaut.

              Das Problem ist folglich dass Reflektion (das wird hier zum dynamischen Laden verwenden) immer den kompletten Bezeichner braucht um etwas zu finden.

              Dieser Bezeichner setzt sich zusammen aus Namespace + Typbezeichnung.

              In der XML ist nur die Typbezeichnung eingetragen und diese wird zum Findes des Typs in der Assembly verwendet. Im meinem Beispiel oben ist der Namespace nicht angegeben und daher funktioniert es. Bei deinem Projekt (die Console.WriteLines kannst du übrigens entfernen, die sind bei mir nur deshalb da weil ich mit einem Konsoleprogramm getestet habe) sind Namespaces angegeben und deshalb wird der Typ nicht gefunden.

              2 Möglichkeiten:
              • Namespaces entfernen (nicht empfohlen)
              • Namespace + Typname in der XML eintragen (empfohlen) -> Achtung: auch im Aufruf dies berücksichtigen.



              mfG Gü

              PS: Wenn es funktioniert kann ich dir noch einen Tipp geben wie die Leistung noch verbessert werden kann. Aber erstmals muss das gehen.
              "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

              Comment


              • #8
                Ich habe es jetzt noch einmal mit dem NameSpace versucht.
                Der Eintrag in der plugins.xml sieht nun wie folgt aus:

                <plugin path="c:\PlugIns" name="PlugInDefinition.Add" assembly="PluginAdd.dll" />

                Der Fehler tritt aber nach wie vor an der gleichen Stelle auf!
                Das habe ich mir echt einfacher vorgestellt ... :-(

                Comment


                • #9
                  path="c:\PlugIns"
                  Sind wirklich alle Assemblies (Add.dll, Subtrakt.dll) in diesem Pfad?
                  Laut deinem angehängten Projekt ist das nicht der Fall. Pass den Pfad so an dass er zu den tatsächlichen Pfaden zeigt - sonst kann nichts gefunden werden.


                  mfG Gü
                  "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                  Comment


                  • #10
                    Jetzt hat es funktioniert!
                    Ich hatte tatsächlich ein Fehler in dem Pfad ...
                    Werde mir jetzt noch einen kleines PlugIn schreiben welches regelmäßig in einer Datenbank nach einer aktuelleren Version schaut und sich dann diese DLL lokal kopiert, die XML-Datei anpasst und anschließend das Programm neu startet.
                    Oder schreibe ich dafür lieber ein seperates Prog, welches ich dann als Dienst laufen lassen kann?

                    Sorry ... aber ich habe mit diesem Thema noch nicht so viel Erfahrung!

                    Comment


                    • #11
                      Ich hatte tatsächlich ein Fehler in dem Pfad ...
                      Wusste ich doch

                      Mal die Definition eines PlugIns -> http://de.wikipedia.org/wiki/Plug-in

                      Werde mir jetzt noch einen kleines PlugIn schreiben welches regelmäßig in einer Datenbank nach einer aktuelleren Version schaut und sich dann diese DLL lokal kopiert, die XML-Datei anpasst und anschließend das Programm neu startet.
                      PlugIn soll PlugIn im Sinne einer Erweiterung sein. Für das Updaten ist kein PlugIn nötig - das ist Aufgabe der Anwendung (oder des Benutzer und somit wieder der Anwendung denn der Benutzer fürht Code dieser Anwendung aus).

                      Ich würde dabei so vorgehen (nur spontane Überlegung - um/eingesetzt hab ich so etwas auch noch nicht):

                      Zuerst muss geklärt werden wo Aktualisierungen und neue PlugIns angeboten werden. Dies kann eine Datenbank sein wie von dir angedacht oder verallgemeinert ein "Service" der in Form eines Webservices, etc. umgesetzt werden kann. Aber das wäre eine eigene Diskussion wert.

                      Danach muss geklärt werden wann und wie oft auf Aktualisierungen geprüft werden soll. Möglich wären
                      • Prüfung bei Programmstart
                      • periodische Prüfung (mit Timer, etc.)
                      • ...

                      Es kommt bei dieser Wahl darauf an wie oft neue Versionen von PlugIns veröffentlicht werden. In der Regel kann somit eine periodische Prüfung mit einem Timer entfallen denn ich kann mir nicht vorstellen dass jemand so fleißig PlugIns schreibt (und testet -> ganzer Softwareentwicklungzyklus muss bestanden werden) dass alle paar Minuten auf Aktualisierungen geprüft werden muss.
                      Deshalb würde ich beim Programmstart prüfen. Da dies für den Benutzer als Startverzögerung empfunden wird und somit als lästig und langsam gilt lagere ich das in einen Thread aus damit die Anwendung und die Aktualisierung parallel stattfinden können und der Benutzer keine Verzögerung merkt (der minimale Overhead durch die Verwendung des Threads fällt nicht ins Gewicht).

                      Während der Aktualisierung muss die XML-Datei bearbeitet werden und dort werden die neuen PlugIns (falls diese automatisch installiert werden sollen) und die Updates zu vorhanden PlugIns eingetragen die außerdem in den Ordner gespeichert werden den die Anwendung als PlugIn-Speicherort verwendet. Beim Updaten von PlugIns ist darauf zu achten dass ein aktuell verwendetes PlugIn nicht überschrieben werden kann. Hierzu gibt es wiederum verschiedenste Möglichkeiten dies umzusetzten. Auch das wäre wiederum einen eigene Diskussion wert (ich würde Shadow Copying verwenden).

                      Wie du siehst ist das Vorgehen mit einer Menge Entscheidungen verbunden die im Vorfeld geklärt werden sollten bevor eine konkrete Umsetzung getätigt wird.

                      Oder schreibe ich dafür lieber ein seperates Prog, welches ich dann als Dienst laufen lassen kann?
                      Als Dienst würde ich generell fast nichts laufen lassen. Siehe hierzu: //TODONT: Use a Windows Service just to run a scheduled process
                      Ein separates Programm als Updater zu verwenden macht keinen Sinn wenn "nur" PlugIns aktualisiert werden sollen. Dies wäre angebracht wenn die Anwendung selber aktualiert werden soll. Aber selbst in diesem Fall ist die Verwendung mit Shadow Copying (meiner Meinung nach) einfacher.


                      Hast du ein konkretes Problem oder willst du nur probieren?

                      mfG Gü
                      "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                      Comment


                      • #12
                        Bis jetzt bin ich nur am probieren.
                        Aber in Zukunft steht evtl. ein Programm an, welches auf mehreren Clients laufen soll.
                        Aus diesem Grund bin ich jetzt schon mal etwas am testen.
                        Der manuelle Aufwand bei Updates etc. soll ja so gering wie möglich gehalten werden.
                        Grundsätzlich kann ich doch auch komplette "Forms Anwendungen" als DLL hinterlegen und als weiteres Plugin einbinden. Oder?
                        So dass ich die Hauptanwendung nur als Container für weitere Applikationen verwende.
                        Von einer Browseranwendung habe ich aus Gründen der Performance und der zu bearbeitenden Datenmengen Abstand genommen ...
                        Vielen, vielen Dank schon mal!

                        Comment


                        • #13
                          Der manuelle Aufwand bei Updates etc. soll ja so gering wie möglich gehalten werden.
                          Wenn du mit manuell eine Benutzerinteraktion meinst: Es kann alles vollautomatisch passieren. Dabei ist aber darauf zu achten dass die Kunden dies auch akzeptieren. Aber es gibt genügend Möglcihkeiten ein passendes Modell zu implementieren.

                          Grundsätzlich kann ich doch auch komplette "Forms Anwendungen" als DLL hinterlegen und als weiteres Plugin einbinden. Oder?
                          Ja ist ohne Probleme möglich. Dabei muss allerdings ein "Host" existieren von dem aus die "Forms Anwendungen" verwaltete werden (starten, updaten, etc.).

                          Von einer Browseranwendung habe ich aus Gründen der Performance und der zu bearbeitenden Datenmengen Abstand genommen ...
                          Es gibt für Browser- und WinForms-Anwendung jeweils Vor- und Nachteile. Darauf will ich aber nicht eingehen. Ich bevorzuge wo es geht auch WinForms-Anwendungen. Diese werden aber nach MVP umgesetzt so dass im Nachhinein (ein anderer) leicht eine Browseranwendung daraus machen kann. Dabei geht es ja nur um die Darstellungsschicht.


                          mfG Gü
                          "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                          Comment


                          • #14
                            Vielen Dank erstmal für alles!
                            Ich werde mal weiter mit der Technologie rumspielen.
                            Kann ich mich bei auftretenden Fragen evtl. noch einmal bei dir melden?
                            Mein Ergebnis wie ich es lösen werde, werde ich auf alle Fälle hier posten ...
                            Du sagtest noch etwas von Performanceoptimierung. Macht das bei den PlugIn's so viel aus?

                            Comment


                            • #15
                              Kann ich mich bei auftretenden Fragen evtl. noch einmal bei dir melden?
                              Ja, sicher. Dazu ist das Forum ja da. Wenn ich die Frage bemerke und Zeit/Interesse habe die Frage zu beantworten werde ich dies auch tun. Vor allem dann wenn etwas weitergeht - wie es hier der Fall ist.

                              Du sagtest noch etwas von Performanceoptimierung. Macht das bei den PlugIn's so viel aus?
                              Den Begriff "Performanceoptimierung" habe ich unglücklich gewählt (liegt wahrscheinlich daran dass ich gerade einne Algorithmus optimiere) und sollte besser mit Eleganz ersetzt werden. Was meine ich aber damit?

                              Im PlugInManager wird jedesmal wenn auf ein PlugIn zugegriffen werden soll dieses mit CreateObject erstellt. Dabei wird die Assembly geladen und der Typ erstellt. Je nachdem wie oft die Anwendung ein PlugIn lädt kann dieses Verhalten positiv beeinflusst werden - sowohl in Bezg auf Laufzeit als auch von der Eleganz des Code (wobei letzteres Geschmackssache ist).

                              Wird das PlugIn in der Anwendung nur einmal geladen trifft dies nicht zu.

                              In einem Programm wie deinem Beispiel wo für jede Berechnung das PlugIn geladen werden muss wäre es doch sinnvoll das PlugIn nur einmal zu laden und dann zu speichern. Somit muss nicht jedesmal der ganze Overhead ausgeführt werden. Die Umsetzung dazu ist relativ einfach. Dazu muss in der Methode CreateObject nur geprüft werden ob das PlugIn im internen "Cache" vorhanden ist und wenn ja wird es zurückgegeben. Falls es nicht vorhanden ist wird es geladen und dem Cache hinzugefügt und dann zurückgegeben.

                              Die Klasse PlugInManager wird dabei wie folgt geändert:
                              [highlight=c#]
                              public class PlugInManager
                              {
                              ...
                              private Dictionary<string, IPlugInDefinition> _plugInChache =
                              new Dictionary<string, IPlugInDefinition>();
                              ...
                              //---------------------------------------------------------------------
                              public IPlugInDefinition CreateObject(string plugInName)
                              {
                              // Prüfen ob das PlugIn im Cache vorhanden ist:
                              if (!_plugInChache.ContainsKey(plugInName))
                              {
                              // PlugIn nicht im Cache -> laden:
                              string fileName = _plugInRegistry[plugInName];
                              ObjectHandle obj = Activator.CreateInstanceFrom(
                              fileName,
                              plugInName);

                              // PlugIn dem Cache hinzufügen:
                              _plugInChache[plugInName] = obj.Unwrap() as IPlugInDefinition;
                              }

                              // PlugIn ist jetzt im Cache und kann zurückgegeben werden:
                              return _plugInChache[plugInName];
                              }
                              //---------------------------------------------------------------------
                              ...
                              }
                              [/highlight]

                              Anzumerken sei dass keine Fehlerbehandlungen eingebaut sind. Auf diese wurde aus Gründen der Übersichtlichtkeit verzichtet und obliegt dir dies einzubauen.


                              mfG Gü

                              PS: Diese Technik wird auch als Identity-Map (Cache) bezeichnet und kann auch für andere Anwendungsfälle von Interesse sein.
                              "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                              Comment

                              Working...
                              X