myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Entwicklung » Rund um die Programmierung » Wie Dependency Injection registrieren bei Plugins, die erst zur Laufzeit bekannt sind?
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Wie Dependency Injection registrieren bei Plugins, die erst zur Laufzeit bekannt sind?

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
-val-
myCSharp.de-Mitglied

Dabei seit: 20.11.2019
Beiträge: 3


-val- ist offline

Wie Dependency Injection registrieren bei Plugins, die erst zur Laufzeit bekannt sind?

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo,

ich beschäftige mich mit Dependency Injection und habe da ein Problem...
Es wird empfohlen alle Abhängikgeiten in einer Root-Klasse zu registrieren. Wie soll das z.B. bei Verwendung von Plugins funktionieren, da ich erst zur Laufzeit weiß, was geladen wird.
Zum Beispiel:

AppHost.exe (WPF Anwendung)
PluginA.dll
PluginB.dll
PluginC.dll

AppHost.exe => Läd PluginA.dll => PluginA.dll kann je nach Benutzerauswahl PluginB.dll oder PluginC.dll laden. In PluginB.dll werden nun weitere Abhänigkeiten benötigt oder erzeugt usw.
Wie kriege ich nun den ServiceProvider nach PluginB bzw. PluginC? Ich könnte den ServiceProvider nun über den Konstruktor oder einer Initialisierungs-Methode durch reichen, aber dann kann ich gleich einen Singleton verwenden. Desweiteren wird dieser Weg auch nicht empfohlen. Alternative wäre der ServiceLocater. Das ist wiederum ein Singleton.

Hat jemand in dieser Thematik Erfahrung und kann empfehlen, wie man dieses Problem am besten angeht? Danke.
20.11.2019 15:41 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
MrSparkle MrSparkle ist männlich
myCSharp.de-Team

avatar-2159.gif


Dabei seit: 16.05.2006
Beiträge: 5.398
Herkunft: Leipzig


MrSparkle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ich würde es wahrscheinlich so umsetzen, daß jedes Plugin eine Methode hat, die die jeweiligen Abhängigkeiten zurückgibt. Der PluginManager (oder wie man das auch nennt) sammelt die Abhängigkeiten und lädt sie dann in der korrekten Reihenfolge.
20.11.2019 15:44 Beiträge des Benutzers | zu Buddylist hinzufügen
-val-
myCSharp.de-Mitglied

Dabei seit: 20.11.2019
Beiträge: 3

Themenstarter Thema begonnen von -val-

-val- ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Müsste der PluginManager dafür nicht alle Plugins kennen? Bei z.B. 100 Plugins müssten anschließend unnötig alle Plugins initialisiert werden. Was ist mit den Plugins welche später zur Laufzeit hinzugeladen werden?
20.11.2019 16:05 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
MrSparkle MrSparkle ist männlich
myCSharp.de-Team

avatar-2159.gif


Dabei seit: 16.05.2006
Beiträge: 5.398
Herkunft: Leipzig


MrSparkle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Nein, der PluginManager bekommt die Abhängigkeiten mitgeteilt, wenn er ein Plugin lädt.

Die Abhängigkeiten kann das Plugin beim Laden mitteilen, oder z.B. auch in einer Konfigurationsdatei hinterlegen, so daß sie schon vor dem eigentlichen Laden bekannt sind.
20.11.2019 16:51 Beiträge des Benutzers | zu Buddylist hinzufügen
Palladin007 Palladin007 ist männlich
myCSharp.de-Mitglied

avatar-4140.png


Dabei seit: 03.02.2012
Beiträge: 1.266
Entwicklungsumgebung: Visual Studio 2019
Herkunft: NRW


Palladin007 ist online

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Du gibst beim Start nicht die Instanzen der Abhängigkeiten an, sondern nur die Info, wie man die Abhängigkeit instanziiert ;)
Natürlich müssen dafür alle DLLs geladen werden, aber das sollte im Zweifel kein Problem darstellen. Wenn Du das trotzdem nicht möchtest, kannst Du auch eine Factory nutzen, die dann die DLL sowie die gesuchte Implementierung zur Laufzeit lädt und instanziiert.

Ausführlicher beschäftigt habe ich mich bisher mit folgenden DI-Frameworks:
- Microsoft.Extensions.DependencyInjection
- Autofac
- DryIoc

Alle drei Frameworks können Abhängigkeiten entsprechend verwalten und instanziieren (richtig angewandt) auch nur die, die am Ende wirklich abgefragt werden.

Dabei ist natürlich wichtig, dass alle möglichen Abhängigkeiten zum Beginn der Anwendung entsprechend registriert werden. Dabei wird nichts instanziiert, es wird nur der Typ mitgeteilt.
Das Framework merkt sich dann jeden Typ, dessen Abhängigkeiten und noch zusätzliche Dinge, z.B. ob und wie lange eine Instanz vorgehalten werden soll.
Wird ein Typ angefragt, löst es diese Abhängigkeiten auf und instanziiert entsprechend der Einstellungen alles andere.

Einem Plugin-Loader würde ich dann die Möglichkeit geben, eigene Typen zu registrieren und zum Schluss registriert es dann das eigentliche Plugin.

