Library / SDK
natemcmaster/DotNetCorePlugins avatar
natemcmaster/DotNetCorePlugins

McMaster.NETCore.Plugins: Dynamic .NET Plugin Loading

.NET Core library for dynamically loading code

1,827 stars241 forksC#Apache-2.0

At a glance

What is it?
McMaster.NETCore.Plugins is a .NET library for loading assemblies as extensions, sharing selected types across load contexts, and separating plugin dependencies. It suits .NET 8 hosts that need more control than the platform's basic assembly-loading API provides.
Who is it for?
Adopt McMaster.NETCore.Plugins when a .NET 8 host needs dynamic assemblies, explicit dependency isolation, shared contract types, or unloadable plugins. Do not choose it for netstandard2.0 plugins, because the README documents known issues there.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 46 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Assembly Isolation for .NET Extension Hosts

McMaster.NETCore.Plugins solves a specific extension problem: loading a .dll at runtime while keeping the plugin's assemblies and dependencies separate from the host application. The library is intended for hosts that discover external components, execute them as extensions, and pass typed objects between the host and plugin code. Its README identifies three main needs: dynamic assembly loading, execution as part of the main application, and dependency isolation.

The project is not limited to a particular plugin model. The documentation uses an `IPlugin` interface only as an example and states that the library does not require that name, an interface, or a particular method. A host can define its own contract, while plugins can be organized as separate projects. The usual arrangement has a host project, one or more plugin projects, and often a contracts project containing the types used across the boundary.

The library supports .NET 8 in version 2.0 and later. The README directs users who need support for versions earlier than .NET 8 to the older 1.* release. It also warns against targeting `netstandard2.0` for a plugin because that target has known issues. These framework boundaries matter more than the simple loading call itself, because a plugin that cannot share the expected contract types will not be useful to the host even if its assembly loads successfully.

Based on the repository layout, the project is a library rather than a complete application framework. The `src/` directory contains the implementation, `test/` contains tests, and `samples/` contains examples including `hello-world`, dependency injection, ASP.NET Core, dynamic implementation, and hot reload examples. This makes it useful as infrastructure beneath an application's own discovery, security, and lifecycle policies.

PluginLoader, Shared Types, and Load Contexts

The central API is `PluginLoader.CreateFromAssemblyFile`. The loader accepts the path to a plugin's main `.dll`, a list of types that should be unified, and an `isUnloadable` setting. The README describes each `PluginLoader` instance as a unique collection of assemblies loaded into memory. That separation is the main architectural difference from placing every assembly in one ordinary application load context.

A basic loader call looks like this:

csharp
PluginLoader.CreateFromAssemblyFile(
    assemblyFile: "./plugins/MyPlugin/MyPlugin1.dll",
    sharedTypes: new [] { typeof(IPlugin), typeof(IServiceCollection), typeof(ILogger) },
    isUnloadable: true)

The `sharedTypes` argument addresses type identity across the host and plugin boundary. Without sharing, two copies of a contracts assembly may be loaded, so a host's `IPlugin` and a plugin's `IPlugin` can be different runtime types even though their source definitions match. The README gives the example of passing contract types through `sharedTypes` so that the loader can unify them. The same mechanism is shown for service and logging types.

The host then calls `LoadDefaultAssembly()` and can inspect the resulting assembly with reflection. The documented host example finds non-abstract types assignable to `IPlugin`, creates them with `Activator.CreateInstance`, and calls `GetName()`. That is only a sample strategy. The library does not require reflection-based discovery or parameterless constructors as universal rules.

The `isUnloadable` flag deserves careful treatment. The README says it allows the plugin to be unloaded from memory at some future point, but callers must first clean up all uses of types from the plugin. A loader API that exposes an unload option does not remove the host's references, event handlers, cached instances, or other objects that can keep the plugin alive.

Publishing Plugins for Dependency Isolation

The README requires each plugin to be published into its own directory. The documented layout is a directory under `plugins/`, with a main assembly whose filename matches the directory name and any additional plugin files alongside it:

text
plugins/
    $PluginName1/
        $PluginName1.dll
        (additional plugin files)
    $PluginName2/
        $PluginName2.dll

This arrangement is not cosmetic. Separate publication directories reduce contention between plugins and help avoid duplicate dependency problems when different plugins need different versions of the same dependency. The host must still locate and validate those files itself. The library supplies loading and isolation mechanisms, while the host defines how plugin directories are discovered.

The sample plugin contract is deliberately small:

csharp
public interface IPlugin
{
    string GetName();
}

The corresponding implementation can be minimal as well:

csharp
internal class MyPlugin1 : IPlugin
{
    public string GetName() => "My plugin v1";
}

The implementation can remain `internal` because the host discovers it through the loaded assembly and reflection rather than requiring it to be public in source. The contract project, if used, provides the interface reference that the host and plugin agree on. The documentation does not specify a package manifest, dependency resolution format, or automatic versioning policy for plugins, so those concerns should not be inferred from the loader.

Installing McMaster.NETCore.Plugins and Loading a First Plugin

Install the library into the host project with the documented NuGet command:

