Plugin Lifecycle
This document covers how plugins are loaded, enabled/disabled, and removed.
Loading Plugins
From Directory (Desktop)
csharp
// Load all plugins from a directory
await app.Services.UsePluginsAsync("plugins");Discovery Process:
- Scans directory and subdirectories for DLLs
- Pre-loads all DLLs as dependencies
- Identifies assemblies with
IPluginManifestimplementations - Creates
PluginLoadContextfor dependency isolation - Loads manifest and discovers component types
- Registers with
PluginState
Directory Structure:
plugins/
├── PluginA/
│ ├── PluginA.dll
│ └── PluginA.deps.json
└── PluginB/
├── PluginB.dll
└── SomeDependency.dllFrom Assembly (WebAssembly)
csharp
// Load from referenced assembly
await app.Services.UsePluginAsync(typeof(MyPlugin.Manifest).Assembly);WebAssembly doesn't support dynamic assembly loading, so plugins must be referenced at compile time.
Plugin State
PluginState Manager
Singleton managing all loaded plugins:
csharp
public class PluginState
{
// All loaded plugins
public IReadOnlyList<PluginInfo> Plugins { get; }
// Only enabled plugins (respects PluginsActive)
public IReadOnlyList<PluginInfo> EnabledPlugins { get; }
// Global enable/disable
public bool PluginsActive { get; set; }
// Enabled component types
public IReadOnlyList<Type> EnabledMenuComponents { get; }
public IReadOnlyList<Type> EnabledContextPanelComponents { get; }
}PluginInfo
Runtime representation of a loaded plugin:
csharp
public class PluginInfo
{
public IPluginManifest Manifest { get; }
public Assembly Assembly { get; }
public bool IsEnabled { get; set; }
public DateTime LoadedAt { get; }
public string? SourcePath { get; }
// Discovered components
public IReadOnlyList<Type> MenuComponents { get; }
public IReadOnlyList<Type> ContextPanelComponents { get; }
}Enable/Disable
Enabling a Plugin
csharp
var pluginState = services.GetRequiredService<PluginState>();
await pluginState.EnablePluginAsync("com.example.myplugin");Flow:
PluginEnablingevent fires (cancellable)- If not cancelled,
IsEnabledset totrue PluginEnabledevent firesStateChangedevent fires- Components now render and receive messages
Disabling a Plugin
csharp
await pluginState.DisablePluginAsync("com.example.myplugin");Flow:
PluginDisablingevent fires (cancellable)- If not cancelled,
IsEnabledset tofalse PluginDisabledevent firesStateChangedevent fires- Components stop rendering, messages filtered
Global Toggle
csharp
// Disable all plugins
pluginState.PluginsActive = false;
// Re-enable all plugins
pluginState.PluginsActive = true;Lifecycle Events
csharp
public class PluginState
{
// Before enabling (can cancel)
public event EventHandler<PluginLifecycleEventArgs>? PluginEnabling;
// After enabling
public event EventHandler<PluginLifecycleEventArgs>? PluginEnabled;
// Before disabling (can cancel)
public event EventHandler<PluginLifecycleEventArgs>? PluginDisabling;
// After disabling
public event EventHandler<PluginLifecycleEventArgs>? PluginDisabled;
// Any state change
public event EventHandler? StateChanged;
}Cancellation Example:
csharp
pluginState.PluginDisabling += (sender, args) =>
{
if (args.Plugin.Manifest.Id == "critical.plugin")
{
args.Cancel = true; // Prevent disabling
}
};Removing Plugins
csharp
await pluginState.RemovePluginAsync("com.example.myplugin");Flow:
- Disables plugin if enabled
- Removes from plugin list
StateChangedevent fires- Optionally prompts for data deletion (if
IPersistentPlugin)
Lifecycle Diagram
┌─────────────┐
│ DLL File │
└──────┬──────┘
│
PluginLoader.LoadPlugin()
│
▼
┌─────────────┐
│ PluginInfo │
│ (Disabled) │
└──────┬──────┘
│
PluginState.RegisterPluginAsync()
│
▼
┌─────────────┐
│ Registered │◄────────────────────┐
│ (Enabled) │ │
└──────┬──────┘ │
│ │
┌─────────────────┼─────────────────┐ │
│ │ │ │
DisablePluginAsync() StateChanged() EnablePluginAsync()
│ │ │ │
▼ ▼ ▼ │
┌─────────────┐ ┌───────────┐ ┌───────────┐ │
│ Disabled │ │ UI │ │ Enabled │────┘
│(no messages)│ │ Updates │ │(messages) │
└──────┬──────┘ └───────────┘ └───────────┘
│
RemovePluginAsync()
│
▼
┌─────────────┐
│ Removed │
│(cleanup opt)│
└─────────────┘Message Bus Filtering
DisabledPluginConsumerFilter prevents disabled plugins from receiving messages:
csharp
public class DisabledPluginConsumerFilter : IConsumerFilter
{
public bool ShouldInvoke<TMessage>(IConsumer<TMessage> consumer, TMessage message)
{
// Returns false if consumer's assembly belongs to disabled plugin
// Also returns false if PluginsActive is false
}
}This is automatically registered when using AddPluginFramework().
