X39.Hosting.Modularization
2.0.0.34
dotnet add package X39.Hosting.Modularization --version 2.0.0.34
NuGet\Install-Package X39.Hosting.Modularization -Version 2.0.0.34
<PackageReference Include="X39.Hosting.Modularization" Version="2.0.0.34" />
paket add X39.Hosting.Modularization --version 2.0.0.34
#r "nuget: X39.Hosting.Modularization, 2.0.0.34"
// Install X39.Hosting.Modularization as a Cake Addin #addin nuget:?package=X39.Hosting.Modularization&version=2.0.0.34 // Install X39.Hosting.Modularization as a Cake Tool #tool nuget:?package=X39.Hosting.Modularization&version=2.0.0.34
Semantic Versioning
This library follows the principles of Semantic Versioning. This means that version numbers and the way they change convey meaning about the underlying changes in the library. For example, if a minor version number changes (e.g., 1.1 to 1.2), this indicates that new features have been added in a backwards-compatible manner.
Overview
The X39.Hosting.Modularization
library simplifies adding modular functionality (plugins) to .NET applications. By
leveraging this library, you can dynamically load, unload, and manage modules that extend the core features of your
software.
Installing the Template
The library provides a project template to streamline setup. To install the template via the .NET CLI, run:
dotnet new install X39.Hosting.Modularization.TemplatePackage
Quickstart
To quickly get started, follow the example below:
// Program.cs
var hostBuilder = Host.CreateDefaultBuilder(args);
hostBuilder.UseModularization(Path.GetFullPath("Modules"));
hostBuilder.ConfigureServices(collection => collection.AddHostedService<Worker>());
var host = hostBuilder.Build();
host.Run();
// Worker.cs
public class Worker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _moduleLoader.LoadAllAsync(stoppingToken);
}
}
Note: The plugin directories are defined once during application startup.
Note: There are additional overloads and ways to load modules, this is just one of them. Follow the code-documentation for further information.
Features
Runtime Module Loading
Dynamically load plugin-based assemblies at runtime. Each module can extend your application's functionality.Module Configuration via
module.json
Every module has amodule.json
file, where you can define a unique module GUID, licenses, and other important metadata.Automatic Dependency Resolution
Modules can declare dependencies (on other modules) in themodule.json
file, and the library will automatically handle these dependencies during module loading.Module Unloading
To unload a module, simply callUnload()
on theModuleContext
. Keep in mind that any references to types from the module will prevent it from fully unloading.Clear Module Entry Point
Every module must implement theIModuleMain
interface once, similar to theMain()
method in a standard .NET application. This provides a clear entry point with dependency injection support.Dependency Injection Support
Modules are instantiated using theIServiceProvider
via dependency injection. Modules can also register services that other dependent modules can consume.
FAQ
Why can't I replace or delete a library after unloading my module?
Unloading a module works, but the .NET CLR doesn’t support fully "unloading" assemblies in all situations. If any part of your application holds a reference to a type from the unloaded module (e.g., via caching), the assembly will remain in memory, preventing you from replacing or deleting it.
To troubleshoot this, check for objects still in memory that may be holding references to the module (e.g., in Rider via the Memory Tab, or Visual Studio's .NET object snapshot tools). Caching is a common culprit here.
Why doesn't my renamed assembly load?
The library does not automatically detect the module entrypoint for security reasons.
If you rename an assembly, you must also update the module.json
file to reflect the
new assembly name.
Why aren't my dependencies available in the module?
Each module is treated as an isolated unit with its own dependency injection container. This means it won't automatically integrate with the main application's dependency tree. Ensure that your dependencies are explicitly defined in your module like so:
public sealed class ModuleMain : IModuleMain
{
private readonly IMyFancyService _myFancyService;
public ModuleMain(IMyFancyService myFancyService)
{
_myFancyService = myFancyService;
}
public ValueTask ConfigureServicesAsync(
IServiceCollection serviceCollection,
CancellationToken cancellationToken
)
{
serviceCollection.AddSingleton(_myFancyService);
return ValueTask.CompletedTask;
}
// [...]
}
How can I get the GUID of the module without injecting the ModuleLoader
?
Yes, you can access the module's GUID, as specified in the module.json
,
by injecting the ModuleGuid
class from the
X39.Hosting.Modularization.Abstraction
namespace:
public sealed class ModuleMain : IModuleMain
{
private readonly Guid _moduleGuid;
public ModuleMain(ModuleGuid moduleGuid)
{
_moduleGuid = moduleGuid;
}
// [...]
}
Examples
Sample projects are available in the
GitHub repository.
These examples demonstrate how to build and extend applications using the Modularization
library.
If you feel like there is some examples missing, feel free to create tickets or supply your own.
License
The entire project, excluding the DotNet template, is released under the LGPLv3 license. The DotNet template, along with all generated code from it, is considered public domain and may be used or licensed freely as per your needs.
Contributing
Contributions are welcome! Please submit a pull request or create a discussion to discuss any changes you wish to make.
Code of Conduct
Be excellent to each other.
Contributors Agreement
First of all, thank you for your interest in contributing to this project! Please add yourself to the list of contributors in the CONTRIBUTORS file when submitting your first pull request. Also, please always add the following to your pull request:
By contributing to this project, you agree to the following terms:
- You grant me and any other person who receives a copy of this project the right to use your contribution under the
terms of the GNU Lesser General Public License v3.0.
- You grant me and any other person who receives a copy of this project the right to relicense your contribution under
any other license.
- You grant me and any other person who receives a copy of this project the right to change your contribution.
- You waive your right to your contribution and transfer all rights to me and every user of this project.
- You agree that your contribution is free of any third-party rights.
- You agree that your contribution is given without any compensation.
- You agree that I may remove your contribution at any time for any reason.
- You confirm that you have the right to grant the above rights and that you are not violating any third-party rights
by granting these rights.
- You confirm that your contribution is not subject to any license agreement or other agreement or obligation, which
conflicts with the above terms.
This is necessary to ensure that this project can be licensed under the GNU Lesser General Public License v3.0 and that a license change is possible in the future if necessary (e.g., to a more permissive license). It also ensures that I can remove your contribution if necessary (e.g., because it violates third-party rights) and that I can change your contribution if necessary (e.g., to fix a typo, change implementation details, or improve performance). It also shields me and every user of this project from any liability regarding your contribution by deflecting any potential liability caused by your contribution to you (e.g., if your contribution violates the rights of your employer). Feel free to discuss this agreement in the discussions section of this repository, i am open to changes here (as long as they do not open me or any other user of this project to any liability due to a malicious contribution).
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.DependencyInjection (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.1)
- X39.Hosting.Modularization.Abstraction (>= 2.0.0.34)
- X39.Util (>= 1.0.0.50)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.0.0.34 | 103 | 9/28/2024 |
1.0.0.33 | 103 | 9/16/2024 |
1.0.0.32-da89c1738d81a83aaf... | 63 | 9/16/2024 |
1.0.0.30-d7feb86db9bc881b33... | 65 | 9/16/2024 |
1.0.0.29-d9f5be5a6cc6bd85d6... | 65 | 9/16/2024 |
0.0.1.27 | 185 | 9/21/2023 |
0.0.1.26 | 162 | 5/4/2023 |
0.0.1.24 | 400 | 10/16/2022 |
0.0.1.23 | 353 | 10/16/2022 |
0.0.1.22 | 363 | 10/16/2022 |
0.0.1.21 | 371 | 10/15/2022 |
0.0.1.20 | 373 | 10/15/2022 |
0.0.1.19 | 384 | 10/15/2022 |
0.0.1.18 | 381 | 10/1/2022 |
0.0.1.17 | 357 | 10/1/2022 |
0.0.1.16 | 358 | 10/1/2022 |
0.0.1.15 | 382 | 9/24/2022 |
0.0.1.14 | 381 | 9/21/2022 |
0.0.1.13 | 365 | 9/20/2022 |
0.0.1.12 | 394 | 9/20/2022 |
0.0.1.11 | 385 | 9/15/2022 |
0.0.1.10 | 408 | 9/14/2022 |
0.0.1.9 | 408 | 9/6/2022 |
0.0.1.8 | 382 | 9/5/2022 |
0.0.1.7 | 401 | 9/2/2022 |
0.0.1.6 | 382 | 9/1/2022 |
0.0.1 | 403 | 9/1/2022 |