+++
title = "#2 Plugin-Based Web App in Dotnet - Loading Plugins"
date = "2024-02-4T00:00:00+00:00"
author = "the1mason"
authorTwitter = "the0mason" #do not include @
tags = ["dotnet", "web", "prototype"]
keywords = ["prototype", "dotnet", "guide", "plugins", "plugin-based", "web application", "ASP.NET", "C#", ".NET 8", "Programming", "Software Architecture"]
description = "The second part of my journey to create a modular based web app. In this part I'll tell you how do you load plugins during runtime."
showFullContent = false
readingTime = true
hideComments = false
draft = false
+++

<script src="/js/repo-card.js"></script>
<div class="repo-card" data-repo="the1mason/Prototype.ModularMVC" data-theme="dark-theme"></div>

# THIS IS A WORK IN PROGRESS ARTICLE! I AM ACTIVELY EDITING IT RIGHT NOW!

there are LOTS of typos as I have not checked this text in grammarly yet...

# Chapters

Writing those takes time. Expect to see one published per one-two weeks.  

1. [Idea, Stack](/posts/modular-app-1)

2. Loading plugins

3. ~~PluginBase, IPlugin~~

4. ~~Creating plugin, DependencyInjection~~

5. ~~Controllers, Views~~

6. ~~Hooks and Triggers - better event system~~

7. ~~Advanced: Unit tests, unloading plugins~~

---

# Dynamic loading

C# being a compiled language requires to dynamically load compiled assemblies in memory using **reflection** in order to modify the application. In this part I will tell you about .net's built-in methods to do this. Along with achieving the needed result: assemblies being loaded, - we'll solve problems like findng plugin's main `.dll`, dependency resolving and avoiding dependency version conflicts.

---

# What do we load? (Plugin folder, Manifest.json)

First of all, we need to determine where do we look for the `.dll` files that we want to load. My solution is simple: We will create a folder `/Plugins` in the app's current directory and store plugin's files in folders with this structure:

{{< code language="txt" title="Example directory tree" id="1" expand="Show" collapse="Hide" isCollapsed="false" >}}

Plugins
- ExamplePlugin
- - ExamplePlugin.dll
- - ExamplePlugin.deps.json
- - ExamplePluginDependency.dll
- - manifest.json
- - Assets/...
- NicePlugin
- - NicePlugin.dll
- - NicePlugin.deps.json
- - manifest.json

{{< /code >}}

Now we need a way to determine: which `.dll` file is the main plugin file. I decided to create a `manifest.json` file - a file that contains plugin's metadata: name, version, author and main `.dll` file name.

Our first step would be to load and parse all `manifest.json` files, then we load  main `.dll` files, referenced in manifests. 

{{< code language="json" title="manifest.json" id="1" expand="Show" collapse="Hide" isCollapsed="false" >}}

{
  "id": "example.plugin",
  "version": "1.23.4:5678",
  "name": "Example Plugin",
  "assembly": "EamplePlugin.dll"
}

{{< /code >}}

