GithubHelp home page GithubHelp logo

weikio / pluginframework Goto Github PK

View Code? Open in Web Editor NEW
530.0 21.0 100.0 761 KB

Everything is a Plugin in .NET

License: MIT License

C# 100.00%
plugin netcore roslyn nuget plugin-architecture dotnet

pluginframework's People

Contributors

mikkok-adafy avatar mikkou-adafy avatar mikoskinen avatar panoukos41 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pluginframework's Issues

Update package logo

All the Nuget packages currently use the Weik.io logo. Update the packages so that Plugin Framework's logo is used.

More Complete MVC Example

Your mvc example is rubbish has no purpose provide a better example of say loading a button and a action happen.

Resolve dependencies in Plugin

The plugins that I have created is having some dependencies being injected via the constructor of the plugin.

In my application I'm using Autofac as the container and I have created an extension method which will scan the location where I have my plugins and registers the dependencies to the container. The scanning and registration seems to be working but due to some reason when I'm trying to get the plugin by tag i'm getting an error that the dependencies of the plugin cannot be resolved.

I just want to check if this is possible , if so am I missing something. Also it would be helpful if there is examples that I can check it out.

Any help is highly appreciated !

Blazor Plugins CSS Isolation

I can successful load razor components from loosely coupled modules using an appropriate plugin interface, but this doesn't include the CSS isolation. Any thoughts?

Support for finding types which could implement interface

Background

The typefinder can currently find types which implement an interface.

But in some scenarios it would be great if we could find types which satisfy the interface without implementing it.

Solution ideas

There's at least couple ways to implement this:

  1. Add a new criteria to TypeFinderCriteria, something like "Type CanImplement".
  2. Alternatively provide a way through TypeFinderCriteria for describing the requirements for the the type. The requirements could be like: "Type has to have method which takes string and int as parameters and returns y" or "Type has to have a constructor which accepts ILogger". Multiple requirements should be supported.

Maybe at this point we could separate TypeFinder into a separate Nuget package?

Also, should we enable ducktaping for the found types at this point? Maybe wrap the types which could implement an interface with a new class that actually implements the interface and use that as a proxy. Not sure about this one. We could perhaps use something like Impromptu for handling the ducktaping.

Get plugins by tag

Plugin Framework supports plugin tagging. It would help if catalogs would support getting a list of plugins based on a tag.

Support for local filesystem feeds.

I'm not yet publishing nuget packages to nuget.org. So for development I'm using Visual Studio's built in nuget filesystem repository to publish packages. The source in VS is named "Microsoft Visual Studio Offline Packages" and typically points to C:\Program Files (x86)\Microsoft SDKs\NuGetPackages. I can see that my packages have been added successfully to that root path via nuget.exe add. However, I have yet to find an example of how to point weikio to this folder and successfully load. Is loading catalogs from a local filesystem repository a supported scenario? If so, can you provide an example of how that could be accomplished (working with dependencies)?

`FolderPluginCatalog` doesn't automatically load dependent assemblies in the same folder

Hello!

Real quick I wanted to say that I'm so glad I found this project, it's the exact amount of auto-magic I was hoping for In a plugin system!

So, I'm making use of a FolderPluginCatalog for now, just as I'm setting up the scaffolding for a project. One of the plugin assembles references a nuget package. I'm using the "xcopy deploy" method to copy my plugin assemblies (and their dependencies via <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>) to my plugins folder.

