Skip to content

Creating Your First Plugin

NyxionSoftware edited this page Aug 5, 2026 · 2 revisions

Creating Your First Plugin

Project setup

Create a WPF-enabled class library targeting the same framework as the application:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="SimulcastUtility.Plugins">
      <HintPath>lib\SimulcastUtility.Plugins.dll</HintPath>
      <Private>false</Private>
    </Reference>
    <Reference Include="SimulcastUtility.Application">
      <HintPath>lib\SimulcastUtility.Application.dll</HintPath>
      <Private>false</Private>
    </Reference>
    <Reference Include="SimulcastUtility.Core">
      <HintPath>lib\SimulcastUtility.Core.dll</HintPath>
      <Private>false</Private>
    </Reference>
  </ItemGroup>
</Project>

Use matching DLLs from the application's published output. Private=false prevents shared host contracts from being copied into the plugin package. Microsoft.Extensions.Logging.Abstractions is already referenced by the plugin contract project; add a matching package reference explicitly if your build environment requires it for logging extension methods.

Minimal implementation

using Microsoft.Extensions.Logging;
using SimulcastUtility.Plugins.Interfaces;

namespace ExamplePlugin
{
    public sealed class Plugin : ISimulcastPlugin
    {
        private IPluginContext? _context;

        public IPluginInfo Info { get; } = new ExamplePluginInfo();

        public Task InitializeAsync(IPluginContext pluginContext, CancellationToken cancellationToken = default)
        {
            _context = pluginContext;
            pluginContext.Logger.LogInformation("{PluginName} initialized.", Info.Name);
            return Task.CompletedTask;
        }

        public Task EnableAsync(CancellationToken cancellationToken = default)
        {
            _context!.ReceiverManager.SelectedReceiverChanged += SelectedReceiverChanged;
            _context.Logger.LogInformation("{PluginName} enabled.", Info.Name);
            return Task.CompletedTask;
        }

        public Task DisableAsync(CancellationToken cancellationToken = default)
        {
            if (_context is not null)
                _context.ReceiverManager.SelectedReceiverChanged -= SelectedReceiverChanged;

            return Task.CompletedTask;
        }

        public Task HandleApplicationArgumentsAsync(IReadOnlyList<string> arguments, CancellationToken cancellationToken = default)
        {
            return Task.CompletedTask;
        }

        private void SelectedReceiverChanged(object? sender, EventArgs e)
        {
            _context?.Logger.LogDebug("The selected receiver changed.");
        }
    }

    public sealed class ExamplePluginInfo : IPluginInfo
    {
        public Guid PluginIdentifier { get; } = Guid.Parse("REPLACE-WITH-A-PERMANENT-GUID");
        public string Name => "Example Plugin";
        public string Description => "A minimal Simulcast Utility plugin.";
        public Version Version => new(1, 0, 0);
        public string Author => "Your Name";
    }
}

Generate the GUID once with Guid.NewGuid() or Visual Studio's Create GUID tool. Never ship the placeholder and never change the published identifier.

Test loop

  1. Build the plugin.
  2. Copy its DLL and private dependencies to a subfolder of the configured plugin directory.
  3. Open Manage Plugins.
  4. Select Refresh Installed Plugins.
  5. Enable the plugin and inspect the application log.
  6. Disable and re-enable it to test cleanup.
  7. Delete it through Manage Plugins to confirm the load context can unload.

Clone this wiki locally