Skip to content

ShawnTheBeachy/puzzle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Puzzle

GitHub Actions Workflow Status NuGet Version GitHub Issues or Pull Requests GitHub repo size

Puzzle is a simple plugin system for .NET. It does not aim to be a fancy, best-to-ever-do-it package. It is for projects where simple, basic plugin support is needed.

Plugins are loaded in isolation; that is, they are loaded in their own AssemblyLoadContext and are isolated from the rest of the application services. Plugin services are injected alongside your application services so that you can resolve them from your DI container without any extra work. However, a plugin service will not be able to resolve your application services.

Puzzle

Download from NuGet.

This is the main package which should be used by plugin hosts. If you are writing a plugin, you will want the Puzzle.Abstractions package.

Installation

Adding plugin support to your application is as easy as one line:

...
builder.Services.AddPlugins(builder.Configuration);
...

Alternatively, for Puzzle to automatically pass itself the configuration:

...
builder.AddPlugins();
...

AddPlugins() should be called as the last step before calling builder.Build().

Configuration

Puzzle configuration options should be nested under a Plugins section.

{
  "Plugins": {
    "Locations": [
      "/path/to/plugins"
    ]
  },
  "StartupThreshold": "00:00:01.0000"
}

Locations (optional)

The locations in which Puzzle will look for plugins. Each plugin must be nested one level deep inside its own folder. For example:

.
├── ...
├── plugins                    # The directory which you will include in `Locations`
│   ├── com.company.pluginA    # The sub-directory for a plugin
|   |   |── PluginA.dll
|   |   └── ...                # Other plugin files
│   ├── com.company.pluginB    # The sub-directory for another plugin
|   |   |── PluginB.dll
|   |   └── ...                # Other plugin files
└── ...

StartupThreshold (optional)

A target time which plugin initialization should not exceed during application startup. If plugin initialization exceeds this time, a warning will be logged.

Puzzle.Abstractions

Download from NuGet.

If you are creating a plugin, this is the package for you.

Service<>

For a plugin service to be registered you must mark it with the Service<> attribute. The Service<> attribute takes a Lifetime argument. This can be Scoped, Singleton, or Transient.

NOTE: This should only be used for services which are implementing an abstraction for the host app. Let's suppose that you have the following setup:

.
├── ...
├── PluginHostApp
|   └── ...
├── PluginAbstractions
│   ├── IDataSource.cs
|   └── ...
├── SqlitePlugin
│   ├── Abstractions
│   |    ├── IConnectionFactory.cs
|   |    └── ...
│   ├── SqliteConnectionFactory.cs
│   ├── SqliteDataSource.cs
|   └── ...
└── ...

PluginHostApp is going to be looking for implementations of IDataSource, which your plugin is going to provide via SqliteDataSource. Because you want SqliteDataSource to be picked up by Puzzle, you must mark it with the Service<IDataSource> attribute. Suppose that SqliteDataSource has a dependency on IConnectionFactory, which is implemented by SqliteConnectionFactory. You would not mark SqliteConnectionFactory with the Service<IConnectionFactory> attribute because PluginHostApp does not need to know about the IConnectionFactory. Instead, you should bootstrap IConnectionFactory -> SqliteConnectionFactory as detailed below.

Your service must be public for Puzzle to pick it up.

Metadata

Your plugin must export an implementation of IPluginMetadata. If you do not export an implementation, your plugin will not be loaded.

public sealed class MyPluginMetadata : IPluginMetadata
{
  public string Id => "com.company.plugin";
  public string Name => "My Plugin";
}

Bootstrapping (optional)

If your plugin requires services to operate you can inject them via an exported IPluginBootstrapper implementation. Only one implementation is supported per plugin.

Puzzle will automatically add logging and IHttpContextAccessor for you; you should not bootstrap them yourself.

public sealed class MyPluginBootstrapper : IPluginBootstrapper
{
  public IServiceCollection Bootstrap(IServiceCollection services, IConfiguration configuration)
  {
    services.AddTransient<MyConnectionBuilder>(configuration.GetRequiredSection("Connections"));
    return services;
  }
}

The IConfiguration object which is passed in has two sources:

  1. Environment variables
  2. (Optional) A settings.json file located at the root of your plugin.

Puzzle.Blazor

Download from NuGet.

If you want to support Blazor components in your application host you will need to install Puzzle.Blazor.

...
builder.AddPlugins(config => config.AddBlazor());
...
app.MapRazorComponents<App>()
  .AddPluginComponents(app)
  ...;
...

Puzzle will automatically register all exported IComponent instances from plugins. When components from these plugins are used, Puzzle will activate the components using the plugin's DI container instead of the host application's DI container.

In order for DI to work the component must use constructor injection; NOT property injection via @inject. The notable exceptions to this rule are NavigationManager and IJsRuntime.

About

A plugin system for .NET.

Resources

License

Stars

Watchers

Forks

Packages

No packages published