This is a slimmed down version of my PluginCoordinator:

	public class PluginCoordinator
	{
		private static readonly DirectoryInfo[] _pluginPaths = new[]
		{
			new DirectoryInfo("./plugins")
		};

		private static readonly Type _pluginBaseType = typeof(IPlugin);

		private static readonly Type[] _pluginTypes = new[]
		{
			typeof(IFooPlugin),
			typeof(IBarPlugin),
			typeof(IBazPlugin),
			/* and so on */
		};

		private static ILoggerFactory _loggerFactory { get; set; }

		public static int LoadPlugins(ILoggerFactory loggerFactory)
		{
			_loggerFactory = loggerFactory;

			var allCatalogs = new CompositePluginCatalog();

			foreach (DirectoryInfo pluginPath in _pluginPaths)
			{
				foreach (Type type in _pluginTypes)
				{
					IPluginCatalog catalog = LoadPluginsOfType(pluginPath, type);
					allCatalogs.AddCatalog(catalog);
				}
			}

			// initialize all catalogs syncronously
			allCatalogs.Initialize().Wait();

			// get all the plugins and do something fun with them!
			var plugins = allCatalogs.GetPlugins();

			return plugins.Count;
		}

		private static IPluginCatalog LoadPluginsOfType(DirectoryInfo pluginDirectory, Type pluginType)
		{
			TypeFinderCriteria? pluginCriteria = TypeFinderCriteriaBuilder.Create()
				.Implements(_pluginBaseType)
				.Implements(pluginType)
				.Build();

			if (pluginCriteria == null)
			{
				throw new InvalidOperationException("Failed to build plugin search criteria.");
			}

			var contextOptions = new PluginLoadContextOptions()
			{
				LoggerFactory = () => _loggerFactory.CreateLogger<PluginAssemblyLoadContext>(),
				//AdditionalRuntimePaths = new List<string> { pluginDirectory.FullName }
				/* ^^^ This is key! ^^^ */
			};

			FolderPluginCatalogOptions options = new()
			{
				IncludeSubfolders = false,
				PluginLoadContextOptions = contextOptions,
				TypeFinderOptions = new TypeFinderOptions()
				{
					TypeFinderCriterias = new List<TypeFinderCriteria> { pluginCriteria }
				}
			};

			return new FolderPluginCatalog(pluginDirectory.FullName, options);
		}
	}

The line commented in the creation of the PluginLoadContextOptions is where I've found my problem. With the code as shown (not setting the additional runtime paths to include the exact same folder causes a FileNotFound exception when say the assembly containing the IFoos tries to load (thus needing it's NuGet dependency -- which is in the folder with it). However, adding the AdditionalRuntimePaths back in like it's written there: Everything loads peachy just like you'd imagine it should.

I can't tell if this is an issue from me using it wrong (and I'm expected to specify the folder not only in the FolderPluginCatalog constructor, but also through the context options,) or if it's something that the framework should handle and just doesn't.

Thanks again for the awesome project!

Add "Plugin identifier"

It would be nice to have a way for an assembly/Nuget package to describe the plugin's content.

For example currently we have to use the following syntax:

        var catalog = new NugetPackagePluginCatalog("Serilog", "2.9.0", configureFinder: configure =>
        {
            configure.HasName("Serilog.Log");
        });

In the future we would like to be able to write this:

        var catalog = new NugetPackagePluginCatalog("Serilog", "2.9.0");

So that the Serilog-package could tell the Plugin Framework that Serilog.Log is the type-plugin for this package.

NOTE: It's unlikely but possible that a same plugin package / assembly is used from two or more different apps and for each app the "plugin" is a different thing. The interface for getting the plugin definition should maybe take HostApp's name and HostApp's version as parameters.

More that one feed in nuget catalog

I want to load my plugins with their dependencies. Because #46 I try with nuget packages but I faced with same problem.

Is there way to load local nuget package and dependencies from official nuget feed? I don't want to publish my packages into nuget.org.

Question about ReflectionOnlyLoad

Does this library use ReflectionOnlyLoad when identifying assemblies to load? A quick search of the source tree seemed to suggest it doesn't.

With other libraries in the .net framework days, I experienced problems where merely searching for assemblies with a given class method signature would load the assembly into the process. Not only does that lock the file but IIRC it can mean unwanted static constructors get run. How does this library avoid such problems?

.NET 5 version

