Laden...

.net 6 Plugin Architektur

Erstellt von GrilleGsutav vor einem Jahr Letzter Beitrag vor einem Jahr 1.230 Views
G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr
.net 6 Plugin Architektur

Ich arbeite daran meine .net 6 RESTApi durch Plugins zu erweitern.
Von der Basisanwendung sollen zum Plugin keine statischen Referenzen bestehen.
Ich habe Probleme meine PluginBase.cs Klasse zu instanziieren.
Die PluginBase.cs wiird in jedem Plugin sein.


  public class PluginBase : IPluginBase
  {
    public IConfiguration _configuration { get; }
    public IServiceCollection _services;

    public PluginBase() { }

    public PluginBase(IConfiguration configuration, IServiceCollection services)
    {
      _configuration = configuration;
      _services = services;
    }

    public string Name { get => "PvSystem"; }


    public int Initialize()
    {
      //services.AddDbContext<RepositoryPvContext>(opt => opt.UseMySql(configuration.GetConnectionString("DefaultConnection"), new MySqlServerVersion(new System.Version(10, 5, 8)), b => b.MigrationsAssembly("Entities")));
      //services.BuildServiceProvider().GetService<RepositoryPvContext>().Database.Migrate();
      Console.WriteLine("PvSystem loaded...");
      return 0;
    }
  }

Dies ist mein Service in dem die Plugin dlls geladen werden sollen:


    private void LoadPlugins(List<string> pluginPaths)
    {
      IEnumerable<IPluginBase> plugins = pluginPaths.SelectMany(pluginPath =>
      {
        Assembly pluginAssembly = LoadPlugin(pluginPath);
        AddApplicationParts(pluginAssembly);

        return CreatePlugin(pluginAssembly);
      }).ToList();

      if (plugins != null || plugins.Count() != 0)
      {
        foreach (IPluginBase plugin in plugins)
        {
          plugin.Initialize();
        }
      }
      Console.WriteLine();
    }

    private Assembly LoadPlugin(string relativePath)
    {
      Console.WriteLine($"Loading plugin from: {relativePath}");
      PluginLoadContext loadContext = new PluginLoadContext(relativePath);
      return loadContext.LoadFromAssemblyName((AssemblyName.GetAssemblyName(relativePath)));
    }

    private void AddApplicationParts(Assembly pluginAssembly)
    {
      var mvcBuilder = _services.AddMvc();
      var partFactory = ApplicationPartFactory.GetApplicationPartFactory(pluginAssembly);
      foreach (var part in partFactory.GetApplicationParts(pluginAssembly))
      {
        mvcBuilder.PartManager.ApplicationParts.Add(part);
      }

      var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(pluginAssembly, throwOnError: true);
      foreach (var assembly in relatedAssemblies)
      {
        partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
        foreach (var part in partFactory.GetApplicationParts(assembly))
        {
          mvcBuilder.PartManager.ApplicationParts.Add(part);
        }
      }
    }

    private IEnumerable<IPluginBase> CreatePlugin(Assembly assembly)
    {
      int count = 0;
      foreach (Type type in assembly.GetTypes())
      {
        if (typeof(IPluginBase).IsAssignableFrom(type))
        {
          IPluginBase result = Activator.CreateInstance(type, true, new object[] { _configuration, _services}) as IPluginBase;
          if (result != null)
          {
            count++;
            yield return result;
          }
        }
      }

      if (count == 0)
      {
        string availableTypes = string.Join(",", assembly.GetTypes().Select(t => t.FullName));
        throw new ApplicationException(
            $"Can't find any type which implements IPluginBase in {assembly} from {assembly.Location}.\n" +
            $"Available types: {availableTypes}");
      }
    }

Bei Zeile 55 kriege ich folgende Fehlermeldung, während der Ausführung.

Fehlermeldung:
System.MissingMethodException
HResult=0x80131513
Nachricht = Constructor on type 'PvSystemPlugin.PluginBase' not found.
Quelle = System.Private.CoreLib

So weit ich das verstehe fehlt der passende Constructor in der PluginBase Klasse.
Meiner Meinung nach ist der aber vorhanden.
Mit parameterlosem Constructor und Activator.CreateInstance(type); funktioniert das einwadfrei.

Hat einer eine Lösung für mein Problem?