bash
dotnet add package McMaster.NETCore.Plugins

Then reference `PluginLoader` and point it at a published plugin assembly. The following is the first loader operation shown by the README:

csharp
using McMaster.NETCore.Plugins;

var loader = PluginLoader.CreateFromAssemblyFile(
    pluginDll,
    sharedTypes: new [] { typeof(IPlugin) });

For a concrete directory-based first use, the sample host finds subdirectories below `AppContext.BaseDirectory/plugins`, constructs the expected `.dll` path, checks that the file exists, and creates one loader per plugin. The README's `dotnet publish` command prepares the plugin directory:

bash
dotnet publish MyPlugin1.csproj --output plugins/MyPlugin1/

After the assembly is loaded, the documented reflection step finds concrete types implementing `IPlugin`:

csharp
foreach (var pluginType in loader
    .LoadDefaultAssembly()
    .GetTypes()
    .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract))
{
    IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);

    Console.WriteLine($"Created plugin instance '{plugin.GetName()}'.");
}

This example assumes a parameterless constructor and is intended to demonstrate one host design. The README points to `samples/hello-world/` for a fully functional sample, so a reader needing complete project wiring should use that example rather than treating the shortened host code as a complete application.

Version Targets, Unload Cleanup, and Missing Policy Features

The clearest limitation is framework targeting. The README states that 2.0 and later support .NET 8, while plugins using `netstandard2.0` have known issues. That makes target framework selection part of the adoption decision, not a build detail that can be deferred. A host on an older .NET version would also need to consider the older 1.* library, according to the README.

Unloading has a second limitation. Setting `isUnloadable: true` permits a future unload, but the application must clean up every use of plugin types first. The README does not define a host-wide disposal registry, lifecycle interface, or automatic reference tracker. Applications that store plugin objects in long-lived collections, register callbacks, or pass plugin instances to other components must account for those references themselves.

The documentation also does not specify plugin trust verification, signing checks, dependency version conflict resolution, activation policy, or rollback. The host must define how it decides which files are acceptable, but the README's directory convention is not a security boundary by itself. The library's stated focus is assembly loading, isolation, type sharing, hot reload, and .NET Core 2 support in older versions, as described by its introduction and comparison text. It should not be treated as a complete plugin security or deployment system without additional application-level design.

There is a real alternative in the standard .NET Core plugin tutorial referenced by the README. The tutorial uses the platform's assembly-loading API to create a simpler plugin system. McMaster.NETCore.Plugins adds features that the tutorial lacks, including an API for unifying types across the load-context boundary, hot reload, and .NET Core 2 support in the comparison made by the README. The trade-off is additional library machinery and more explicit host responsibilities around isolation and cleanup.

Apache-2.0 Licensing and Release Maintenance

The repository is licensed under Apache-2.0. That identifies the license associated with the project, but it does not by itself settle questions about notices, modifications, distribution, or compatibility with a particular product. Users should examine `LICENSE.txt` and their own compliance process when integrating the library.

The repository is not archived. Its last push was on 2026-09-07, so the project has recent recorded activity based on the repository facts. The recent releases include `v2.0.0` on 2025-01-05, `v2.0.0-beta.211` on 2024-12-31, and `v2.0.0-beta.130` on 2022-06-05. The README states that 2.0 and later support .NET 8, which is a more consequential compatibility constraint than the release labels alone.

Upgrade planning should account for the target framework, contract type placement, plugin publication layout, and whether unload behavior is required. The repository includes samples and tests, including a hot-reload sample, but the README excerpt does not define a migration procedure or a compatibility matrix for every plugin target. Before changing versions, an integrator should compare the loader API and target framework against the host's existing contract assemblies and plugin build settings.

Editorial conclusion

Adopt McMaster.NETCore.Plugins when a .NET 8 host needs dynamic assemblies, explicit dependency isolation, shared contract types, or unloadable plugins. Do not choose it for netstandard2.0 plugins, because the README documents known issues there. Verify type identity, publication layout, cleanup obligations, and framework compatibility before integration, with the loader configuration and plugin directory layout as the first implementation checks.

Frequently asked questions

How do I install McMaster.NETCore.Plugins?

The README instructs users to install the `McMaster.NETCore.Plugins` NuGet package with `dotnet add package McMaster.NETCore.Plugins`. Version 2.0 and later supports .NET 8, while older .NET versions require an older 1.* release according to the README.

What is a shared type in McMaster.NETCore.Plugins?

A shared type is a type passed through the `sharedTypes` argument so the loader can unify it across the host and plugin load boundary. The README gives `IPlugin`, `IServiceCollection`, and `ILogger` as examples of types that can be shared.

Does `isUnloadable: true` automatically unload a plugin?

No. The README says the option allows a plugin to be unloaded at some future point, but the application must first clean up all usages of types from the plugin. The library does not document an automatic cleanup process.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. natemcmaster/DotNetCorePlugins on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/natemcmaster-dotnetcoreplugins.svg)](https://hysenlabs.com/projects/natemcmaster-dotnetcoreplugins)

Was this useful?