Configure the projects so that they target both the netcoreapp31/netstandard20 as they currently do and add support for .NET 5.

So all our project files in the future should target both the netcoreapp31/netstandard20 and .NET 5.

  • Configure project files
  • Make sure all the current samples and tests work without any changes
  • Create .NET 5 sample for Blazor WebAsembly (can be identical to the Blazor Server Sample)

FolderPluginCatalog: unnecessary assembly loading

Hi, @mikoskinen.

Thank you for your great efforts on this, really appreciate it!

While testing this framework for my case, I found inconsistency with TypeFinderCriterias for FolderPluginCatalog. To be more precise, the problem is in FolderPluginCatalog.IsPluginAssembly method, as it uses the obsolete TypeFinderCriterias property to determine if the specified assembly contains plugins.
https://github.com/weikio/PluginFramework/blob/master/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalog.cs#L192
https://github.com/weikio/PluginFramework/blob/master/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalog.cs#L248

As the result, if you configure FolderPluginCatalogOptions with the new TypeFinderOptions and not with the obsolete TypeFinderCriterias, it will treat all assemblies as those that contain plugins, which leads to unnecessary assembly loading.

I've already created a fix to this issue, but it seems that I don't have permission to push it.
Could you please take a look?

Thanks,
Vasyl

Can't work in Blazor WebAssembly App

There is a Blazor Server sample, which is successful. Then I created a Blazor WebAssembly App and build the project. It shows:

Weikio.PluginFramework.Samples.Shared.csproj targets netcoreapp3.1. It can't be targeted to .NETStandard,Version=v2.1
Weikio.PluginFramework.AspNetCore.csproj targets netcoreapp3.1. It can't be targeted to .NETStandard,Version=v2.1

I noticed the TargetFramework in the Blazor WebAssembly App project is:

<PropertyGroup>
    <TargetFramework>netstardard2.1</TargetFramework>
</PropertyGroup>

How to solve this?

Type plugin with tags not created

First of all thank you sharing this awesome library.

Use case

Create plugin catalogs of type "Type" with tags

Scenario

  1. Define plugin interface in a library ex: Test.Abstraction
  2. Define the plugin interface in a separate library ex: TestPlugin
  3. Create the plugin app with reference to Test.Abstractions.

Issue

Plugins are not created with tags.

I have attached a unit test project with scenario. Please let me know if i'm, missing something ?

PluginFramework.zip

NugetFeedPlugInCatalog & nuget dependency resolution issue..

Hi All,

On .net 6 and I'm having some trouble getting a NugetFeedPlugInCatalog configured properly.

Setting up the web service, within ConfigureDevelopmentServices

   var packageFolder = @"C:\PlugInStaging\EasyPost";
   var feedDetails   = new NuGetFeed("PlugIn Staging", packageFolder);
   var pluginCatalog = new NugetFeedPluginCatalog(feedDetails);

   services.AddPluginFramework()
               .AddPluginCatalog(pluginCatalog)
               .AddPluginType<IPlugIn>();

Resulting in

MINIMAL -
MINIMAL - Attempting to gather dependency information for package 'Acme.EasyPost.1.0.19' with respect to project 'C:\Users\t\AppData\Local\Temp\NugetFeedPluginCatalog\4mqidyxh.v5a', targeting 'net6.0'
DEBUG - Total number of results gathered : 15
MINIMAL - Gathering dependency information took 60 ms
DEBUG - Summary of time taken to gather dependencies per source :
DEBUG - C:\Users\t\AppData\Local\Temp\NugetFeedPluginCatalog\4mqidyxh.v5a - 56 ms
DEBUG - C:\PlugInStaging\EasyPost - 75 ms
MINIMAL - Attempting to resolve dependencies for package 'Acme.EasyPost.1.0.19' with DependencyBehavior 'Lowest'
fail: Weikio.PluginFramework.AspNetCore.PluginFrameworkInitializer[0]
Failed to initialize Weikio.PluginFramework.Catalogs.NugetFeedPluginCatalog
System.InvalidOperationException: Unable to resolve dependency 'Acme.Support'.