4.942 Beiträge seit 2008
vor einem Jahr

Hallo,

ich vermute, du verwendest die falsche Überladung, denn deinen Aufruf gibt es so nicht direkt: Activator.CreateInstance (es wird bei dir wohl die (type, params object[]) - Überladung benutzt mit true als ersten Konstruktor-Parameter).

G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr

Also wenn ich das ganze so mache:


IPluginBase result = Activator.CreateInstance(type, new object[] { _configuration, _services}) as IPluginBase;

Was dann ja Überladung:


public static object? CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, params object?[]? args);

wäre, geht das auch nicht.
Kann zur Laufzeit aus der Fehlermeldung schließen was für einen Constructor der grade erwartet?

4.942 Beiträge seit 2008
vor einem Jahr

Schau dir mal im Debugger mit Type.GetConstructors() die Parameter an, also GetParameters().

PS: Du kannst auch explizit nach einer bestimmten Überladung suchen: Type.GetConstructor.

G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr

PluginBase.cs klasse erwartet wohl:


Microsoft.Extensions.Configuration.IConfiguration

Ich komme aber mit:


Microsoft.Extensions.Configuration.ConfigurationRoot

In meiner RESTApi und in der PluginBae.cs

Importe ich:


using Microsoft.Extensions.Configuration;

um auf IConfiguration zuzugreifen.
Da eine ist ein .net 6 API Projekt
Das andere ein .net 6 Klassenbibliothek
Hat es vllt. ist IConfiguration in unterschiedlichen .nuget Packages vllt,. unterschiedlich?

4.942 Beiträge seit 2008
vor einem Jahr

Probiere es doch ersteinmal mit ein oder zwei Standarddatentypen (string, int, ...) oder nur jeweils einer deiner beiden Interface-Typen als Parameter.
Oder aber du kopierst doch mal die PluginBase-Klasse in dein Plugin-Projekt und rufst dafür dann Activator.CreateInstance auf.

Bei den Schnittstellen muß natürlich dasselbe interface benutzt werden, aber das sollte bei den MS-Klassen ja sichergestellt sein (Microsoft.Extensions.Configuration.ConfigurationRoot implementiert ja (indirekt über IConfigurationRoot) die Schnittstelle IConfiguration).
Und welchen Typ verwendest du für IServiceCollection?

G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr

Mit einem Constructor der so aussieht:


    public PluginBase(int test)
    {
    }

und dem aufruf:


IPluginBase result = Activator.CreateInstance(type, new object[] { 1337 }) as IPluginBase;

Geht es.
Ich verwende in beiden Projekten


using Microsoft.Extensions.DependencyInjection;

Für die IServiceCollection.

In der Variable _services.Count() sind 350, kann es sein das er jetzt eine Constructor mit all diesen 350 Types erwartet?

4.942 Beiträge seit 2008
vor einem Jahr

Nein, das nicht.
Zeige mal die Deklaration der von dir verwendeten konkreten _services-Klasse (also nur die ersten Zeilen, d.h. public class X : ...).

Verwendest du denn dieselben Assemblies (bzw. NuGet-Pakete) für diese Schnittstellen in deinen beiden Projekten?

G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr

Plugin Projektdatei:


<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>net5.0</TargetFramework>
		<EnableDynamicLoading>true</EnableDynamicLoading>
	</PropertyGroup>

	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
	  <DebugType>embedded</DebugType>
	</PropertyGroup>

	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
	  <DebugType>embedded</DebugType>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
		<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.17" />
		<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.17">
		  <PrivateAssets>all</PrivateAssets>
		  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
		</PackageReference>
		<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.17" />
		<PackageReference Include="NLog" Version="5.0.4" />
		<PackageReference Include="NLog.Extensions.Logging" Version="5.0.4" />
		<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.4" />
	</ItemGroup>

	<ItemGroup>
		<ProjectReference Include="..\..\Attributes\Attributes.csproj">
			<Private>false</Private>
			<ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
		<ProjectReference Include="..\..\Contracts\Contracts.csproj">
			<Private>false</Private>
			<ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
		<ProjectReference Include="..\..\Entities\Entities.csproj">
			<Private>false</Private>
			<ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
		<ProjectReference Include="..\..\Enums\Enums.csproj">
			<Private>false</Private>
			<ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
		<ProjectReference Include="..\..\Models\Models.csproj">
			<Private>false</Private>
			<ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
		<ProjectReference Include="..\..\Repository\Repository.csproj">
			<Private>false</Private>
			<ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
		<ProjectReference Include="..\..\Services\Services.csproj">
			<Private>false</Private>
			<ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
		<ProjectReference Include="..\PluginBase\PluginBase.csproj">
			<Private>false</Private>
			<ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
	</ItemGroup>