[ManifestLoader.cs](https://github.com/the1mason/Prototype.ModularMVC/blob/869cebee5e55cb3e8bf8f228d712bbda621d6b17/Prototype.ModularMVC.App/Prototype.ModularMVC.PluginBase/Impl/ManifestLoaders/ManifestLoader.cs)

[Program.cs](https://github.com/the1mason/Prototype.ModularMVC/blob/869cebee5e55cb3e8bf8f228d712bbda621d6b17/Prototype.ModularMVC.App/Prototype.ModularMVC.App.Server/Program.cs)


---

# AssemblyLoadContext

*image placeholder: assemblyLoadContextDiagram.svg*

The next question is how are we going to load those `.dll` files, that we got from `manifest.json` files?

`AssemblyLoadContext` is used to isolate groups of loaded assemblies from each other to resolve version conflicts. Let's say that `PluginA` references `CoolLibrary.Abstractions v.3.1`, while PluginB is referencing the same `CoolLibrary.Abstractions`, but `v.2.5.` In order for this to work, we will create an AssemblyLoadContext for each main `.dll` plugin and load it's dependencies from this context.

Oh right! Plugins have dependencies, but `AssemblyLoadContext` WOULD NOT find those automagically. We will need to explicitly tell it to look for them. In order to do so, we will create a `PluginLoadContext : AssemblyLoadContext`. This class will use an `AssemblyDependencyResolver` class that will read `.deps.json` file of a provided file and resolve dependencies from it.

*.deps.json is created automatically when you compile any csharp project and includes all your dependencies!*

{{< code language="csharp" title="PluginLoadContext" id="2" expand="Show" collapse="Hide" isCollapsed="false" >}}

public class PluginLoadContext : AssemblyLoadContext
{
    private AssemblyDependencyResolver _resolver;

    public PluginLoadContext(string pluginPath) : base()
    {
        // initialize the dependency resolver
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }

    protected override Assembly Load(AssemblyName assemblyName)
    {
        // use the dependency resolver to find all depended libraries and load them
        string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
        // if successful, load the main assembly
        if (assemblyPath != null)
        {
            return LoadFromAssemblyPath(assemblyPath);
        }

        return null!;
    }

    // essentially, dark magic. don't think about it 😛
    protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
    {
        string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
        if (libraryPath != null)
        {
            return LoadUnmanagedDllFromPath(libraryPath);
        }

        return IntPtr.Zero;
    }
}

{{< /code >}}

[PluginLoadContext.cs](https://github.com/the1mason/Prototype.ModularMVC/blob/869cebee5e55cb3e8bf8f228d712bbda621d6b17/Prototype.ModularMVC.App/Prototype.ModularMVC.PluginBase/PluginLoadContext.cs)

---

# Project template for the plugin file

This part is more about what we will need in the future. As our main application uses the `MVC` pattern, our plugins would have *views* and *controllers*. We should modify our `.csproj` as follows:

{{< code language="xml" title="ExamplePlugin.cshtml" id="3" expand="Show" collapse="Hide" isCollapsed="false" >}}

<Project Sdk="Microsoft.NET.Sdk.Razor">

	<PropertyGroup>
		<TargetFramework>net8.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<EnableDynamicLoading>true</EnableDynamicLoading>
		<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
	</PropertyGroup>
	
	<ItemGroup>
		<Reference Include="Prototype.ModularMVC.PluginBase">
			<Private>false</Private>
			<HintPath>..\Prototype.ModularMVC.App\Prototype.ModularMVC.PluginBase\bin\Debug\net8.0\Prototype.ModularMVC.PluginBase.dll</HintPath>
		</Reference>
	</ItemGroup>

	<ItemGroup>
		<FrameworkReference Include="Microsoft.AspNetCore.App">
		</FrameworkReference>
	</ItemGroup>

	<ItemGroup>
	  <Content Update="manifest.json">
	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
	  </Content>
	</ItemGroup>

</Project>

{{< /code >}}

Explanation:

- `Microsoft.NET.Sdk.Razor` will make sure that our `.cshtml` razor pages will be compiled to classes and added to the output assembly.
- `EnableDynamicLoading` hints that this library is intended to be loaded dynamically, rather than executed or referenced directly.
- `Microsoft.AspNetCore.App` framework reference will allow us to use ASP.NET-specific classes.
- `AddRazorSupportForMvc` speaks for itself, doesn't it?
- `manifest.json` should be created and filled by the author. Then copied to the output directory. 
- Referencing `PluginBase` as well as the `PluginBase` library itself will be explained in the next chapter.

---

# Conclusion

At this point, we can locate and load our plugins with their dependencies at runtime.  
This article is based on [this microsoft learn article](https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support), as well as my code base. I have added some abstractions to make it easier to extend my loading mechanism, so the original article might be a little easier to understand. I didn't simplified my examples because I think that anybody aiming to create the same plugin system as I did will eventually reach similar conclusions and will make similar modifications to the code from the original article.

- [Program.cs](https://github.com/the1mason/Prototype.ModularMVC/blob/869cebee5e55cb3e8bf8f228d712bbda621d6b17/Prototype.ModularMVC.App/Prototype.ModularMVC.App.Server/Program.cs) - using manifest loader and plugin loader to load plugins  
- [ManifestLoader.cs](https://github.com/the1mason/Prototype.ModularMVC/blob/869cebee5e55cb3e8bf8f228d712bbda621d6b17/Prototype.ModularMVC.App/Prototype.ModularMVC.PluginBase/Impl/ManifestLoaders/ManifestLoader.cs) - loads manifests  
- [ManifestBasedPluginLoader.cs](https://github.com/the1mason/Prototype.ModularMVC/blob/869cebee5e55cb3e8bf8f228d712bbda621d6b17/Prototype.ModularMVC.App/Prototype.ModularMVC.PluginBase/Impl/PluginLoaders/ManifestBasedPluginLoader.cs) - loads plugins, using paths from the `ManifestLoader`.  
- [PluginLoadContext.cs](https://github.com/the1mason/Prototype.ModularMVC/blob/869cebee5e55cb3e8bf8f228d712bbda621d6b17/Prototype.ModularMVC.App/Prototype.ModularMVC.PluginBase/PluginLoadContext.cs) - Implementation of an `AssemblyLoadContext.cs` that resolves dependencies.  


In the next article, we will talk about communication between the server and loaded plugins, `PluginBase` library, and using dependency injection to tie everything together.