I think that this throws when DI resolution kicks in, from a component that requires the catalog.
Maybe a Packaging issue so I included a .nuspec which should force all dependencies into the .net6 nuget folder (based on this => https://josef.codes/dotnet-pack-include-referenced-projects/)
I copied the package to the package folder. When unzipped, the package does contain the required dependencies.

After retest, same issue.
The framework is very elegant IMHO. Excited to use this! I'm just not getting my proof of concept locked down just yet.
Any suggestions to get there?

Thanks,
Travis

MVC controllers in plugin

Thank you for the framework. Great work.

Can I load Controllers from a plugin and get them to work with the routing?

Weikio.NugetDownloader not available

It looks like you made a change to use this NuGet package in #35, however the package doesn't exist on NuGet yet.

I suggest either reverting the change from #35 or publishing the package so 3rd party development can be done here. :)

ASP.NET Core: Configure the default plugin

Background

Plugin Framework's ASP.NET Core package contains code which allows the developer to automatically register the plugins into the dependency injection system. Given the following code:

        services.AddPluginFramework()
            .AddPluginCatalog(folderPluginCatalog)
            .AddPluginType<IOperator>();

All the IOperators are available through IEnumerable:

    public CalculatorController(IEnumerable<IOperator> operators)
    {
        _operators = operators;
    }

In addition, a single IOperator can also be injected:

    public CalculatorController(IOperator myOperator)
    {
        _myOperator = myOperator;
    }

The problem:

The problem with injecting a single IOperator is that the developer can't control which IOperator is returned. This is hard coded as FirstOrDefault in AddPluginType:

            var result = GetTypes<T>(sp);

            return result.FirstOrDefault();

Enhancement:

The developer should be able to control which IOperator is the "default". This would be useful in scenarios where only one plugin of a given type can be enabled (through UI or through configuration, for example).

Maybe the ideal solution for this could be through IOptions. The AddPluginType should contain a parameter which can be used as a syntactic sugar but under the hood, this should just configure the default option.

Security Considerations

I skimmed your code for few minutes and found nothing (correct me if I'm wrong!) about security. Plugins will have complete freedom to do everything. Is there anyway to restrict what plugin can do in this framework?

Add support for appsettings.json

It should be possible to configure Plugin Framework using a configuration file (appsettings.json or some other json-file).

Add this functionality into a new library. The AspNet-project should reference this new library as the functionality is most likely used with ASP.NET Core.

Other Framework Versions

I was wondering if this would work with other .Net Frameworks? We currently have many applications that are in the 4.x framework and would like to incorporate something like this. Would the entire application need to be converted to .Net 4.x or does it incorporate features that are only available with .Net Core?

Can the framework load older Framework code in the .Net Core version?

Thanks,
Tim

LoggerFactory

Is there a way I can set the logger factory so that the internal logs can be viewed in case of any error in loading the plugins or it's dependencies.

I'm trying to understand the different options to integrate in my project. Once again thank you for sharing this project !

Plugin framework implementation in WebAppWithAppSettings

In this sample project WebAppWithAppSettings. I see there is a project reference of the plugin project in the host application. My understanding was when we implement plugin framework we don't need direct reference of the plugin project in the host application.

Please let me know if my understanding is correct or wrong.

Question/observation: necessity to keep catalog reference

Hi - I just ran across an issue which I think understand now: I had the problem that I could successfully instantiate plugins, but for some reason could not invoke a method on one of them because that required to load an additional assembly. At this time the AssemblyLoadContext was in state Unloading and I got this exception:

System.IO.FileLoadException: Could not load file or assembly 'MyPluginLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. An operation is not legal in the current state. (0x80131509)
File name: 'MyPluginLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
---> System.InvalidOperationException: AssemblyLoadContext is unloading or was already unloaded.
at System.Runtime.Loader.AssemblyLoadContext.VerifyIsAlive()
at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(String assemblyPath)
at Weikio.PluginFramework.Context.PluginAssemblyLoadContext.Load(AssemblyName assemblyName)
at System.Runtime.Loader.AssemblyLoadContext.ResolveUsingLoad(AssemblyName assemblyName)
at System.Runtime.Loader.AssemblyLoadContext.Resolve(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)

At this time I no longer had a reference to the plugin catalog which itself holds a reference to the AssemblyLoadContext. As soon as I kept the reference to the catalog this issue was gone.

Is it correct that I have to keep a permanent reference to the catalog to ensure that delayed loading of dependencies can be done for a plugin?

Hot reload with .net 6?

Hi,

now that .net 6 is already here is the functionality going to improve with hot reload and runtime unloading/reloading of plugins without restarting the application. with net 6 the multiplatform android,ios,mac etc support is also given by microsoft with blazor and all. whats the plan for this next update, what i am after is basically just blazor & hot reload currently.

thanks for such an awesome work

Resolve additional (shared) DLL paths automatically

Issue:

The future version should automatically try to resolve the additional runtime/shared paths/frameworks required by the plugins:

image

Relates to #21 where we had to manually add additional runtime path:

PluginLoadContextOptions.Defaults.AdditionalRuntimePaths.Add(Path.GetDirectoryName(typeof(Button).Assembly.Location));

The AspNetCore-library also has this:

        var aspNetCoreLocation = Path.GetDirectoryName(aspNetCoreControllerAssemblyLocation);
        PluginLoadContextOptions.Defaults.AdditionalRuntimePaths.Add(aspNetCoreLocation);

Without these, the plugins can't load all the required DLLs.

Possible solution:

It might be possible to use MetadataReference to get the references. For example:

var assemblyRef = MetadataReference.CreateFromFile(assemblyPath);
var references = assembly.GetReferencedAssemblies();

This must be done recursively to find all the referenced assemblies and their locations. This info is then added to PathAssemblyResolver in FolderPluginCatalog.

NugetPackagePluginCatalog should download the package only once

PluginFramework supports installing plugins using Nuget. This is implemented in NugetPackagePluginCatalog.

If NugetPackagePluginCatalog's packagesFolder is set, the Nuget package gets always installed into the same path. Still, NugetCatalog always tries to download the Nuget package.

Change

We want to download the package only once. When NugetPackagePluginCatalog is initialized, it should check if the package has been previously installed and if so, it should use that instead of trying to download the package again.

Implementation idea

When first initializing the catalog, can we serialize and save nugetDownloadResult into some fixed location (based on the packages folder & package identifier (name.version)) in NugetPackagePluginCatalog.Initialize? Before calling nuGetDownloader.DownloadAsync we could check if the file already exists and deserialize the result instead of using NugetDownloader.

Note

Behind the scenes NugetPackagePluginCatalog uses NugetDownloader. NugetDownloader takes care of installing the correct version of the package (x86 vs x64 vs arm vs Win10 vs Win11 etc). NugetDownloader passes NugetDownloadResult to PluginFramework and PF uses that info when initializing the catalog.

NugetDownloader can be found from https://github.com/weikio/nugetdownloader but we shouldn't need to modify it.

Testing

New unit test should be created to test this functionality by cloning the NugetPackagePluginCatalogTests.CanInstallPackageWithNativeDepencencies. This is a good test case as it uses the native dlls etc. which are provided by the NugetDownloader.

Question: Plugins from paralel project in solution

Hello there,

As I understand it, you can have a few types of "Catalogues". I understand the "typical" scenario is to just have a Plugins directory alongside the hosting binary. This makes it necessary to copy files around... If I have a solution with several projects, one of them being the host application, and another one a plugin, can I make it work with just clicking the "play" button on Visual Studio? To simplify development... Maybe with project references?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.