</Project>

PluginBase.cs:


using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using PluginBase;
using PvSystemPlugin.Entities.Context;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PvSystemPlugin
{
  public class PluginBase : IPluginBase
  {
    public IConfiguration _configuration { get; }
    public IServiceCollection _services;

    public PluginBase(IServiceCollection services)
    {
      _services = services;
    }

    public PluginBase(IConfiguration configuration, IServiceCollection services)
    {
      _configuration = configuration;
      _services = services;
    }

    public string Name { get => "PvSystem"; }


    public int Initialize()
    {
      //services.AddDbContext<RepositoryPvContext>(opt => opt.UseMySql(configuration.GetConnectionString("DefaultConnection"), new MySqlServerVersion(new System.Version(10, 5, 8)), b => b.MigrationsAssembly("Entities")));
      //services.BuildServiceProvider().GetService<RepositoryPvContext>().Database.Migrate();
      Console.WriteLine("PvSystem loaded...");
      return 0;
    }
  }
}


Basis Anwendung:


<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <GenerateDocumentationFile>True</GenerateDocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="AutoMapper" Version="10.1.1" />
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
    <PackageReference Include="Google.Apis.Auth" Version="1.50.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.3" />
    <PackageReference Include="Microsoft.AspNetCore.DataProtection.Extensions" Version="5.0.6" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.5" />
    <PackageReference Include="NLog" Version="5.0.4" />
    <PackageReference Include="NLog.Extensions.Logging" Version="5.0.4" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Attributes\Attributes.csproj" />
    <ProjectReference Include="..\Contracts\Contracts.csproj" />
    <ProjectReference Include="..\Entities\Entities.csproj" />
    <ProjectReference Include="..\Models\Models.csproj" />
    <ProjectReference Include="..\Plugins\PluginBase\PluginBase.csproj" />
    <ProjectReference Include="..\Repository\Repository.csproj" />
    <ProjectReference Include="..\Services\Services.csproj" />
  </ItemGroup>

  <ItemGroup>
    <Folder Include="Context\" />
    <Folder Include="Upload\Editor\Images\" />
  </ItemGroup>

</Project>

4.942 Beiträge seit 2008
vor einem Jahr

Dort sind aber nicht die beiden benötigten Referenzen zu sehen. Überprüfe mal, ob du in beiden Projekten die versionsgleichen NuGet-Pakete Microsoft.Extensions.Configuration/5.0.0 und Microsoft.Extensions.DependencyInjection/5.0.2 (das neueste für 5.0) verwendest.

Und noch mal die Frage: von welchem konkreten Typ ist die _services-Variable in deinem RestAPI (Sdk.Web) Projekt?

PS: Außerdem sehe ich gerade, daß dieses Thema ".net 6" heißt, du aber noch ".net 5" benutzt!?!

16.842 Beiträge seit 2008
vor einem Jahr

Wo ich es aufgrund der csproj so sehe... nur als Feedback.


  <ItemGroup>
    <ProjectReference Include="..\Attributes\Attributes.csproj" />
    <ProjectReference Include="..\Contracts\Contracts.csproj" />
    <ProjectReference Include="..\Entities\Entities.csproj" />
    <ProjectReference Include="..\Models\Models.csproj" />
    <ProjectReference Include="..\Plugins\PluginBase\PluginBase.csproj" />
    <ProjectReference Include="..\Repository\Repository.csproj" />
    <ProjectReference Include="..\Services\Services.csproj" />
  </ItemGroup>

so ein Aufbau ist ein relativ markantes Zeichen, dass die Software Architektur überdacht werden sollte 🙂
Plugin- oder Feature-Architekturen, wie sie auch ASP.NET Core bzw. allgemeine moderne Basen verfolgen, gehen eher den Domain Driven Design weg. Darauf baut auch die gesamte ASP.NET Core Architektur auf.
Du hast hier einen eher Legacy Aufbau aus den späten 90ern, Anfang 2000er die auf Basis von Infrastruktur statt Verantwortlichkeiten trennen.

