Article... x3 God save me

This commit is contained in:
the1mason 2024-01-22 15:11:55 -08:00
parent e7776fbb2c
commit 7dc358e713
3 changed files with 51 additions and 30 deletions

View File

@ -9,7 +9,7 @@ color = "red"
<br>
<br>
<div style="text-align: center; display: flex; justify-content: center;">
<img src="crabHdFixed512signal.png" style="height: 150px; border: 7px solid rgba(0, 0, 0, 0); border-radius: 50%; box-shadow: 0 0 0 5px #FF6266;"/>
<img src="crabHdFixed512signal.png" style="height: 150px; border: 7px solid rgba(0, 0, 0, 0); border-radius: 0%; box-shadow: 0 0 0 5px #FF6266;"/>
</div>
<br>
<h1 style="text-align: center; font-size: 45px; margin: 0px;">Vladislav Belkov</h1>

View File

@ -16,56 +16,77 @@ draft = false
### Table of Contents
- [Introduction](#introduction)
- [Why](#why)
- [How](#how)
- [The Prototype](#the-prototype)
- - [IPlugin](#iplugin)
- - [Loading Plugins](#loading-plugins)
- - [Hooks and Triggers](#hooks-and-triggers)
- [Sources](#sources)
# Introduction
This post is about my experience of making a prototype of a web app with plugin support as well as my reasoning. As a result, this app could be extended by adding compiled `.dll` class libraries into a plugins folder. I've made it possible to load not only classes but also views and API controllers. You can check out the final version of this prototype in this [GitHub Repo](//github.com/the1mason/prototype.modularmvc).
Also right now I'm building a web application, using similar concepts. As of now, it's not on github, but you can find it [here](//git.the1mason.com/the1mason/octocore).
Have you ever heard of plugins? These are loadable libraries, extending your application.
This article is an overview of my plugin-based web application prototype and mechanisms behind it's features as well as my thought process and decision making during development. This article is, essentially, a step by step guide to making your own plugin-based web app prototype.
<script src="/js/repo-card.js"></script>
<div class="repo-card" data-repo="the1mason/Prototype.ModularMVC" data-theme="dark-theme"></div>
*This article assumes some knowledge of programming and design patterns.*
# Why
# Problem
Self-hosted web applications can solve different problems and be of use to a variety of different people with slightly different needs. For this to work, I think that such an application should provide an option to extend its functionality. This would allow other people to build an ecosystem of different extensions.
Self-hosted web applications can solve different problems and be of use to a variety of different people with slightly different needs. For this to work, I think that such an application should provide an option to extend its functionality. This would allow other people to build an ecosystem of different extensions around it.
# Stack
# Choose your stack!
![C#, MVC, HTMX](/posts/modular-app/stack.svg)
Making a C# web application was my goal from the begginning. That's my backend language for this project, but why MVC and what is HTMX?
Let's have a quick look at worthy alternatives, and then I'll explain my choices.
---
`Blazor WASM` does not support runtime assembly loading, which makes client extension impossible. It has [Lazy loading](https://learn.microsoft.com/en-us/aspnet/core/blazor/webassembly-lazy-load-assemblies?view=aspnetcore-8.0), but it still requires these assemblies to be defined in the project file, which is not viable for our case.
**C#**
`WebApi + <name a JS framework>` is also not an option. It would require plugins to be written in two languages. Also, the client would have to be rebuilt after each plugin installation.
I'm a dotnet developer and I write C# code for living. This project is as much of an excersise for me as it is an interesting design prototype for C#, that hasn't been described in detail as much online.
So, what are MVC and HTMX?
I haven't seen such plugin-based sites written in C#. There are some projects, using plugin based architecture... Well, there's even a [Microsoft Learn Article](https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support) about building such an app!
`ASP.NET MVC` is an older framework that uses `Razor Pages` to render HTML on a server and return it to the client. But it has a significant problem: Lack of reactivity. Each user's action would have to be processed on the server like in the good old days...
So in order for the app to be usable, I have decided to go with `HTMX`:
**Q:** Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?
> htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
> *— from [htmx.org](htmx.org)*
**A:** You see... there's a problem: Neither `learn.microsoft.com` nor any other webside covers dynamically updating web interface with plugins! If you want to learn about it, it's the right place. Also just loading libraries isn't enough because app also has to provide some ways for plugins to interact with it, which is also covered here!
---
**MVC with HTMX**
ASP.NET MVC is a web framework, that incorporates the MVC design pattern and uses SSR (Server Side Rendering) to serve content. It is a perfect fit for the HTMX library. This is a compact JavaScript library that extends basic HTML by adding some custom attributes just enough to build your app around it. You can distribute events, make AJAX requests and build truly dynamic app by using as little JS as possible.
<div style="display: flex; justify-content: center">
<img src="/posts/modular-app/createdwith.jpeg" style="height: 50px;"/>
<img src="/posts/modular-app/createdwith.jpeg" style="height: 100px;"/>
</div>
<br>
This means, that I will use razor pages to generate an HTML body with HTMX tags, and return it to the client. The client would then read HTML, executing HTMX tags. Ain't that awesome?
HTMX uses [Hypermedia as the Engine of Application State (HATEOAS)](https://htmx.org/essays/hateoas/) - it's a principle that leaves all state handling to the server, providing the client only with a set of actions that it can take.
Your regular SPA will get raw data from the server (like bank balance) and based on it, it will show or hide certain actions (like we won't show the withdrawal option if balance is 0 or less). With HATEOAS, the server just won't give the link to withdraw money, making this action impossible in the first place.
# How
---
[This](https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support) article gives the base for plugin loading. It doesn't cover the ASP.NET part but otherwise is very helpful.
Still, I will explain basic concepts here. Just to have everything in one place.
# Loading plugins
C#, being a compiled language, can't be extended as easily as interpreted languages like Pytnon or PHP. To load plugins, we will need to load precompiled libraries dynamically after the app is compiled. To do this, [Microsoft Learn Article, mentioned before](https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support) suggests using custom AssemblyLoadContext with AssemblyDependencyResolver.
> Wait, wait, wait! Using... what? I'm pretty sure you could just `Assembly.Load()` stuff, right?
Not so easy! If you want to build a really working plugin system, you need a way to determine: whether this assembly has dependencies or not, and what are they.
Let's imagine, that our plugin uses `Newtonsoft.Json` library - one of the all-time most popular C# libraries. Should we load it together with the plugin and how do we find it?
C# has a built-in mechanism to resolve dependencies. When you compile your project, aside from `Project.Name.dll` you would have `Project.Name.deps.json` file, that will include paths to all it's dependencies! That's where `AssemblyDependencyResolver` comes in. It'll find all of plugin's `.dll` dependencies and load those as well.
> And also! What it two plugins will have the same library, but conflicting versions? Would we be able to load to of the same assemblies?
No! And yes.
`AssemblyLoadContext` is used exactly for this. Along with `AssemblyDependencyResolver`, it will create an isolated context with current assembly and all it's dependencies. This will allow multiple plugins to have same dependencies with different versions.
There's an example of such custom AssemblyLoadContext [Click Me](https://github.com/the1mason/Prototype.ModularMVC/blob/main/Prototype.ModularMVC.App/Prototype.ModularMVC.PluginBase/PluginLoadContext.cs) and also an example of this context being used [Click Me](https://github.com/the1mason/Prototype.ModularMVC/blob/main/Prototype.ModularMVC.App/Prototype.ModularMVC.PluginBase/Impl/PluginLoaders/ManifestBasedPluginLoader.cs#L89).
---
# Plugin - App interaction
So now we figured out how we load stuff. Now: how do we interact with the app from out plugins?
First, we make all plugins to referense the `Prototype.PluginBase` project. This project will provide types that both plugin and our server can understand. We'll build communication using those.
**Dependency Injection**
Before the app is built and ran,

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB