![]() |
VOOZH | about |
dotnet add package McMaster.NETCore.Plugins --version 2.0.0
NuGet\Install-Package McMaster.NETCore.Plugins -Version 2.0.0
<PackageReference Include="McMaster.NETCore.Plugins" Version="2.0.0" />
<PackageVersion Include="McMaster.NETCore.Plugins" Version="2.0.0" />Directory.Packages.props
<PackageReference Include="McMaster.NETCore.Plugins" />Project file
paket add McMaster.NETCore.Plugins --version 2.0.0
#r "nuget: McMaster.NETCore.Plugins, 2.0.0"
#:package McMaster.NETCore.Plugins@2.0.0
#addin nuget:?package=McMaster.NETCore.Plugins&version=2.0.0Install as a Cake Addin
#tool nuget:?package=McMaster.NETCore.Plugins&version=2.0.0Install as a Cake Tool
👁 Build Status
👁 Code Coverage
👁 NuGet
👁 NuGet Downloads
This project provides API for loading .NET assemblies dynamically, executing them as extensions to the main application, and finding and isolating the dependencies of the plugin from the main application. It allows fine-grained control over assembly isolation and type sharing. Read more details about type sharing below.
Note
2.0+ of library supports .NET 8. If you still need .NET < 8 support, look for an old 1.* version of this library.
Blog post introducing this project, July 25, 2018: .NET Core Plugins: Introducing an API for loading .dll files (and their dependencies) as 'plugins'.
Since 2018, .NET Core 3 was released, and it added stdlib API to improve assembly loading. If you are interested in understanding that API, see "Create a .NET Core application with plugins" on docs.microsoft.com. The result of this tutorial would be a simpler version of DotNetCorePlugins, but missing some features like an API for unifying types across the load context boundary, hot reload, and .NET Core 2 support.
Install the McMaster.NETCore.Plugins NuGet package.
dotnet add package McMaster.NETCore.Plugins
The main API to use is PluginLoader.CreateFromAssemblyFile.
PluginLoader.CreateFromAssemblyFile(
assemblyFile: "./plugins/MyPlugin/MyPlugin1.dll",
sharedTypes: new [] { typeof(IPlugin), typeof(IServiceCollection), typeof(ILogger) },
isUnloadable: true)
See example projects in for more detailed, example usage.
Using plugins requires at least two projects: (1) the 'host' app which loads plugins and (2) the plugin, but typically also uses a third, (3) an contracts project which defines the interaction between the plugin and the host.
For a fully functional sample of this, see .
You can define your own plugin contract. A minimal contract might look like this.
public interface IPlugin
{
string GetName();
}
There is nothing special about the name "IPlugin" or the fact that it's an interface. This is just here to illustrate a concept. Look at for additional examples of ways you could define the interaction between host and plugins.
Warning
Using
netstandard2.0as the TargetFramework for your plugin project has known issues. Usenet8.0instead.
A minimal implementation of the plugin could be as simple as this.
internal class MyPlugin1 : IPlugin
{
public string GetName() => "My plugin v1";
}
As mentioned above, this is just an example. This library doesn't require the use of "IPlugin" or interfaces or "GetName()" methods. This code is only here to demonstrates how you can decouple hosts and plugins, but still use interfaces for type-safe interactions.
The host application can load plugins using the PluginLoader API. The host app needs to define a way to find
the assemblies for the plugin on disk. One way to do this is to follow a convention, such as:
plugins/
$PluginName1/
$PluginName1.dll
(additional plugin files)
$PluginName2/
$PluginName2.dll
It is important that each plugin is published into a separate directory. This will avoid contention between plugins and duplicate dependency issues.
You can prepare the sample plugin above by running
dotnet publish MyPlugin1.csproj --output plugins/MyPlugin1/
An implementation of a host which finds and loads this plugin might look like this. This sample uses reflection to find
all types in plugins which implement IPlugin, and then initializes the types' parameter-less constructors.
This is just one way to implement a host. More examples of how to use plugins can be found in .
using McMaster.NETCore.Plugins;
var loaders = new List<PluginLoader>();
// create plugin loaders
var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins");
foreach (var dir in Directory.GetDirectories(pluginsDir))
{
var dirName = Path.GetFileName(dir);
var pluginDll = Path.Combine(dir, dirName + ".dll");
if (File.Exists(pluginDll))
{
var loader = PluginLoader.CreateFromAssemblyFile(
pluginDll,
sharedTypes: new [] { typeof(IPlugin) });
loaders.Add(loader);
}
}
// Create an instance of plugin types
foreach (var loader in loaders)
{
foreach (var pluginType in loader
.LoadDefaultAssembly()
.GetTypes()
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract))
{
// This assumes the implementation of IPlugin has a parameterless constructor
IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
Console.WriteLine($"Created plugin instance '{plugin.GetName()}'.");
}
}
<a id="shared-types"></a>
By default, each instance of PluginLoader represents a unique collection of assemblies loaded into memory.
This can make it difficult to use the plugin if you want to pass information from plugin to the host and vice versa.
Shared types allow you define the kinds of objects that will be passed between plugin and host.
For example, let's say you have a simple host app like , and
two plugins which were compiled with a reference interface IPlugin. This interface comes from Contracts.dll.
When the application runs, by default, each plugin and the host will have their own version of Contracts.dll
which .NET will keep isolated.
The problem with this isolation is that an object of IPlugin created within the "PluginApple" or "PluginBanana" context does not appear to be an instance of IPlugin in any of the other plugin contexts.
Configuring a shared type of IPlugin allows the .NET to pass objects of this type across the plugin isolation
boundary. It does this by ignoring the version of Contracts.dll in each plugin folder, and sharing the version that comes with the Host.
Read .
A common usage for plugins is to load class libraries that contain MVC controllers or Razor Pages. You can
set up an ASP.NET Core to load controllers and views from a plugin using the McMaster.NETCore.Plugins.Mvc
package.
dotnet add package McMaster.NETCore.Plugins.Mvc
The main API to use is .AddPluginFromAssemblyFile(), which can be chained onto the call to .AddMvc()
or .AddRazorPages() in the Startup.ConfigureServices method.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
var pluginFile = Path.Combine(AppContext.BaseDirectory, "plugins/MyRazorPlugin/MyRazorPlugin.dll");
services
.AddMvc()
// The AddPluginFromAssemblyFile method comes from McMaster.NETCore.Plugins.Mvc
.AddPluginFromAssemblyFile(pluginFile);
}
}
See example projects in for more detailed, example usage.
Sometimes you may want to use a plugin along with reflection APIs such as Type.GetType(string typeName)
or Assembly.Load(string assemblyString). Depending on where these APIs are used, they might fail to
load the assemblies in your plugin. There is an API which you can use to set the ambient context
which .NET's reflection APIs will use to load the correct assemblies from your plugin.
Example:
var loader = PluginLoader.CreateFromAssemblyFile("./plugins/MyPlugin/MyPlugin1.dll");
using (loader.EnterContextualReflection())
{
var myPluginType = Type.GetType("MyPlugin.PluginClass");
var myPluginAssembly = Assembly.Load("MyPlugin1");
}
Read this post written by .NET Core engineers for even more details on contextual reflection.
Under the hood, DotNetCorePlugins is using a .NET API called ApplicationLoadContext.
This creates a scope for resolving assemblies. By default, PluginLoader will create a new context
and fallback to a default context if it cannot find an assembly or if type sharing is enabled.
The default fallback context is inferred when PluginLoader is instantiated. In certain advanced scenarios,
you may need to manually change the default context, for instance, plugins which then load more plugins,
or when running .NET in a custom native host.
To override the default assembly load context, set PluginConfig.DefaultContext. Example:
AssemblyLoadContext myCustomDefaultContext = // (something).
PluginLoader.CreateFromAssemblyFile(dllPath,
config => config.DefaultContext = myCustomDefaultContext);
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 5 NuGet packages that depend on McMaster.NETCore.Plugins:
| Package | Downloads |
|---|---|
|
ReportGenerator.Core
ReportGenerator converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov, or lcov into human readable reports in various formats. The reports show the coverage quotas and also visualize which lines of your source code have been covered. Use this package if you want to write a custom plugin for ReportGenerator or if you want to call/execute ReportGenerator within your code base. Plugin development: https://github.com/danielpalme/ReportGenerator/wiki/Custom-reports https://github.com/danielpalme/ReportGenerator/wiki/Custom-history-storage |
|
|
Microting.eFormApi.BasePn
Package Description |
|
|
CounterStrikeSharp.API
Official server side runtime assembly for CounterStrikeSharp |
|
|
CodeGeneration.Roslyn.Engine
The engine of source code generation; used by CodeGeneration.Roslyn.Tool. Useful for testing custom source generators. API: * CodeGeneration.Roslyn.CompilationGenerator * CodeGeneration.Roslyn.DocumentTransform |
|
|
McMaster.NETCore.Plugins.Mvc
Provides API for dynamically loading MVC controllers into an ASP.NET Core web application. This package should be used by the host application which needs to load plugins. See https://github.com/natemcmaster/DotNetCorePlugins/blob/main/README.md for more samples and documentation. |
Showing the top 16 popular GitHub repositories that depend on McMaster.NETCore.Plugins:
| Repository | Stars |
|---|---|
|
danielpalme/ReportGenerator
ReportGenerator converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov or lcov into human readable reports in various formats.
|
|
|
Squidex/squidex
Headless CMS and Content Managment Hub
|
|
|
roflmuffin/CounterStrikeSharp
CounterStrikeSharp allows you to write server plugins in C# for Counter-Strike 2/Source2/CS2
|
|
|
Artemis-RGB/Artemis
Provides advanced unified lighting across many different brands RGB peripherals
|
|
|
Reloaded-Project/Reloaded-II
Universal .NET Core Powered Modding Framework for any Native Game X86, X64.
|
|
|
revoframework/Revo
Event Sourcing, CQRS and DDD framework for C#/.NET Core.
|
|
|
mehdihadeli/food-delivery-modular-monolith
🌭 A practical and imaginary food and grocery delivery modular monolith, built with .Net 8, Domain-Driven Design, CQRS, Vertical Slice Architecture, Event-Driven Architecture, and the latest technologies.
|
|
|
AArnott/CodeGeneration.Roslyn
Assists in performing Roslyn-based code generation during a build.
|
|
|
compujuckel/AssettoServer
Custom Assetto Corsa server with focus on freeroam
|
|
|
ACEmulator/ACE
Asheron's Call server emulator.
|
|
|
arduosoft/RawCMS
RawCMS is the headless CMS written in asp.net core build for developers that embraces API first technology. Please give us a feedback!
|
|
|
neronotte/Greg.Xrm.Command
Command Line utility for Dataverse
|
|
|
NeilMacMullen/kusto-loco
C# KQL query engine with flexible I/O layers and visualization
|
|
|
Capnode/Algoloop
Windows desktop algo trading with QuantConnect Lean engine.
|
|
|
Kaioru/Edelstein
A v.95.1 Mushroom game server emulator written in C# .NET
|
|
|
microsoft/service-fabric-observer
Highly configurable, extensible and performant Service Fabric watchdog service that, out of the box, monitors a broad range of physical machine resources that tend to be very important to Service Fabric services and maps these metrics to SF entities. It targets both Windows and Linux SF clusters.
|
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0 | 430,438 | 1/5/2025 |
| 2.0.0-beta.214 | 89 | 12/28/2025 |
| 1.4.0 | 6,709,151 | 5/27/2021 |
| 1.3.1 | 851,714 | 8/28/2020 |
| 1.3.0 | 191,194 | 5/8/2020 |
| 1.2.0 | 64,792 | 3/10/2020 |
| 1.1.0 | 29,019 | 1/17/2020 |
| 1.0.0 | 9,570 | 1/2/2020 |
| 0.3.2 | 11,852 | 12/16/2019 |
| 0.3.1 | 145,611 | 10/14/2019 |
| 0.3.0 | 3,053 | 9/24/2019 |
Breaking changes:
* Require .NET >= 8.0
* Drop dependencies on .NET Core 2 libraries for manually parsing .deps.json. Use the built-in .NET 8 API for this.