Der Aufbau, den Du hast, fördert des weiteren Architekturfehler und Wartungsschwierigkeiten, weil Du zB Abhängigkeiten zwangsläufig mischen musst, was der Hauptgrund ist, dass man diese Basis nicht mehr verfolgt / verfolgen sollte. Dazu gehören vor allem Referenzfehler und Abhängigkeitsprobleme.

Nochmalerweise würde man das ungefähr so aufbauen:

MyPlatform.HttpApi
MyPlatform.Plugins.Abstractions
MyPlatform.Plugins.UsersPlugin <<< Domäne
MyPlatform.Plugins.UsersPlugin.HttpApi <<< Infrastructure
MyPlatform.Plugins.UsersPlugin.Authorization
MyPlatform.Plugins.OrderPlugin <<< Domäne
MyPlatform.Plugins.OrderPlugin.HttpApi <<< Infrastructure
MyPlatform.Plugins.ArticlesPlugin <<< Domäne
MyPlatform.Plugins.ArticlesPlugin.HttpApi <<< Infrastructure

Oder halt "Features" statt "Plugins", wenn man eine Feature Architektur verfolgt ohne Plugins
Eine echte Pluginfähigkeit ist in den meisten Webanwendungen gar nicht gewünscht (weil das technisch immer eine unsichere Infrastrukturbasis erfordert) obwohl so benannt.

Da eine ist ein .net 6 API Projekt
Das andere ein .net 6 Klassenbibliothek
Hat es vllt. ist IConfiguration in unterschiedlichen .nuget Packages vllt,. unterschiedlich?

Du hast, wie Th69 gesagt hat, .NET 5 und nicht .NET 6.
Ebenso kann es gut sein, dass es sich beschwert, dass die Signaturen der Abhängigkeiten, die geladen werden sollen, nicht passen, weil die NuGet Pakete sich unterscheiden.

IConfiguration und Co sind des weiteren nicht dazu gedacht, dass diese quer durch DLLs geschubst werden, sodass da "alle Plugins" zugreifen können.
Damit schaffst Du Dir auch eine Abhängigkeit, sodass Du die IConfiguration niemals wirst umstrukturieren können.

Es ist viel besser (auch im Sinne von Plugins) wenn Du eine Schnittstelle schaffst, in der das Plugin eine Klasse an den Host übergeben kann, und so Configurations geladen werden können. Dafür verwendet man i.d.R. den Configuration Binder, der für solche Anwendungsfälle gedacht ist. Das Plugin hat also eine Klasse, die die Konfiguration darstellt.
Damit wirst auch die Abhängigkeiten los und Du hast eine sicherere Basis, weil Du nicht die rohe Config mit allen Inhalten an Plugins preisgibst.

Die Grundidee ist ja, dass der Host nur die Schnittstelle kennt und auch die Plugins nur eine Schnittstelle kennen.
Wenn Du roh irgendwelche Sachen übergibst, wie eben die rohe Config, dann schaffst Du eine immense, kaum fixbare Abhängigkeit und untergräbst die Idee von Plugins.
Deine PluginRegistration (oder in meinem Sample PlatformRegistration) muss wirklich alles, alles alles alles abstrahieren, was Deine Plugins brauchen / nutzen dürfen.

Hier als Beispiel aus meiner Feature Architektur, wie das umgesetzt ist:


            // register parts
            partBuilder.Register(services, new TenantsFeaturePart(configuration.GetSection("TenantsFeature")));
            partBuilder.Register(services, new TodoPart(configuration.GetSection("TodoFeature")));

MyDemoPlatformPlatformRegistration.cs#L51

G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr

Entschuldigung dann sind das tatsächlich noch .net 5 Projekte sorry.
Angefangen habe ich mit meinem Projekt auf Basis des Repository Pattern.
Domian Driven Design habe keine Vorstellung wie ein Projekt dann konkret aufgebaut wird.
z.B. wie würde die Projektstruktur aussehen?

Ich habe alles in einzelne Projekte aufgeteilt:
Siehe Bilder: Screenshot 2022-09-08 184805.png, Screenshot 2022-09-08 185435.png