Am Einfachsten zum Lernen dürfte die Variante von Microsoft sein, Autofac ist aber ebenfalls recht einfach zu lernen und kann etwas mehr. Bei DryIoc ist mMn. der Einstieg detlich schwieriger, allerdings kann es sehr viel, bleibt dabei aber trotzdem sehr performant.
Die beiden sind aber auch voll-kompatibel zu der Variante von Microsoft, Du kannst also überall die Microsoft-Variante nutzen, nur beim initialisieren nutzt Du dann die Implementierung des jeweiligen Frameworks.
21.11.2019 17:11 Beiträge des Benutzers | zu Buddylist hinzufügen
-val-
myCSharp.de-Mitglied

Dabei seit: 20.11.2019
Beiträge: 3

Themenstarter Thema begonnen von -val-

-val- ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke für die Hinweise, ich konnte nun das Problem weiter abstrahieren...
Wie kriegt man am sinnvollsten den PluginProvider, PluginAssemblyProvider usw. in einem anderen Plugin bekannt?

Wäre das eine Möglichkeit? Problem nur, es passiert nicht "automatisiert".

C#-Code:
// Übergabe der Abhängigkeit
// mainPlugin.Initialize(serviceProvider.GetService<PluginProvider>());

C#-Code:
    public interface IPlugin : IDisposable
    {
        event EventHandler<string> Disposed;
        IPluginInfo Info { get; }
        T GetContract<T>() where T : class;

        void Initialize(object initializeObject);
    }

C#-Code:
public interface ICommandPlugin : IPlugin
    {
        IPluginCommand Command { get; }
    }

C#-Code:
// Startup.dll
public class Startup : CommandPlugin, ICommandStart
    {
        IPlugin mainPlugin;

        public override void Initialize(object initializeObject)
        {
            var serviceCollection = new ServiceCollection();

            serviceCollection.AddTransient((s) => new PhysicalFileProvider("\\Plugins", "*.dll", true, true));
            serviceCollection.AddTransient<IPluginProvider<PluginAssembly>, PluginAssemblyProvider>();
            serviceCollection.AddTransient<IPluginProvider<IPlugin>, PluginProvider>();

            var serviceProvider = serviceCollection.BuildServiceProvider();

            mainPlugin = serviceProvider.GetService<PluginProvider>().CreatePlugin<ICommandPlugin>("MP01"); // MainPlugin

            // Übergabe der Abhängigkeit
            // mainPlugin.Initialize(serviceProvider.GetService<PluginProvider>());            
            mainPlugin.Command.Execute<ICommandStart>(this);
        }

        public void Start(IPlugin sender, params object[] parameters)
        {
            mainPlugin.Command.Execute<ICommandStart>(this);
        }
    }
}

C#-Code:
// MainPlugin.dll
public class MainPlugin: CommandPlugin, ICommandStart
    {
       PluginProvider pluginProvider;

       public override void Initialize(object initializeObject)
       {
          if (initializeObject is PluginProvider pluginProvider)
          {
               // this.pluginProvider = pluginProvider;
          }
       }

       public void Start(IPlugin sender, params object[] parameters)
       {
            //
       }
    }
}

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von -val- am 24.11.2019 23:32.

24.11.2019 23:29 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 13.938
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Es ist ungünstig, dass die Plugins eine direkte Abhängigkeit zur Implementierung haben.
Es reicht doch, dass die Plugins das Interface kennen; also zB IPluginProvider

Wenn man sich hier an die Namespace Regeln von .NET hält, dann kann man das auch super einfach über ein entsprechendes Mono Repo in Form von mehreren Projekten abdecken, sodass es keine Circular Dependencies gibt.
Man kann sich hier einfach den Aufbau abschauen, wie es die Microsoft.Extensions.* NuGets machen.


So wie ich das sehe leben Deine Plugins zum einen eingbettet aus der Assembly, zum anderen aus Dateien - und Du hast dafür verschiedene Provider.
Das macht Dein DI Design unnötig komplex, soweit ich das sehe; und entspricht auch nicht den Empfehlungen von DI (zumindest das Microsoft DI).
Hab lieber ein Provider, der aber aus verschiedenen Quellen laden kann, wie es auch gängige Bibliotheken tun (ASP.NET, MediatR, EFCore....).

Bei Dir könnte das so aussehen:

C#-Code:
serviceCollection.AddPluginContext<PluginContext>>(o=>
{
   o.UsePluginsFromAssembly();
   o.UsePluginsFromAssembly(typeof(MyCustomType).Assembly);
   o.UsePluginsFromPhysicalDirectory("\\Plugins", "*.dll", true, true);
});

Dazu brauchst Du eine Options Klasse, die Du dann als Action verwenden kannst:

C#-Code:
        public static IServiceCollection AddPluginContext<TContext>(
            this IServiceCollection services, Action<PluginContextOptions> options)
            where TContext : class, IPluginContext
        {
            PluginContextOptions contextOptions = new PluginContextOptions();
            options.Invoke(contextOptions);

            services.Configure<PluginContextOptions>(contextOptions);
            services.AddScoped<IPluginContext, TContext>();

            // ....
            return services;
        }
25.11.2019 00:52 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 7 Monate.
Der letzte Beitrag ist älter als 7 Monate.
Antwort erstellen


© Copyright 2003-2020 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 04.07.2020 20:21