McMaster.NETCore.Plugins

repository·main·Indexed 23 days ago

https://github.com/natemcmaster/dotnetcoreplugins

An API for loading .NET assemblies dynamically while isolating their dependencies from the main application. It provides fine-grained control over assembly isolation and type sharing via PluginLoader.CreateFromAssemblyFile, enabling extensible plugin architectures. The library supports .NET 8 (version 2.0+) and earlier versions (1.*), and includes a specialized McMaster.NETCore.Plugins.Mvc package for loading ASP.NET Core MVC controllers and Razor Pages.

Tokens
4.3K
Snippets
14
Records
24
Agent score
82%

What's inside McMaster.NETCore.Plugins

  1. Understand the Dynamic Implementation Sample

    main

    This sample demonstrates how to build a pluggable application where adding new modules can extend or alter existing behavior using a dependency injection container. It showcases how to coordinate shared types between a host and multiple plugins.

    Project Structure:

    • Host: A console application that serves as the entry point and contains the sample execution logic.
    • Contracts: A project containing the interfaces shared by both the host and the plugins to ensure type compatibility.
    • Mixer: A plugin providing the default implementation. It includes FruitService (returning 3 fruits) and a Mixer service (returning a fruit shake).
    • ServiceImplementation: An alternative plugin. When included in the plugin set, it overrides the default FruitService behavior, causing the shake to be composed of only a Banana.
  2. Understand the Dependency Injection plugin sample

    main

    This sample demonstrates a plugin architecture where multiple plugins coordinate types with a host application using a dependency injection (DI) container. The architecture consists of four projects:

    • DI.HostApp: A console application that scans for a plugins folder in its base directory, loads discovered plugins, and configures them within a dependency injection collection.
    • DI.SharedAbstractions: A project containing interfaces that are shared between the host and the plugins to ensure type compatibility.
    • MyPlugin1 and MyPlugin2: Plugin implementations that implement the shared abstractions and register their services with the host's DI container.
  3. What is a shared type and why is it used?

    main

    A shared type is an interface or class that is present in both the host application and the plugin assembly. In this library, you pass these types into PluginLoader.CreateFromAssemblyFile via the sharedTypes parameter.

    This mechanism is used to control type unification. Without specifying shared types, the host and the plugin might load different versions of the same assembly, causing InvalidCastException when the host tries to cast a plugin object to a shared interface. By defining shared types, the PluginLoader ensures that the types are resolved to the same identity in the AssemblyLoadContext, allowing seamless communication between the host and the plugin.

  4. Understand shared types in PluginLoader

    main

    In the context of PluginLoader, shared types are types that are unified between the host application and a loaded plugin.

    By default, this library does not unify types, allowing multiple versions of the same assembly to exist in separate plugins. While this prevents version conflicts, it breaks type exchange (the ability to pass an instance of a type from the host to a plugin or vice versa).

    To enable type exchange for specific types, you must explicitly define them as shared types during plugin loading.

  5. How Hot Reloading works with PluginLoader

    main

    Hot reloading in this plugin system is achieved by using a PluginLoader configured with hot reloading support. The host application implements a reactive pattern to handle updates:

    1. Event Subscription: The host subscribes to the PluginLoader.Reloaded event. This event is fired whenever the loader detects that new versions of the plugin assemblies are available on disk.
    2. Assembly Invocation: When the Reloaded event is triggered, the host identifies the new assembly version and invokes its entry point.

    This allows a running application to update its logic dynamically without a full process restart.

  6. Understand the Hello World sample architecture

    main

    The Hello World sample demonstrates a basic plugin architecture using three distinct projects:

    1. HostApp: A console application that scans for a plugins folder in its base directory and attempts to load any discovered plugins.
    2. MyPlugin: A project that provides a concrete implementation of the plugin interface.
    3. PluginContract: A shared project containing the interface that both the host and the plugins must reference to communicate.
  7. How to load plugins with shared types

    main

    When building a plugin system, certain types must have the same identity in both the host application and the loaded plugins to allow for successful type exchange (e.g., passing an interface implementation from a plugin to a host).

    To achieve this, use the sharedTypes parameter in the PluginLoader.CreateFromAssemblyFile method. By providing an array of types that the host and plugins should treat as identical, you prevent version mismatch issues where the plugin might otherwise attempt to use its own private copy of a framework assembly (like ASP.NET Core) instead of the host's version.

        var loader = PluginLoader.CreateFromAssemblyFile(
            pluginAssembly,
            sharedTypes: new[]
            {
                typeof(IApplicationBuilder),
                typeof(IWebPlugin),
                typeof(IServiceCollection),
            });
  8. How shared types work in plugin isolation

    main

    By default, each PluginLoader instance creates a unique collection of assemblies in memory. This isolation prevents dependency conflicts but makes it difficult to pass objects between the host and the plugin because the same type (e.g., an interface from a shared Contracts.dll) will be treated as different types in different contexts.

    Shared Types solve this by instructing the loader to ignore the version of the assembly in the plugin folder and instead use the version provided by the Host. This allows an object created in a plugin context to be recognized as the same type in the host context.

  9. Run the ASP.NET Core MVC Plugin Sample

    main

    This sample demonstrates how an ASP.NET Core application can load MVC controllers from assemblies that are not known at build time. The sample consists of two projects:

    1. MvcWebApp: An ASP.NET Core application that scans for a plugins folder in its base directory and attempts to load any plugins found there.
    2. MvcAppPlugin1: A plugin project that implements MVC controllers.

    To run the sample, execute the following commands from the sample folder:

    dotnet restore
    dotnet run --project MvcApp/