Wenn ich das richtig verstehe würde im Domain Driven Design es dann z.B so ausehen:
Bild: Screenshot 2022-09-08 190747.png

Hätte vllt. jemand von euch Lust sich auf Discord oder Teamspeak auszutauschen?

Für mich ist es immer sehr viel einfacher mich mit Code/Projektbeispielen in die Thematiken rein zu arbeiten.
Oder man schaut sich gemeinsam mein Projekt an.

Schon mal danke für die tolle Hilfe.

16.842 Beiträge seit 2008
vor einem Jahr

z.B. wie würde die Projektstruktur aussehen?

Hab Dir den Link auf mein Demo Projekt nicht umsonst gegeben 🙂
Nich nett, dass Du es offenbar nich ma paar Minuten angeschaut hast... 😦

..und ich hab Dir sogar den Aufbau mitten in meinen Beitrag gepostet??!?!?

Angefangen habe ich mit meinem Projekt auf Basis des Repository Pattern.

Der Repository Pattern hat einen spezifischen Use Case. Eine Anwendung besteht i.d.R. aus vielen verschiedenen Pattern; und jeder hat seine Berechtigung.

Domian Driven Design habe keine Vorstellung wie ein Projekt dann konkret aufgebaut wird.

Es war im Endeffekt lediglich ein Hinweis. Du musst diesem nicht nachgehen.
Aber es empfiehlt sich (sehr) sich in diese Thematik allein einzulesen und sich dessen vertraut zu machen, allein weil man quasi nicht mehr drum herum kommt.
Auch die aller meisten ASP.NET Tutorials nutzen DDD als Basis. Deine Basis is wirklich schon viele viele Jahre outdatet.
Wie gesagt: es war ein Hinweis.

Auch nur als Hinweis:

Hätte vllt. jemand von euch Lust sich auf Discord oder Teamspeak auszutauschen?

Für mich ist es immer sehr viel einfacher mich mit Code/Projektbeispielen in die Thematiken rein zu arbeiten.
Oder man schaut sich gemeinsam mein Projekt an.

Ein Forum ist bewusst ein asynchroner Weg der Kommunikation. Die Leute können antworten wenn sie wollen, und wann sie wollen.
Wenn Du jemanden suchst, der Dein Projekt über einen synchronen Weg aktiv begleitet, dann setz am besten einen Aufruf in der Jobbörse auf.

G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr

Habe dein Link gar nicht gesehen sorry. schaue es mir gleich an.
danke

G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr

Dein Projekt ist ja schon sehr komplex. Da werde ich ein bisschen brauchen das durch zu arbeiten.
Hier wäre mal meins: https://github.com/GrilleGustav/api
Mein Projekt ist zwar etwas was ich Privat auch nutzen möchte, aber es dienst hauptsächlich zum lernen der diversen Möglichkeiten ein Plugin System zu bauen.
Mir sind halt ein paar Dinge wichtig de mein Projekt können muss:

  • Es muss eine RESTApi-Schnittstelle haben.

  • Die Basis RESTApi muss folgendes beinhalten:

  • Die Möglichkeit Emails zu versenden.

  • Der Inhalt der Emails muss über Emailtemplates frei gestaltbar sein. Platzhalter im Email Inhalt müssen durch werte ersetzbar sein.

  • Plugins müssen sich eigene Emailtemplates anlegen können.

  • Es muss eine Benutzerverwaltung da sein

  • Es muss die Möglichkeit bestehen zur Laufzeit Plugins zu laden.

  • Von der Basisanwendung darf keine statische Referenz zu den Plugins bestehen. Also eine echte

Pluginfähigkeit

usw.

Die laufende Anwendung soll halt erstmal nur Basis Funktionalitäten beinhalten.
Sie stellt in dem Sinne nichts da. Erst die Plugins/Erweiterungen machen sie z.B. zu einer Spritverbrauchsapp oder um Smarthomegeräte zu verwalten.

Das heißt für mich ich muss jetzt für mich herausfinden wie ich die Controller, Repositorys, Services, Automapper, den Datenkontext... von denen die Basisanwendung nichts weiß, zur Laufzeit registrieren kann.

Es war im Endeffekt lediglich ein Hinweis. Du musst diesem nicht nachgehen

Natürlich werde ich dem Nachgehen und du hast ja vollkommen recht dies umzusetzen.
Es sind so viele Dinge man sich erstmal einarbeiten muss und jeder hat da ja auch seinen eigenen Programmierstiel.
Du willst gar nicht wissen auf was für Basis ich für die Arbeit Programmieren muss. 🙂
Nach dem was ich jetzt in deinem Projekt gesehen habe, gehen einige Dinge in meinem Projekt ja in eine ähnliche Richtung. Halt weit aus weniger Komplex.

16.842 Beiträge seit 2008
vor einem Jahr

Dein Projekt ist ja schon sehr komplex. Da werde ich ein bisschen brauchen das durch zu arbeiten.

Sie ist umfangreich, und sicherlich für Fortgeschrittene. Definitiv.
Auf der anderen Seite ist das eben kaum einfacher zu machen (für diese Zielgruppe).

Zu Deinen Anforderungen:
REST ist ein Paradigma, das feste Regeln für Daten-getriebene APIs bietet.
Was Du beschreibst sind Aktionen, die durch REST nicht abgedeckt sind.
https://dotnet.rest/docs/overview/what-is-rest/

Was Du willst ist eine HTTP API auf Basis von Json/XML - das hast Du auch programmiert.
Aber eine REST API macht was völlig anderes, als Du beschreibst. Nicht schlimm, ist halt einfach der falsche Begriff.

Das heißt für mich ich muss jetzt für mich herausfinden wie ich die Controller, Repositorys, Services, Automapper, den Datenkontext... von denen die Basisanwendung nichts weiß, zur Laufzeit registrieren kann.

Dann brauchst Du einen Registrator, der wirklich alles alles alles abstrahiert.
Ansonsten passt der Weg ja, macht man mit ASP.NET Parts und das Plugin laden haste ja auch so umgesetzt, wie es die Docs IIRC ich als Beispiel auch vorgeben.

Das einzige, was mir noch auffällt:


 IPluginBase result = Activator.CreateInstance(type, true, new object[] { _configuration, _services}) as IPluginBase;

Für das Erstellen von Instanzen unter der Beachtung von Dependency Injection gibt es ActivatorUtilities.CreateInstance Method.
Vielleicht hilft das ja.

G
GrilleGsutav Themenstarter:in
27 Beiträge seit 2017
vor einem Jahr

Also ich weiß jetzt warum ich z.B. die IServiceCollection im Plugin nivht benutzen kann. Die IServiceCollection stammt im Base Project und im Plugin aus unterschiedlichen namespaces und daher kann ich die im Plugin nicht benutzen.
Jetzt habe ich das gefunden:
https://github.com/natemcmaster/DotNetCorePlugins

Der nutzt etwas was er "sharedType" nennt, um zu gewährleisten, dass das Porojekt was die Plugins lädt und die Plugins die selben Types benutzen.

Wenn man sich das Beispiel DependencyInjection anschaut fällt mir aber was auf.
Wenn ich z.B. ein Service registrieren möchte z.b. <IMyservice, MyService> dann muss sich die IMyService.cs immer in einen Gemeinsamen Prokekt befinden, auf das die Basis Anwendung und das Plugin Statische Referenzen haben ansonsten kann ich im z.b im Controller den Service nicht benutzen.

Das gefällt mir nicht, ich möchte alles was zum Plugin gehört auch nur im Plugin Projekt haben.
Also Services, PluginDbContext usw.
Alles was in der IServiceCollection im Plugin Registriert werden soll, soll auch erst mit dem dynamischen laden des Plugin der Plugin ladenden Anwendung bekannt sein.

Im Prinzip müsste man das von natemcmaster erstellte daraufhin anpassen.
Ich verstehe nur nicht ganz warum es bei ihm nicht funktioniert.
Habt ihr da eine Idee?

16.842 Beiträge seit 2008
vor einem Jahr

Wenn ich z.B. ein Service registrieren möchte z.b. <IMyservice, MyService> dann muss sich die IMyService.cs immer in einen Gemeinsamen Prokekt befinden, auf das die Basis Anwendung und das Plugin Statische Referenzen haben ansonsten kann ich im z.b im Controller den Service nicht benutzen.

Nein, muss sie nicht. Ist in meinem Beispielprojekt auch nicht der Fall.