Ahh shit... Here we go again

This commit is contained in:
the1mason 2024-01-22 17:00:20 -08:00
parent 7dc358e713
commit 15d71bcf51
49 changed files with 5513 additions and 10 deletions

View File

@ -1,5 +1,5 @@
+++
title = "Plugin-Based Web Application in Dotnet"
title = "#1 Plugin-Based Web App in Dotnet - The Idea"
date = "2024-01-20T00:00:00+00:00"
author = "the1mason"
authorTwitter = "the0mason" #do not include @
@ -13,22 +13,39 @@ hideComments = false
draft = false
+++
### Table of Contents
<script src="/js/repo-card.js"></script>
<div class="repo-card" data-repo="the1mason/Prototype.ModularMVC" data-theme="dark-theme"></div>
- [Introduction](#introduction)
# Chapters
Writing those takes time. Expect to see one published per one-two weeks.
1. Idea, Stack
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~~
# Introduction
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.
This series of articles 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. These articles are a step by step guide to making your own plugin-based web app prototype.
*This article assumes some knowledge of programming and design patterns.*
*I assume some that readers have some knowledge of C# and design patterns*
# 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 around it.
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. For example, a shopping website might have plugins for different payment systems, or a comment section under the product page. For me this also means, that instead of making one feature-rich website, that would be so specific to my needs, that it wouldn't of any use to anyone but me, I can write a bunch of smaller modules, that could be used by someone, without having to configure other modules.
# Choose your stack!
# Choosing my stack
![C#, MVC, HTMX](/posts/modular-app/stack.svg)
@ -40,9 +57,9 @@ I'm a dotnet developer and I write C# code for living. This project is as much o
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!
**Q:** Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?
> **Q:** Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?
**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!
> **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!
---
@ -58,6 +75,17 @@ ASP.NET MVC is a web framework, that incorporates the MVC design pattern and use
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.
HTMX would allow this app to be extended more easily as well. Most of the modern JS frameworks require transpiling, bundling and other sorts of stuff. This means that when a plugin is installed the client is most likely will have to be rebuilt. This is slow and needs additional dependencies.
> Have you heard about Blazor WASM? You can just write client code in C#!
Blazor WASM does not support dynamic loading for plugins. Because of that, plugins won't be able to extend the client. Also it's initial load time is stupidly slow.
---
The next article will cover the following topocs: Loading plugins in runtime, creating plugin's instances, app-plugin communication. I'll have the link here when I'm done writing it!
<!--
---
# Loading plugins
@ -89,4 +117,4 @@ First, we make all plugins to referense the `Prototype.PluginBase` project. This
**Dependency Injection**
Before the app is built and ran,
Before the app is built and ran, -->

View File

@ -0,0 +1,71 @@
+++
title = "Plugin-Based Web Application in Dotnet"
date = "2024-01-20T00:00:00+00:00"
author = "the1mason"
authorTwitter = "the0mason" #do not include @
cover = "posts/modular-app/title.svg"
tags = ["dotnet", "web", "prototype"]
keywords = ["prototype", "dotnet", "guide", "plugins", "plugin-based", "web application", "ASP.NET", "C#", ".NET 8", "Programming", "Software Architecture"]
description = "Have you ever thought about making a web application, that could be easily extended by third-party developers? I've been thinking about making this app for a while, so here's my experience..."
showFullContent = false
readingTime = true
hideComments = false
draft = true
+++
### 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).
<script src="/js/repo-card.js"></script>
<div class="repo-card" data-repo="the1mason/Prototype.ModularMVC" data-theme="dark-theme"></div>
# Why
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.
# 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.
`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.
So, what are MVC and HTMX?
`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`:
> 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)*
<div style="display: flex; justify-content: center">
<img src="/posts/modular-app/createdwith.jpeg" style="height: 50px;"/>
</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?
# 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.

175
public/404.html Normal file
View File

@ -0,0 +1,175 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>404 Page not found :: the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/404.html" />
<link rel="stylesheet" href="/the1mason.com/styles.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="website" />
<meta property="og:title" content="404 Page not found">
<meta property="og:description" content="" />
<meta property="og:url" content="the1mason.com/404.html" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/img/favicon/red.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<div class="post">
<h1 class="post-title">404 — Page not found...</h1>
<div class="post-content">
<a href="/">Back to home page&nbsp;</a>
</div>
</div>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

BIN
public/bigcrab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

45
public/bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,176 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Categories :: the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/categories/" />
<link rel="stylesheet" href="/the1mason.com/styles.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Categories">
<meta property="og:description" content="" />
<meta property="og:url" content="the1mason.com/categories/" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/img/favicon/red.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
<link href="the1mason.com/categories/index.xml" rel="alternate" type="application/rss+xml" title="the1mason" />
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<div class="terms">
<h1>Categories</h1>
<ul>
</ul>
</div>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Categories on the1mason</title>
<link>the1mason.com/categories/</link>
<description>Recent content in Categories on the1mason</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language><atom:link href="the1mason.com/categories/index.xml" rel="self" type="application/rss+xml" />
</channel>
</rss>

BIN
public/crab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
public/crabr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

1191
public/css/red-local.css Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

256
public/index.html Normal file
View File

@ -0,0 +1,256 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="generator" content="Hugo 0.121.2">
<title>the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="- .net developer" />
<meta name="keywords" content="" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/" />
<link rel="stylesheet" href="/the1mason.com/css/red-local.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="website" />
<meta property="og:title" content="the1mason">
<meta property="og:description" content="- .net developer" />
<meta property="og:url" content="the1mason.com/" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/img/favicon/red.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
<link href="the1mason.com/index.xml" rel="alternate" type="application/rss+xml" title="the1mason" />
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<div class="index-content ">
<hr>
<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: 0%; box-shadow: 0 0 0 5px #FF6266;"/>
</div>
<br>
<h1 style="text-align: center; font-size: 45px; margin: 0px;">Vladislav Belkov</h1>
<p style="text-align: center; margin: 0px;">Dotnet developer</p>
<br>
<br>
<hr>
<h2 id="welcome">Welcome!</h2>
<p>This is my site. I don&rsquo;t post here often, so feel free to check out my <a href="//github.com/the1mason">GitHub</a>.<br>
You can also check any of my posts below.</p>
</div>
<div class="posts">
<article class="post on-list">
<h1 class="post-title">
<a href="the1mason.com/posts/modular-app-1/">#1 Plugin-Based Web App in Dotnet - The Idea</a>
</h1>
<div class="post-meta">
<time class="post-date">
2024-01-20 ::
</time>
<span class="post-author">the1mason</span>
</div>
<span class="post-tags">
#<a href="the1mason.com/tags/dotnet/">dotnet</a>&nbsp;
#<a href="the1mason.com/tags/web/">web</a>&nbsp;
#<a href="the1mason.com/tags/prototype/">prototype</a>&nbsp;
</span>
<img src="the1mason.com/posts/modular-app/title.svg"
class="post-cover"
alt="#1 Plugin-Based Web App in Dotnet - The Idea"
title="Cover Image" />
<div class="post-content">
Have you ever thought about making a web application, that could be easily extended by third-party developers? I&rsquo;ve been thinking about making this app for a while, so here&rsquo;s my experience&hellip;
</div>
<div>
<a class="read-more button" href="the1mason.com/posts/modular-app-1/">Read more →</a>
</div>
</article>
<div class="pagination">
<div class="pagination__buttons">
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

123
public/index.xml Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>the1mason</title>
<link>the1mason.com/</link>
<description>Recent content on the1mason</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<lastBuildDate>Sat, 20 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="the1mason.com/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>#1 Plugin-Based Web App in Dotnet - The Idea</title>
<link>the1mason.com/posts/modular-app-1/</link>
<pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate>
<guid>the1mason.com/posts/modular-app-1/</guid>
<description>Chapters Writing those takes time. Expect to see one published per one-two weeks.
Idea, Stack
Loading plugins
PluginBase, IPlugin
Creating plugin, DependencyInjection
Controllers, Views
Hooks and Triggers - better event system
Advanced: Unit tests, unloading plugins
Introduction Have you ever heard of plugins? These are loadable libraries, extending your application.
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development.</description>
<content>&lt;script src=&#34;the1mason.com/js/repo-card.js&#34;&gt;&lt;/script&gt;
&lt;div class=&#34;repo-card&#34; data-repo=&#34;the1mason/Prototype.ModularMVC&#34; data-theme=&#34;dark-theme&#34;&gt;&lt;/div&gt;
&lt;h1 id=&#34;chapters&#34;&gt;Chapters&lt;/h1&gt;
&lt;p&gt;Writing those takes time. Expect to see one published per one-two weeks.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Idea, Stack&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Loading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;PluginBase, IPlugin&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Creating plugin, DependencyInjection&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Controllers, Views&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Hooks and Triggers - better event system&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Advanced: Unit tests, unloading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Have you ever heard of plugins? These are loadable libraries, extending your application.&lt;br&gt;
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development. These articles are a step by step guide to making your own plugin-based web app prototype.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I assume some that readers have some knowledge of C# and design patterns&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;problem&#34;&gt;Problem&lt;/h1&gt;
&lt;p&gt;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. For example, a shopping website might have plugins for different payment systems, or a comment section under the product page. For me this also means, that instead of making one feature-rich website, that would be so specific to my needs, that it wouldn&amp;rsquo;t of any use to anyone but me, I can write a bunch of smaller modules, that could be used by someone, without having to configure other modules.&lt;/p&gt;
&lt;h1 id=&#34;choosing-my-stack&#34;&gt;Choosing my stack&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;the1mason.com/posts/modular-app/stack.svg&#34; alt=&#34;C#, MVC, HTMX&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;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&amp;rsquo;t been described in detail as much online.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t seen such plugin-based sites written in C#. There are some projects, using plugin based architecture&amp;hellip; Well, there&amp;rsquo;s even a &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support&#34;&gt;Microsoft Learn Article&lt;/a&gt; about building such an app!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; You see&amp;hellip; there&amp;rsquo;s a problem: Neither &lt;code&gt;learn.microsoft.com&lt;/code&gt; nor any other webside covers dynamically updating web interface with plugins! If you want to learn about it, it&amp;rsquo;s the right place. Also just loading libraries isn&amp;rsquo;t enough because app also has to provide some ways for plugins to interact with it, which is also covered here!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;MVC with HTMX&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div style=&#34;display: flex; justify-content: center&#34;&gt;
&lt;img src=&#34;the1mason.com/posts/modular-app/createdwith.jpeg&#34; style=&#34;height: 100px;&#34;/&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;HTMX uses &lt;a href=&#34;https://htmx.org/essays/hateoas/&#34;&gt;Hypermedia as the Engine of Application State (HATEOAS)&lt;/a&gt; - it&amp;rsquo;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&amp;rsquo;t show the withdrawal option if balance is 0 or less). With HATEOAS, the server just won&amp;rsquo;t give the link to withdraw money, making this action impossible in the first place.&lt;/p&gt;
&lt;p&gt;HTMX would allow this app to be extended more easily as well. Most of the modern JS frameworks require transpiling, bundling and other sorts of stuff. This means that when a plugin is installed the client is most likely will have to be rebuilt. This is slow and needs additional dependencies.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have you heard about Blazor WASM? You can just write client code in C#!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Blazor WASM does not support dynamic loading for plugins. Because of that, plugins won&amp;rsquo;t be able to extend the client. Also it&amp;rsquo;s initial load time is stupidly slow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The next article will cover the following topocs: Loading plugins in runtime, creating plugin&amp;rsquo;s instances, app-plugin communication. I&amp;rsquo;ll have the link here when I&amp;rsquo;m done writing it!&lt;/p&gt;
&lt;!--
---
# Loading plugins
C#, being a compiled language, can&#39;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.
&gt; Wait, wait, wait! Using... what? I&#39;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&#39;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&#39;s dependencies! That&#39;s where `AssemblyDependencyResolver` comes in. It&#39;ll find all of plugin&#39;s `.dll` dependencies and load those as well.
&gt; 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&#39;s dependencies. This will allow multiple plugins to have same dependencies with different versions.
There&#39;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&#39;ll build communication using those.
**Dependency Injection**
Before the app is built and ran, --&gt;</content>
</item>
</channel>
</rss>

80
public/js/repo-card.js Normal file
View File

@ -0,0 +1,80 @@
window.tarptaeya = {};
window.tarptaeya.reloadRepoCards = async function() {
const CACHE_TIMEOUT = 60000;
async function get(url) {
const now = new Date().getTime();
const prevResp = JSON.parse(localStorage.getItem(url));
if (prevResp && Math.abs(now - prevResp.time) < CACHE_TIMEOUT) {
return prevResp.data;
}
const resp = await fetch(url);
const json = await resp.json();
localStorage.setItem(url, JSON.stringify({time: now, data: json}));
return json;
}
const emojis = await get('https://api.github.com/emojis');
const colors = await get('https://raw.githubusercontent.com/ozh/github-colors/master/colors.json');
const themes = {
'light-default': {
background: 'white',
borderColor: '#e1e4e8',
color: '#586069',
linkColor: '#0366d6',
},
'dark-theme': {
background: 'rgb(13, 17, 23)',
borderColor: 'rgb(48, 54, 61)',
color: 'rgb(139, 148, 158)',
linkColor: 'rgb(88, 166, 255)',
}
};
for (const el of document.querySelectorAll('.repo-card')) {
const name = el.getAttribute('data-repo');
const theme = themes[el.getAttribute('data-theme') || 'light-default'];
const data = await get(`https://api.github.com/repos/${name}`);
data.description = (data.description || '').replace(/:\w+:/g, function(match) {
const name = match.substring(1, match.length - 1);
const emoji = emojis[name];
if (emoji) {
return `<span><img src="${emoji}" style="width: 1rem; height: 1rem; vertical-align: -0.2rem;" alt="${name}"></span>`;
}
return match;
});
el.innerHTML = `
<div style="font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; border: 1px solid ${theme.borderColor}; border-radius: 6px; background: ${theme.background}; padding: 16px; font-size: 14px; line-height: 1.5; color: #24292e;">
<div style="display: flex; align-items: center;">
<svg style="fill: ${theme.color}; margin-right: 8px;" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"></path></svg>
<span style="font-weight: 600; color: ${theme.linkColor};">
<a style="text-decoration: none; color: inherit;" href="${data.html_url}">${data.name}</a>
</span>
</div>
<div style="display: ${data.fork ? 'block' : 'none'}; font-size: 12px; color: ${theme.color};">Forked from <a style="color: inherit; text-decoration: none;" href="${data.fork ? data.source.html_url : ''}">${data.fork ? data.source.full_name : ''}</a></div>
<div style="font-size: 12px; margin-bottom: 16px; margin-top: 8px; color: ${theme.color};">${data.description}</div>
<div style="font-size: 12px; color: ${theme.color}; display: flex;">
<div style="${data.language ? '' : 'display: none;'} margin-right: 16px;">
<span style="width: 12px; height: 12px; border-radius: 100%; background-color: ${data.language ? colors[data.language].color : ''}; display: inline-block; top: 1px; position: relative;"></span>
<span>${data.language}</span>
</div>
<div style="display: ${data.stargazers_count === 0 ? 'none' : 'flex'}; align-items: center; margin-right: 16px;">
<svg style="fill: ${theme.color};" aria-label="stars" viewBox="0 0 16 16" version="1.1" width="16" height="16" role="img"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"></path></svg>
&nbsp; <span>${data.stargazers_count}</span>
</div>
<div style="display: ${data.forks === 0 ? 'none' : 'flex'}; align-items: center;">
<svg style="fill: ${theme.color};" aria-label="fork" viewBox="0 0 16 16" version="1.1" width="16" height="16" role="img"><path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path></svg>
&nbsp; <span>${data.forks}</span>
</div>
</div>
</div>
`;
}
};
window.addEventListener('DOMContentLoaded', window.tarptaeya.reloadRepoCards);

10
public/page/1/index.html Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>the1mason.com/</title>
<link rel="canonical" href="the1mason.com/">
<meta name="robots" content="noindex">
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=the1mason.com/">
</head>
</html>

228
public/posts/index.html Normal file
View File

@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Posts :: the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/posts/" />
<link rel="stylesheet" href="/the1mason.com/styles.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Posts">
<meta property="og:description" content="" />
<meta property="og:url" content="the1mason.com/posts/" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/img/favicon/red.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
<link href="the1mason.com/posts/index.xml" rel="alternate" type="application/rss+xml" title="the1mason" />
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<div class="posts">
<article class="post on-list">
<h1 class="post-title">
<a href="the1mason.com/posts/modular-app-1/">#1 Plugin-Based Web App in Dotnet - The Idea</a>
</h1>
<div class="post-meta">
<time class="post-date">
2024-01-20 ::
</time>
<span class="post-author">the1mason</span>
</div>
<span class="post-tags">
#<a href="the1mason.com/tags/dotnet/">dotnet</a>&nbsp;
#<a href="the1mason.com/tags/web/">web</a>&nbsp;
#<a href="the1mason.com/tags/prototype/">prototype</a>&nbsp;
</span>
<img src="the1mason.com/posts/modular-app/title.svg"
class="post-cover"
alt="#1 Plugin-Based Web App in Dotnet - The Idea"
title="Cover Image" />
<div class="post-content">
Have you ever thought about making a web application, that could be easily extended by third-party developers? I&rsquo;ve been thinking about making this app for a while, so here&rsquo;s my experience&hellip;
</div>
<div>
<a class="read-more button" href="the1mason.com/posts/modular-app-1/">Read more →</a>
</div>
</article>
<div class="pagination">
<div class="pagination__buttons">
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

123
public/posts/index.xml Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Posts on the1mason</title>
<link>the1mason.com/posts/</link>
<description>Recent content in Posts on the1mason</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<lastBuildDate>Sat, 20 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="the1mason.com/posts/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>#1 Plugin-Based Web App in Dotnet - The Idea</title>
<link>the1mason.com/posts/modular-app-1/</link>
<pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate>
<guid>the1mason.com/posts/modular-app-1/</guid>
<description>Chapters Writing those takes time. Expect to see one published per one-two weeks.
Idea, Stack
Loading plugins
PluginBase, IPlugin
Creating plugin, DependencyInjection
Controllers, Views
Hooks and Triggers - better event system
Advanced: Unit tests, unloading plugins
Introduction Have you ever heard of plugins? These are loadable libraries, extending your application.
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development.</description>
<content>&lt;script src=&#34;the1mason.com/js/repo-card.js&#34;&gt;&lt;/script&gt;
&lt;div class=&#34;repo-card&#34; data-repo=&#34;the1mason/Prototype.ModularMVC&#34; data-theme=&#34;dark-theme&#34;&gt;&lt;/div&gt;
&lt;h1 id=&#34;chapters&#34;&gt;Chapters&lt;/h1&gt;
&lt;p&gt;Writing those takes time. Expect to see one published per one-two weeks.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Idea, Stack&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Loading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;PluginBase, IPlugin&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Creating plugin, DependencyInjection&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Controllers, Views&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Hooks and Triggers - better event system&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Advanced: Unit tests, unloading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Have you ever heard of plugins? These are loadable libraries, extending your application.&lt;br&gt;
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development. These articles are a step by step guide to making your own plugin-based web app prototype.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I assume some that readers have some knowledge of C# and design patterns&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;problem&#34;&gt;Problem&lt;/h1&gt;
&lt;p&gt;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. For example, a shopping website might have plugins for different payment systems, or a comment section under the product page. For me this also means, that instead of making one feature-rich website, that would be so specific to my needs, that it wouldn&amp;rsquo;t of any use to anyone but me, I can write a bunch of smaller modules, that could be used by someone, without having to configure other modules.&lt;/p&gt;
&lt;h1 id=&#34;choosing-my-stack&#34;&gt;Choosing my stack&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;the1mason.com/posts/modular-app/stack.svg&#34; alt=&#34;C#, MVC, HTMX&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;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&amp;rsquo;t been described in detail as much online.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t seen such plugin-based sites written in C#. There are some projects, using plugin based architecture&amp;hellip; Well, there&amp;rsquo;s even a &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support&#34;&gt;Microsoft Learn Article&lt;/a&gt; about building such an app!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; You see&amp;hellip; there&amp;rsquo;s a problem: Neither &lt;code&gt;learn.microsoft.com&lt;/code&gt; nor any other webside covers dynamically updating web interface with plugins! If you want to learn about it, it&amp;rsquo;s the right place. Also just loading libraries isn&amp;rsquo;t enough because app also has to provide some ways for plugins to interact with it, which is also covered here!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;MVC with HTMX&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div style=&#34;display: flex; justify-content: center&#34;&gt;
&lt;img src=&#34;the1mason.com/posts/modular-app/createdwith.jpeg&#34; style=&#34;height: 100px;&#34;/&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;HTMX uses &lt;a href=&#34;https://htmx.org/essays/hateoas/&#34;&gt;Hypermedia as the Engine of Application State (HATEOAS)&lt;/a&gt; - it&amp;rsquo;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&amp;rsquo;t show the withdrawal option if balance is 0 or less). With HATEOAS, the server just won&amp;rsquo;t give the link to withdraw money, making this action impossible in the first place.&lt;/p&gt;
&lt;p&gt;HTMX would allow this app to be extended more easily as well. Most of the modern JS frameworks require transpiling, bundling and other sorts of stuff. This means that when a plugin is installed the client is most likely will have to be rebuilt. This is slow and needs additional dependencies.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have you heard about Blazor WASM? You can just write client code in C#!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Blazor WASM does not support dynamic loading for plugins. Because of that, plugins won&amp;rsquo;t be able to extend the client. Also it&amp;rsquo;s initial load time is stupidly slow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The next article will cover the following topocs: Loading plugins in runtime, creating plugin&amp;rsquo;s instances, app-plugin communication. I&amp;rsquo;ll have the link here when I&amp;rsquo;m done writing it!&lt;/p&gt;
&lt;!--
---
# Loading plugins
C#, being a compiled language, can&#39;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.
&gt; Wait, wait, wait! Using... what? I&#39;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&#39;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&#39;s dependencies! That&#39;s where `AssemblyDependencyResolver` comes in. It&#39;ll find all of plugin&#39;s `.dll` dependencies and load those as well.
&gt; 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&#39;s dependencies. This will allow multiple plugins to have same dependencies with different versions.
There&#39;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&#39;ll build communication using those.
**Dependency Injection**
Before the app is built and ran, --&gt;</content>
</item>
</channel>
</rss>

View File

@ -0,0 +1,314 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>#1 Plugin-Based Web App in Dotnet - The Idea :: the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Have you ever thought about making a web application, that could be easily extended by third-party developers? I&#39;ve been thinking about making this app for a while, so here&#39;s my experience..." />
<meta name="keywords" content="prototype, dotnet, guide, plugins, plugin-based, web application, ASP.NET, C#, .NET 8, Programming, Software Architecture" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/posts/modular-app-1/" />
<link rel="stylesheet" href="/the1mason.com/styles.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="article" />
<meta property="og:title" content="#1 Plugin-Based Web App in Dotnet - The Idea">
<meta property="og:description" content="Have you ever thought about making a web application, that could be easily extended by third-party developers? I&#39;ve been thinking about making this app for a while, so here&#39;s my experience..." />
<meta property="og:url" content="the1mason.com/posts/modular-app-1/" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/posts/modular-app/title.svg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
<meta property="article:published_time" content="2024-01-20 00:00:00 &#43;0000 UTC" />
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<article class="post">
<h1 class="post-title">
<a href="the1mason.com/posts/modular-app-1/">#1 Plugin-Based Web App in Dotnet - The Idea</a>
</h1>
<div class="post-meta">
<time class="post-date">
2024-01-20 ::
</time>
<span class="post-author">the1mason</span>
<span class="post-reading-time">:: 4 min read (670 words)</span>
</div>
<span class="post-tags">
#<a href="the1mason.com/tags/dotnet/">dotnet</a>&nbsp;
#<a href="the1mason.com/tags/web/">web</a>&nbsp;
#<a href="the1mason.com/tags/prototype/">prototype</a>&nbsp;
</span>
<img src="the1mason.com/posts/modular-app/title.svg"
class="post-cover"
alt="#1 Plugin-Based Web App in Dotnet - The Idea"
title="Cover Image" />
<div class="post-content"><div>
<script src="/js/repo-card.js"></script>
<div class="repo-card" data-repo="the1mason/Prototype.ModularMVC" data-theme="dark-theme"></div>
<h1 id="chapters">Chapters<a href="#chapters" class="hanchor" ariaLabel="Anchor">&#8983;</a> </h1>
<p>Writing those takes time. Expect to see one published per one-two weeks.</p>
<ol>
<li>
<p>Idea, Stack</p>
</li>
<li>
<p><del>Loading plugins</del></p>
</li>
<li>
<p><del>PluginBase, IPlugin</del></p>
</li>
<li>
<p><del>Creating plugin, DependencyInjection</del></p>
</li>
<li>
<p><del>Controllers, Views</del></p>
</li>
<li>
<p><del>Hooks and Triggers - better event system</del></p>
</li>
<li>
<p><del>Advanced: Unit tests, unloading plugins</del></p>
</li>
</ol>
<h1 id="introduction">Introduction<a href="#introduction" class="hanchor" ariaLabel="Anchor">&#8983;</a> </h1>
<p>Have you ever heard of plugins? These are loadable libraries, extending your application.<br>
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&rsquo;s features, as well as my thought process and decision making during development. These articles are a step by step guide to making your own plugin-based web app prototype.</p>
<p><em>I assume some that readers have some knowledge of C# and design patterns</em></p>
<h1 id="problem">Problem<a href="#problem" class="hanchor" ariaLabel="Anchor">&#8983;</a> </h1>
<p>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. For example, a shopping website might have plugins for different payment systems, or a comment section under the product page. For me this also means, that instead of making one feature-rich website, that would be so specific to my needs, that it wouldn&rsquo;t of any use to anyone but me, I can write a bunch of smaller modules, that could be used by someone, without having to configure other modules.</p>
<h1 id="choosing-my-stack">Choosing my stack<a href="#choosing-my-stack" class="hanchor" ariaLabel="Anchor">&#8983;</a> </h1>
<p><img src="/posts/modular-app/stack.svg" alt="C#, MVC, HTMX"></p>
<hr>
<p><strong>C#</strong></p>
<p>I&rsquo;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&rsquo;t been described in detail as much online.</p>
<p>I haven&rsquo;t seen such plugin-based sites written in C#. There are some projects, using plugin based architecture&hellip; Well, there&rsquo;s even a <a href="https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support">Microsoft Learn Article</a> about building such an app!</p>
<blockquote>
<p><strong>Q:</strong> Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?</p>
</blockquote>
<blockquote>
<p><strong>A:</strong> You see&hellip; there&rsquo;s a problem: Neither <code>learn.microsoft.com</code> nor any other webside covers dynamically updating web interface with plugins! If you want to learn about it, it&rsquo;s the right place. Also just loading libraries isn&rsquo;t enough because app also has to provide some ways for plugins to interact with it, which is also covered here!</p>
</blockquote>
<hr>
<p><strong>MVC with HTMX</strong></p>
<p>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.</p>
<div style="display: flex; justify-content: center">
<img src="/posts/modular-app/createdwith.jpeg" style="height: 100px;"/>
</div>
<br>
<p>HTMX uses <a href="https://htmx.org/essays/hateoas/">Hypermedia as the Engine of Application State (HATEOAS)</a> - it&rsquo;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&rsquo;t show the withdrawal option if balance is 0 or less). With HATEOAS, the server just won&rsquo;t give the link to withdraw money, making this action impossible in the first place.</p>
<p>HTMX would allow this app to be extended more easily as well. Most of the modern JS frameworks require transpiling, bundling and other sorts of stuff. This means that when a plugin is installed the client is most likely will have to be rebuilt. This is slow and needs additional dependencies.</p>
<blockquote>
<p>Have you heard about Blazor WASM? You can just write client code in C#!</p>
</blockquote>
<p>Blazor WASM does not support dynamic loading for plugins. Because of that, plugins won&rsquo;t be able to extend the client. Also it&rsquo;s initial load time is stupidly slow.</p>
<hr>
<p>The next article will cover the following topocs: Loading plugins in runtime, creating plugin&rsquo;s instances, app-plugin communication. I&rsquo;ll have the link here when I&rsquo;m done writing it!</p>
<!--
---
# 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, -->
</div></div>
</article>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -0,0 +1,38 @@
<svg width="1068" height="492" viewBox="0 0 1068 492" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1068" height="492" fill="#221F29"/>
<g filter="url(#filter0_d_1_33)">
<rect x="398" y="163" width="272" height="178" fill="#FF6266"/>
<rect x="394.5" y="159.5" width="279" height="185" stroke="#221F29" stroke-width="7"/>
</g>
<path d="M490.052 267.844H472.188L468.976 281H453.884L472.54 220.016H490.052L508.708 281H493.264L490.052 267.844ZM474.476 257.372H487.676L481.076 230.4L474.476 257.372ZM543.477 239.904C543.477 237.733 543.067 235.944 542.245 234.536C541.453 233.099 540.236 232.043 538.593 231.368C536.98 230.664 534.941 230.312 532.477 230.312H527.549V250.244H533.225C535.367 250.244 537.2 249.877 538.725 249.144C540.251 248.411 541.424 247.281 542.245 245.756C543.067 244.231 543.477 242.28 543.477 239.904ZM558.349 239.86C558.349 244.583 557.337 248.484 555.313 251.564C553.289 254.644 550.459 256.947 546.821 258.472C543.184 259.968 538.96 260.716 534.149 260.716H527.549V281H513.117V220.016H532.301C540.573 220.016 546.983 221.659 551.529 224.944C556.076 228.229 558.349 233.201 558.349 239.86ZM596.243 239.904C596.243 237.733 595.832 235.944 595.011 234.536C594.219 233.099 593.001 232.043 591.359 231.368C589.745 230.664 587.707 230.312 585.243 230.312H580.315V250.244H585.991C588.132 250.244 589.965 249.877 591.491 249.144C593.016 248.411 594.189 247.281 595.011 245.756C595.832 244.231 596.243 242.28 596.243 239.904ZM611.115 239.86C611.115 244.583 610.103 248.484 608.079 251.564C606.055 254.644 603.224 256.947 599.587 258.472C595.949 259.968 591.725 260.716 586.915 260.716H580.315V281H565.883V220.016H585.067C593.339 220.016 599.748 221.659 604.295 224.944C608.841 228.229 611.115 233.201 611.115 239.86Z" fill="#221F29"/>
<rect x="3.5" y="-3.5" width="162" height="91" transform="matrix(-1 0 0 1 961 322)" fill="#221F29" stroke="#FF6266" stroke-width="7"/>
<path d="M841.717 360.325C841.717 359.708 841.6 359.2 841.367 358.8C841.142 358.392 840.796 358.092 840.329 357.9C839.871 357.7 839.292 357.6 838.592 357.6H837.192V363.262H838.804C839.413 363.262 839.933 363.158 840.367 362.95C840.8 362.742 841.133 362.421 841.367 361.987C841.6 361.554 841.717 361 841.717 360.325ZM845.942 360.312C845.942 361.654 845.654 362.762 845.079 363.637C844.504 364.512 843.7 365.167 842.667 365.6C841.633 366.025 840.433 366.237 839.067 366.237H837.192V372H833.092V354.675H838.542C840.892 354.675 842.713 355.142 844.004 356.075C845.296 357.008 845.942 358.421 845.942 360.312ZM855.195 353.45V367.825C855.195 368.358 855.345 368.742 855.645 368.975C855.953 369.2 856.374 369.312 856.907 369.312C857.249 369.312 857.578 369.275 857.895 369.2C858.211 369.117 858.507 369.021 858.782 368.912L859.732 371.55C859.282 371.783 858.74 371.987 858.107 372.163C857.474 372.338 856.736 372.425 855.895 372.425C854.295 372.425 853.12 371.967 852.37 371.05C851.62 370.125 851.245 368.887 851.245 367.337V356.15H847.332V353.45H855.195ZM867.21 358.725V367.812C867.21 368.454 867.326 368.896 867.56 369.137C867.801 369.379 868.135 369.5 868.56 369.5C868.968 369.5 869.376 369.371 869.785 369.112C870.193 368.854 870.535 368.504 870.81 368.062V358.725H874.76V372H871.31L871.135 370.45C870.685 371.1 870.093 371.592 869.36 371.925C868.626 372.258 867.86 372.425 867.06 372.425C865.776 372.425 864.822 372.054 864.197 371.312C863.572 370.562 863.26 369.558 863.26 368.3V358.725H867.21ZM890.038 356.6L890.963 359.5C890.496 359.683 889.95 359.817 889.325 359.9C888.7 359.983 887.971 360.025 887.138 360.025C887.971 360.383 888.604 360.829 889.038 361.362C889.479 361.887 889.7 362.579 889.7 363.437C889.7 364.296 889.471 365.062 889.013 365.737C888.554 366.404 887.904 366.929 887.063 367.312C886.221 367.696 885.225 367.887 884.075 367.887C883.792 367.887 883.525 367.875 883.275 367.85C883.025 367.825 882.779 367.787 882.538 367.737C882.396 367.812 882.288 367.921 882.213 368.062C882.138 368.204 882.1 368.35 882.1 368.5C882.1 368.708 882.183 368.896 882.35 369.062C882.525 369.229 882.929 369.312 883.562 369.312H885.738C886.771 369.312 887.663 369.479 888.413 369.812C889.171 370.146 889.758 370.6 890.175 371.175C890.592 371.75 890.8 372.408 890.8 373.15C890.8 374.508 890.204 375.579 889.013 376.363C887.821 377.146 886.05 377.538 883.7 377.538C882 377.538 880.683 377.363 879.75 377.013C878.825 376.671 878.179 376.179 877.813 375.538C877.454 374.904 877.275 374.15 877.275 373.275H880.775C880.775 373.658 880.85 373.971 881 374.213C881.158 374.463 881.454 374.646 881.888 374.763C882.329 374.879 882.967 374.938 883.8 374.938C884.642 374.938 885.283 374.871 885.725 374.738C886.167 374.604 886.467 374.421 886.625 374.188C886.792 373.963 886.875 373.708 886.875 373.425C886.875 373.033 886.721 372.717 886.413 372.475C886.104 372.233 885.617 372.113 884.95 372.113H882.838C881.404 372.113 880.363 371.85 879.712 371.325C879.063 370.792 878.738 370.171 878.738 369.462C878.738 368.971 878.867 368.504 879.125 368.062C879.392 367.621 879.763 367.25 880.238 366.95C879.388 366.5 878.779 365.971 878.413 365.362C878.046 364.746 877.863 364.008 877.863 363.15C877.863 362.15 878.117 361.292 878.625 360.575C879.133 359.85 879.833 359.292 880.725 358.9C881.625 358.508 882.65 358.312 883.8 358.312C884.792 358.329 885.654 358.262 886.388 358.112C887.121 357.954 887.779 357.742 888.363 357.475C888.946 357.2 889.504 356.908 890.038 356.6ZM883.9 360.875C883.275 360.875 882.779 361.075 882.413 361.475C882.046 361.867 881.863 362.408 881.863 363.1C881.863 363.833 882.05 364.396 882.425 364.787C882.8 365.179 883.292 365.375 883.9 365.375C884.558 365.375 885.058 365.179 885.4 364.787C885.75 364.396 885.925 363.817 885.925 363.05C885.925 362.308 885.754 361.762 885.413 361.412C885.071 361.054 884.567 360.875 883.9 360.875ZM901.353 358.725V369.3H904.728V372H893.64V369.3H897.403V361.425H893.765V358.725H901.353ZM898.965 351.775C899.649 351.775 900.207 351.987 900.64 352.412C901.074 352.837 901.29 353.367 901.29 354C901.29 354.633 901.074 355.167 900.64 355.6C900.207 356.025 899.649 356.237 898.965 356.237C898.274 356.237 897.707 356.025 897.265 355.6C896.832 355.167 896.615 354.633 896.615 354C896.615 353.367 896.832 352.837 897.265 352.412C897.707 351.987 898.274 351.775 898.965 351.775ZM908.23 372V358.725H911.68L911.955 360.262C912.547 359.596 913.172 359.1 913.83 358.775C914.489 358.45 915.239 358.287 916.08 358.287C917.214 358.287 918.105 358.633 918.755 359.325C919.405 360.017 919.73 360.992 919.73 362.25V372H915.78V363.387C915.78 362.846 915.743 362.417 915.668 362.1C915.593 361.775 915.46 361.546 915.268 361.412C915.085 361.271 914.83 361.2 914.505 361.2C914.23 361.2 913.96 361.262 913.693 361.387C913.435 361.504 913.18 361.675 912.93 361.9C912.68 362.125 912.43 362.396 912.18 362.712V372H908.23Z" fill="#FF6266"/>
<path d="M737 372H734.5V374.5H737V372ZM737 319H739.5V316.5H737V319ZM687.5 319L712.5 333.434V304.566L687.5 319ZM789 369.5H737V374.5H789V369.5ZM739.5 372V319H734.5V372H739.5ZM737 316.5H710V321.5H737V316.5Z" fill="#FF6266"/>
<rect x="3.5" y="-3.5" width="162" height="91" transform="matrix(-1 0 0 1 961 210)" fill="#221F29" stroke="#FF6266" stroke-width="7"/>
<path d="M841.717 248.325C841.717 247.708 841.6 247.2 841.367 246.8C841.142 246.392 840.796 246.092 840.329 245.9C839.871 245.7 839.292 245.6 838.592 245.6H837.192V251.262H838.804C839.413 251.262 839.933 251.158 840.367 250.95C840.8 250.742 841.133 250.421 841.367 249.987C841.6 249.554 841.717 249 841.717 248.325ZM845.942 248.312C845.942 249.654 845.654 250.762 845.079 251.637C844.504 252.512 843.7 253.167 842.667 253.6C841.633 254.025 840.433 254.237 839.067 254.237H837.192V260H833.092V242.675H838.542C840.892 242.675 842.713 243.142 844.004 244.075C845.296 245.008 845.942 246.421 845.942 248.312ZM855.195 241.45V255.825C855.195 256.358 855.345 256.742 855.645 256.975C855.953 257.2 856.374 257.312 856.907 257.312C857.249 257.312 857.578 257.275 857.895 257.2C858.211 257.117 858.507 257.021 858.782 256.912L859.732 259.55C859.282 259.783 858.74 259.987 858.107 260.163C857.474 260.338 856.736 260.425 855.895 260.425C854.295 260.425 853.12 259.967 852.37 259.05C851.62 258.125 851.245 256.887 851.245 255.337V244.15H847.332V241.45H855.195ZM867.21 246.725V255.812C867.21 256.454 867.326 256.896 867.56 257.137C867.801 257.379 868.135 257.5 868.56 257.5C868.968 257.5 869.376 257.371 869.785 257.112C870.193 256.854 870.535 256.504 870.81 256.062V246.725H874.76V260H871.31L871.135 258.45C870.685 259.1 870.093 259.592 869.36 259.925C868.626 260.258 867.86 260.425 867.06 260.425C865.776 260.425 864.822 260.054 864.197 259.312C863.572 258.562 863.26 257.558 863.26 256.3V246.725H867.21ZM890.038 244.6L890.963 247.5C890.496 247.683 889.95 247.817 889.325 247.9C888.7 247.983 887.971 248.025 887.138 248.025C887.971 248.383 888.604 248.829 889.038 249.362C889.479 249.887 889.7 250.579 889.7 251.437C889.7 252.296 889.471 253.062 889.013 253.737C888.554 254.404 887.904 254.929 887.063 255.312C886.221 255.696 885.225 255.887 884.075 255.887C883.792 255.887 883.525 255.875 883.275 255.85C883.025 255.825 882.779 255.787 882.538 255.737C882.396 255.812 882.288 255.921 882.213 256.062C882.138 256.204 882.1 256.35 882.1 256.5C882.1 256.708 882.183 256.896 882.35 257.062C882.525 257.229 882.929 257.312 883.562 257.312H885.738C886.771 257.312 887.663 257.479 888.413 257.812C889.171 258.146 889.758 258.6 890.175 259.175C890.592 259.75 890.8 260.408 890.8 261.15C890.8 262.508 890.204 263.579 889.013 264.363C887.821 265.146 886.05 265.538 883.7 265.538C882 265.538 880.683 265.363 879.75 265.013C878.825 264.671 878.179 264.179 877.813 263.538C877.454 262.904 877.275 262.15 877.275 261.275H880.775C880.775 261.658 880.85 261.971 881 262.213C881.158 262.463 881.454 262.646 881.888 262.763C882.329 262.879 882.967 262.938 883.8 262.938C884.642 262.938 885.283 262.871 885.725 262.738C886.167 262.604 886.467 262.421 886.625 262.188C886.792 261.963 886.875 261.708 886.875 261.425C886.875 261.033 886.721 260.717 886.413 260.475C886.104 260.233 885.617 260.113 884.95 260.113H882.838C881.404 260.113 880.363 259.85 879.712 259.325C879.063 258.792 878.738 258.171 878.738 257.462C878.738 256.971 878.867 256.504 879.125 256.062C879.392 255.621 879.763 255.25 880.238 254.95C879.388 254.5 878.779 253.971 878.413 253.362C878.046 252.746 877.863 252.008 877.863 251.15C877.863 250.15 878.117 249.292 878.625 248.575C879.133 247.85 879.833 247.292 880.725 246.9C881.625 246.508 882.65 246.312 883.8 246.312C884.792 246.329 885.654 246.262 886.388 246.112C887.121 245.954 887.779 245.742 888.363 245.475C888.946 245.2 889.504 244.908 890.038 244.6ZM883.9 248.875C883.275 248.875 882.779 249.075 882.413 249.475C882.046 249.867 881.863 250.408 881.863 251.1C881.863 251.833 882.05 252.396 882.425 252.787C882.8 253.179 883.292 253.375 883.9 253.375C884.558 253.375 885.058 253.179 885.4 252.787C885.75 252.396 885.925 251.817 885.925 251.05C885.925 250.308 885.754 249.762 885.413 249.412C885.071 249.054 884.567 248.875 883.9 248.875ZM901.353 246.725V257.3H904.728V260H893.64V257.3H897.403V249.425H893.765V246.725H901.353ZM898.965 239.775C899.649 239.775 900.207 239.987 900.64 240.412C901.074 240.837 901.29 241.367 901.29 242C901.29 242.633 901.074 243.167 900.64 243.6C900.207 244.025 899.649 244.237 898.965 244.237C898.274 244.237 897.707 244.025 897.265 243.6C896.832 243.167 896.615 242.633 896.615 242C896.615 241.367 896.832 240.837 897.265 240.412C897.707 239.987 898.274 239.775 898.965 239.775ZM908.23 260V246.725H911.68L911.955 248.262C912.547 247.596 913.172 247.1 913.83 246.775C914.489 246.45 915.239 246.287 916.08 246.287C917.214 246.287 918.105 246.633 918.755 247.325C919.405 248.017 919.73 248.992 919.73 250.25V260H915.78V251.387C915.78 250.846 915.743 250.417 915.668 250.1C915.593 249.775 915.46 249.546 915.268 249.412C915.085 249.271 914.83 249.2 914.505 249.2C914.23 249.2 913.96 249.262 913.693 249.387C913.435 249.504 913.18 249.675 912.93 249.9C912.68 250.125 912.43 250.396 912.18 250.712V260H908.23Z" fill="#FF6266"/>
<path d="M687 253L712 238.566V267.434L687 253ZM789 255.5H709.5V250.5H789V255.5Z" fill="#FF6266"/>
<rect x="3.5" y="-3.5" width="162" height="91" transform="matrix(-1 0 0 1 961 86)" fill="#221F29" stroke="#FF6266" stroke-width="7"/>
<path d="M841.717 124.325C841.717 123.708 841.6 123.2 841.367 122.8C841.142 122.392 840.796 122.092 840.329 121.9C839.871 121.7 839.292 121.6 838.592 121.6H837.192V127.262H838.804C839.413 127.262 839.933 127.158 840.367 126.95C840.8 126.742 841.133 126.421 841.367 125.987C841.6 125.554 841.717 125 841.717 124.325ZM845.942 124.312C845.942 125.654 845.654 126.762 845.079 127.637C844.504 128.512 843.7 129.167 842.667 129.6C841.633 130.025 840.433 130.237 839.067 130.237H837.192V136H833.092V118.675H838.542C840.892 118.675 842.713 119.142 844.004 120.075C845.296 121.008 845.942 122.421 845.942 124.312ZM855.195 117.45V131.825C855.195 132.358 855.345 132.742 855.645 132.975C855.953 133.2 856.374 133.312 856.907 133.312C857.249 133.312 857.578 133.275 857.895 133.2C858.211 133.117 858.507 133.021 858.782 132.912L859.732 135.55C859.282 135.783 858.74 135.987 858.107 136.163C857.474 136.338 856.736 136.425 855.895 136.425C854.295 136.425 853.12 135.967 852.37 135.05C851.62 134.125 851.245 132.887 851.245 131.337V120.15H847.332V117.45H855.195ZM867.21 122.725V131.812C867.21 132.454 867.326 132.896 867.56 133.137C867.801 133.379 868.135 133.5 868.56 133.5C868.968 133.5 869.376 133.371 869.785 133.112C870.193 132.854 870.535 132.504 870.81 132.062V122.725H874.76V136H871.31L871.135 134.45C870.685 135.1 870.093 135.592 869.36 135.925C868.626 136.258 867.86 136.425 867.06 136.425C865.776 136.425 864.822 136.054 864.197 135.312C863.572 134.562 863.26 133.558 863.26 132.3V122.725H867.21ZM890.038 120.6L890.963 123.5C890.496 123.683 889.95 123.817 889.325 123.9C888.7 123.983 887.971 124.025 887.138 124.025C887.971 124.383 888.604 124.829 889.038 125.362C889.479 125.887 889.7 126.579 889.7 127.437C889.7 128.296 889.471 129.062 889.013 129.737C888.554 130.404 887.904 130.929 887.063 131.312C886.221 131.696 885.225 131.887 884.075 131.887C883.792 131.887 883.525 131.875 883.275 131.85C883.025 131.825 882.779 131.787 882.538 131.737C882.396 131.812 882.288 131.921 882.213 132.062C882.138 132.204 882.1 132.35 882.1 132.5C882.1 132.708 882.183 132.896 882.35 133.062C882.525 133.229 882.929 133.312 883.562 133.312H885.738C886.771 133.312 887.663 133.479 888.413 133.812C889.171 134.146 889.758 134.6 890.175 135.175C890.592 135.75 890.8 136.408 890.8 137.15C890.8 138.508 890.204 139.579 889.013 140.363C887.821 141.146 886.05 141.538 883.7 141.538C882 141.538 880.683 141.363 879.75 141.013C878.825 140.671 878.179 140.179 877.813 139.538C877.454 138.904 877.275 138.15 877.275 137.275H880.775C880.775 137.658 880.85 137.971 881 138.213C881.158 138.463 881.454 138.646 881.888 138.763C882.329 138.879 882.967 138.938 883.8 138.938C884.642 138.938 885.283 138.871 885.725 138.738C886.167 138.604 886.467 138.421 886.625 138.188C886.792 137.963 886.875 137.708 886.875 137.425C886.875 137.033 886.721 136.717 886.413 136.475C886.104 136.233 885.617 136.113 884.95 136.113H882.838C881.404 136.113 880.363 135.85 879.712 135.325C879.063 134.792 878.738 134.171 878.738 133.462C878.738 132.971 878.867 132.504 879.125 132.062C879.392 131.621 879.763 131.25 880.238 130.95C879.388 130.5 878.779 129.971 878.413 129.362C878.046 128.746 877.863 128.008 877.863 127.15C877.863 126.15 878.117 125.292 878.625 124.575C879.133 123.85 879.833 123.292 880.725 122.9C881.625 122.508 882.65 122.312 883.8 122.312C884.792 122.329 885.654 122.262 886.388 122.112C887.121 121.954 887.779 121.742 888.363 121.475C888.946 121.2 889.504 120.908 890.038 120.6ZM883.9 124.875C883.275 124.875 882.779 125.075 882.413 125.475C882.046 125.867 881.863 126.408 881.863 127.1C881.863 127.833 882.05 128.396 882.425 128.787C882.8 129.179 883.292 129.375 883.9 129.375C884.558 129.375 885.058 129.179 885.4 128.787C885.75 128.396 885.925 127.817 885.925 127.05C885.925 126.308 885.754 125.762 885.413 125.412C885.071 125.054 884.567 124.875 883.9 124.875ZM901.353 122.725V133.3H904.728V136H893.64V133.3H897.403V125.425H893.765V122.725H901.353ZM898.965 115.775C899.649 115.775 900.207 115.987 900.64 116.412C901.074 116.837 901.29 117.367 901.29 118C901.29 118.633 901.074 119.167 900.64 119.6C900.207 120.025 899.649 120.237 898.965 120.237C898.274 120.237 897.707 120.025 897.265 119.6C896.832 119.167 896.615 118.633 896.615 118C896.615 117.367 896.832 116.837 897.265 116.412C897.707 115.987 898.274 115.775 898.965 115.775ZM908.23 136V122.725H911.68L911.955 124.262C912.547 123.596 913.172 123.1 913.83 122.775C914.489 122.45 915.239 122.287 916.08 122.287C917.214 122.287 918.105 122.633 918.755 123.325C919.405 124.017 919.73 124.992 919.73 126.25V136H915.78V127.387C915.78 126.846 915.743 126.417 915.668 126.1C915.593 125.775 915.46 125.546 915.268 125.412C915.085 125.271 914.83 125.2 914.505 125.2C914.23 125.2 913.96 125.262 913.693 125.387C913.435 125.504 913.18 125.675 912.93 125.9C912.68 126.125 912.43 126.396 912.18 126.712V136H908.23Z" fill="#FF6266"/>
<path d="M737 129H734.5V126.5H737V129ZM737 182H739.5V184.5H737V182ZM687.5 182L712.5 167.566V196.434L687.5 182ZM789 131.5H737V126.5H789V131.5ZM739.5 129V182H734.5V129H739.5ZM737 184.5H710V179.5H737V184.5Z" fill="#FF6266"/>
<rect x="110.5" y="318.5" width="162" height="91" fill="#221F29" stroke="#FF6266" stroke-width="7"/>
<path d="M156.717 360.325C156.717 359.708 156.6 359.2 156.367 358.8C156.142 358.392 155.796 358.092 155.329 357.9C154.871 357.7 154.292 357.6 153.592 357.6H152.192V363.262H153.804C154.413 363.262 154.933 363.158 155.367 362.95C155.8 362.742 156.133 362.421 156.367 361.987C156.6 361.554 156.717 361 156.717 360.325ZM160.942 360.312C160.942 361.654 160.654 362.762 160.079 363.637C159.504 364.512 158.7 365.167 157.667 365.6C156.633 366.025 155.433 366.237 154.067 366.237H152.192V372H148.092V354.675H153.542C155.892 354.675 157.713 355.142 159.004 356.075C160.296 357.008 160.942 358.421 160.942 360.312ZM170.195 353.45V367.825C170.195 368.358 170.345 368.742 170.645 368.975C170.953 369.2 171.374 369.312 171.907 369.312C172.249 369.312 172.578 369.275 172.895 369.2C173.211 369.117 173.507 369.021 173.782 368.912L174.732 371.55C174.282 371.783 173.74 371.987 173.107 372.163C172.474 372.338 171.736 372.425 170.895 372.425C169.295 372.425 168.12 371.967 167.37 371.05C166.62 370.125 166.245 368.887 166.245 367.337V356.15H162.332V353.45H170.195ZM182.21 358.725V367.812C182.21 368.454 182.326 368.896 182.56 369.137C182.801 369.379 183.135 369.5 183.56 369.5C183.968 369.5 184.376 369.371 184.785 369.112C185.193 368.854 185.535 368.504 185.81 368.062V358.725H189.76V372H186.31L186.135 370.45C185.685 371.1 185.093 371.592 184.36 371.925C183.626 372.258 182.86 372.425 182.06 372.425C180.776 372.425 179.822 372.054 179.197 371.312C178.572 370.562 178.26 369.558 178.26 368.3V358.725H182.21ZM205.038 356.6L205.963 359.5C205.496 359.683 204.95 359.817 204.325 359.9C203.7 359.983 202.971 360.025 202.138 360.025C202.971 360.383 203.604 360.829 204.038 361.362C204.479 361.887 204.7 362.579 204.7 363.437C204.7 364.296 204.471 365.062 204.013 365.737C203.554 366.404 202.904 366.929 202.063 367.312C201.221 367.696 200.225 367.887 199.075 367.887C198.792 367.887 198.525 367.875 198.275 367.85C198.025 367.825 197.779 367.787 197.538 367.737C197.396 367.812 197.288 367.921 197.213 368.062C197.138 368.204 197.1 368.35 197.1 368.5C197.1 368.708 197.183 368.896 197.35 369.062C197.525 369.229 197.929 369.312 198.562 369.312H200.738C201.771 369.312 202.663 369.479 203.413 369.812C204.171 370.146 204.758 370.6 205.175 371.175C205.592 371.75 205.8 372.408 205.8 373.15C205.8 374.508 205.204 375.579 204.013 376.363C202.821 377.146 201.05 377.538 198.7 377.538C197 377.538 195.683 377.363 194.75 377.013C193.825 376.671 193.179 376.179 192.813 375.538C192.454 374.904 192.275 374.15 192.275 373.275H195.775C195.775 373.658 195.85 373.971 196 374.213C196.158 374.463 196.454 374.646 196.888 374.763C197.329 374.879 197.967 374.938 198.8 374.938C199.642 374.938 200.283 374.871 200.725 374.738C201.167 374.604 201.467 374.421 201.625 374.188C201.792 373.963 201.875 373.708 201.875 373.425C201.875 373.033 201.721 372.717 201.413 372.475C201.104 372.233 200.617 372.113 199.95 372.113H197.838C196.404 372.113 195.363 371.85 194.712 371.325C194.063 370.792 193.738 370.171 193.738 369.462C193.738 368.971 193.867 368.504 194.125 368.062C194.392 367.621 194.763 367.25 195.238 366.95C194.388 366.5 193.779 365.971 193.413 365.362C193.046 364.746 192.863 364.008 192.863 363.15C192.863 362.15 193.117 361.292 193.625 360.575C194.133 359.85 194.833 359.292 195.725 358.9C196.625 358.508 197.65 358.312 198.8 358.312C199.792 358.329 200.654 358.262 201.388 358.112C202.121 357.954 202.779 357.742 203.363 357.475C203.946 357.2 204.504 356.908 205.038 356.6ZM198.9 360.875C198.275 360.875 197.779 361.075 197.413 361.475C197.046 361.867 196.863 362.408 196.863 363.1C196.863 363.833 197.05 364.396 197.425 364.787C197.8 365.179 198.292 365.375 198.9 365.375C199.558 365.375 200.058 365.179 200.4 364.787C200.75 364.396 200.925 363.817 200.925 363.05C200.925 362.308 200.754 361.762 200.413 361.412C200.071 361.054 199.567 360.875 198.9 360.875ZM216.353 358.725V369.3H219.728V372H208.64V369.3H212.403V361.425H208.765V358.725H216.353ZM213.965 351.775C214.649 351.775 215.207 351.987 215.64 352.412C216.074 352.837 216.29 353.367 216.29 354C216.29 354.633 216.074 355.167 215.64 355.6C215.207 356.025 214.649 356.237 213.965 356.237C213.274 356.237 212.707 356.025 212.265 355.6C211.832 355.167 211.615 354.633 211.615 354C211.615 353.367 211.832 352.837 212.265 352.412C212.707 351.987 213.274 351.775 213.965 351.775ZM223.23 372V358.725H226.68L226.955 360.262C227.547 359.596 228.172 359.1 228.83 358.775C229.489 358.45 230.239 358.287 231.08 358.287C232.214 358.287 233.105 358.633 233.755 359.325C234.405 360.017 234.73 360.992 234.73 362.25V372H230.78V363.387C230.78 362.846 230.743 362.417 230.668 362.1C230.593 361.775 230.46 361.546 230.268 361.412C230.085 361.271 229.83 361.2 229.505 361.2C229.23 361.2 228.96 361.262 228.693 361.387C228.435 361.504 228.18 361.675 227.93 361.9C227.68 362.125 227.43 362.396 227.18 362.712V372H223.23Z" fill="#FF6266"/>
<path d="M331 372H333.5V374.5H331V372ZM331 319H328.5V316.5H331V319ZM380.5 319L355.5 333.434V304.566L380.5 319ZM279 369.5H331V374.5H279V369.5ZM328.5 372V319H333.5V372H328.5ZM331 316.5H358V321.5H331V316.5Z" fill="#FF6266"/>
<rect x="110.5" y="206.5" width="162" height="91" fill="#221F29" stroke="#FF6266" stroke-width="7"/>
<path d="M156.717 248.325C156.717 247.708 156.6 247.2 156.367 246.8C156.142 246.392 155.796 246.092 155.329 245.9C154.871 245.7 154.292 245.6 153.592 245.6H152.192V251.262H153.804C154.413 251.262 154.933 251.158 155.367 250.95C155.8 250.742 156.133 250.421 156.367 249.987C156.6 249.554 156.717 249 156.717 248.325ZM160.942 248.312C160.942 249.654 160.654 250.762 160.079 251.637C159.504 252.512 158.7 253.167 157.667 253.6C156.633 254.025 155.433 254.237 154.067 254.237H152.192V260H148.092V242.675H153.542C155.892 242.675 157.713 243.142 159.004 244.075C160.296 245.008 160.942 246.421 160.942 248.312ZM170.195 241.45V255.825C170.195 256.358 170.345 256.742 170.645 256.975C170.953 257.2 171.374 257.312 171.907 257.312C172.249 257.312 172.578 257.275 172.895 257.2C173.211 257.117 173.507 257.021 173.782 256.912L174.732 259.55C174.282 259.783 173.74 259.987 173.107 260.163C172.474 260.338 171.736 260.425 170.895 260.425C169.295 260.425 168.12 259.967 167.37 259.05C166.62 258.125 166.245 256.887 166.245 255.337V244.15H162.332V241.45H170.195ZM182.21 246.725V255.812C182.21 256.454 182.326 256.896 182.56 257.137C182.801 257.379 183.135 257.5 183.56 257.5C183.968 257.5 184.376 257.371 184.785 257.112C185.193 256.854 185.535 256.504 185.81 256.062V246.725H189.76V260H186.31L186.135 258.45C185.685 259.1 185.093 259.592 184.36 259.925C183.626 260.258 182.86 260.425 182.06 260.425C180.776 260.425 179.822 260.054 179.197 259.312C178.572 258.562 178.26 257.558 178.26 256.3V246.725H182.21ZM205.038 244.6L205.963 247.5C205.496 247.683 204.95 247.817 204.325 247.9C203.7 247.983 202.971 248.025 202.138 248.025C202.971 248.383 203.604 248.829 204.038 249.362C204.479 249.887 204.7 250.579 204.7 251.437C204.7 252.296 204.471 253.062 204.013 253.737C203.554 254.404 202.904 254.929 202.063 255.312C201.221 255.696 200.225 255.887 199.075 255.887C198.792 255.887 198.525 255.875 198.275 255.85C198.025 255.825 197.779 255.787 197.538 255.737C197.396 255.812 197.288 255.921 197.213 256.062C197.138 256.204 197.1 256.35 197.1 256.5C197.1 256.708 197.183 256.896 197.35 257.062C197.525 257.229 197.929 257.312 198.562 257.312H200.738C201.771 257.312 202.663 257.479 203.413 257.812C204.171 258.146 204.758 258.6 205.175 259.175C205.592 259.75 205.8 260.408 205.8 261.15C205.8 262.508 205.204 263.579 204.013 264.363C202.821 265.146 201.05 265.538 198.7 265.538C197 265.538 195.683 265.363 194.75 265.013C193.825 264.671 193.179 264.179 192.813 263.538C192.454 262.904 192.275 262.15 192.275 261.275H195.775C195.775 261.658 195.85 261.971 196 262.213C196.158 262.463 196.454 262.646 196.888 262.763C197.329 262.879 197.967 262.938 198.8 262.938C199.642 262.938 200.283 262.871 200.725 262.738C201.167 262.604 201.467 262.421 201.625 262.188C201.792 261.963 201.875 261.708 201.875 261.425C201.875 261.033 201.721 260.717 201.413 260.475C201.104 260.233 200.617 260.113 199.95 260.113H197.838C196.404 260.113 195.363 259.85 194.712 259.325C194.063 258.792 193.738 258.171 193.738 257.462C193.738 256.971 193.867 256.504 194.125 256.062C194.392 255.621 194.763 255.25 195.238 254.95C194.388 254.5 193.779 253.971 193.413 253.362C193.046 252.746 192.863 252.008 192.863 251.15C192.863 250.15 193.117 249.292 193.625 248.575C194.133 247.85 194.833 247.292 195.725 246.9C196.625 246.508 197.65 246.312 198.8 246.312C199.792 246.329 200.654 246.262 201.388 246.112C202.121 245.954 202.779 245.742 203.363 245.475C203.946 245.2 204.504 244.908 205.038 244.6ZM198.9 248.875C198.275 248.875 197.779 249.075 197.413 249.475C197.046 249.867 196.863 250.408 196.863 251.1C196.863 251.833 197.05 252.396 197.425 252.787C197.8 253.179 198.292 253.375 198.9 253.375C199.558 253.375 200.058 253.179 200.4 252.787C200.75 252.396 200.925 251.817 200.925 251.05C200.925 250.308 200.754 249.762 200.413 249.412C200.071 249.054 199.567 248.875 198.9 248.875ZM216.353 246.725V257.3H219.728V260H208.64V257.3H212.403V249.425H208.765V246.725H216.353ZM213.965 239.775C214.649 239.775 215.207 239.987 215.64 240.412C216.074 240.837 216.29 241.367 216.29 242C216.29 242.633 216.074 243.167 215.64 243.6C215.207 244.025 214.649 244.237 213.965 244.237C213.274 244.237 212.707 244.025 212.265 243.6C211.832 243.167 211.615 242.633 211.615 242C211.615 241.367 211.832 240.837 212.265 240.412C212.707 239.987 213.274 239.775 213.965 239.775ZM223.23 260V246.725H226.68L226.955 248.262C227.547 247.596 228.172 247.1 228.83 246.775C229.489 246.45 230.239 246.287 231.08 246.287C232.214 246.287 233.105 246.633 233.755 247.325C234.405 248.017 234.73 248.992 234.73 250.25V260H230.78V251.387C230.78 250.846 230.743 250.417 230.668 250.1C230.593 249.775 230.46 249.546 230.268 249.412C230.085 249.271 229.83 249.2 229.505 249.2C229.23 249.2 228.96 249.262 228.693 249.387C228.435 249.504 228.18 249.675 227.93 249.9C227.68 250.125 227.43 250.396 227.18 250.712V260H223.23Z" fill="#FF6266"/>
<path d="M381 253L356 238.566V267.434L381 253ZM279 255.5H358.5V250.5H279V255.5Z" fill="#FF6266"/>
<rect x="110.5" y="82.5" width="162" height="91" fill="#221F29" stroke="#FF6266" stroke-width="7"/>
<path d="M156.717 124.325C156.717 123.708 156.6 123.2 156.367 122.8C156.142 122.392 155.796 122.092 155.329 121.9C154.871 121.7 154.292 121.6 153.592 121.6H152.192V127.262H153.804C154.413 127.262 154.933 127.158 155.367 126.95C155.8 126.742 156.133 126.421 156.367 125.987C156.6 125.554 156.717 125 156.717 124.325ZM160.942 124.312C160.942 125.654 160.654 126.762 160.079 127.637C159.504 128.512 158.7 129.167 157.667 129.6C156.633 130.025 155.433 130.237 154.067 130.237H152.192V136H148.092V118.675H153.542C155.892 118.675 157.713 119.142 159.004 120.075C160.296 121.008 160.942 122.421 160.942 124.312ZM170.195 117.45V131.825C170.195 132.358 170.345 132.742 170.645 132.975C170.953 133.2 171.374 133.312 171.907 133.312C172.249 133.312 172.578 133.275 172.895 133.2C173.211 133.117 173.507 133.021 173.782 132.912L174.732 135.55C174.282 135.783 173.74 135.987 173.107 136.163C172.474 136.338 171.736 136.425 170.895 136.425C169.295 136.425 168.12 135.967 167.37 135.05C166.62 134.125 166.245 132.887 166.245 131.337V120.15H162.332V117.45H170.195ZM182.21 122.725V131.812C182.21 132.454 182.326 132.896 182.56 133.137C182.801 133.379 183.135 133.5 183.56 133.5C183.968 133.5 184.376 133.371 184.785 133.112C185.193 132.854 185.535 132.504 185.81 132.062V122.725H189.76V136H186.31L186.135 134.45C185.685 135.1 185.093 135.592 184.36 135.925C183.626 136.258 182.86 136.425 182.06 136.425C180.776 136.425 179.822 136.054 179.197 135.312C178.572 134.562 178.26 133.558 178.26 132.3V122.725H182.21ZM205.038 120.6L205.963 123.5C205.496 123.683 204.95 123.817 204.325 123.9C203.7 123.983 202.971 124.025 202.138 124.025C202.971 124.383 203.604 124.829 204.038 125.362C204.479 125.887 204.7 126.579 204.7 127.437C204.7 128.296 204.471 129.062 204.013 129.737C203.554 130.404 202.904 130.929 202.063 131.312C201.221 131.696 200.225 131.887 199.075 131.887C198.792 131.887 198.525 131.875 198.275 131.85C198.025 131.825 197.779 131.787 197.538 131.737C197.396 131.812 197.288 131.921 197.213 132.062C197.138 132.204 197.1 132.35 197.1 132.5C197.1 132.708 197.183 132.896 197.35 133.062C197.525 133.229 197.929 133.312 198.562 133.312H200.738C201.771 133.312 202.663 133.479 203.413 133.812C204.171 134.146 204.758 134.6 205.175 135.175C205.592 135.75 205.8 136.408 205.8 137.15C205.8 138.508 205.204 139.579 204.013 140.363C202.821 141.146 201.05 141.538 198.7 141.538C197 141.538 195.683 141.363 194.75 141.013C193.825 140.671 193.179 140.179 192.813 139.538C192.454 138.904 192.275 138.15 192.275 137.275H195.775C195.775 137.658 195.85 137.971 196 138.213C196.158 138.463 196.454 138.646 196.888 138.763C197.329 138.879 197.967 138.938 198.8 138.938C199.642 138.938 200.283 138.871 200.725 138.738C201.167 138.604 201.467 138.421 201.625 138.188C201.792 137.963 201.875 137.708 201.875 137.425C201.875 137.033 201.721 136.717 201.413 136.475C201.104 136.233 200.617 136.113 199.95 136.113H197.838C196.404 136.113 195.363 135.85 194.712 135.325C194.063 134.792 193.738 134.171 193.738 133.462C193.738 132.971 193.867 132.504 194.125 132.062C194.392 131.621 194.763 131.25 195.238 130.95C194.388 130.5 193.779 129.971 193.413 129.362C193.046 128.746 192.863 128.008 192.863 127.15C192.863 126.15 193.117 125.292 193.625 124.575C194.133 123.85 194.833 123.292 195.725 122.9C196.625 122.508 197.65 122.312 198.8 122.312C199.792 122.329 200.654 122.262 201.388 122.112C202.121 121.954 202.779 121.742 203.363 121.475C203.946 121.2 204.504 120.908 205.038 120.6ZM198.9 124.875C198.275 124.875 197.779 125.075 197.413 125.475C197.046 125.867 196.863 126.408 196.863 127.1C196.863 127.833 197.05 128.396 197.425 128.787C197.8 129.179 198.292 129.375 198.9 129.375C199.558 129.375 200.058 129.179 200.4 128.787C200.75 128.396 200.925 127.817 200.925 127.05C200.925 126.308 200.754 125.762 200.413 125.412C200.071 125.054 199.567 124.875 198.9 124.875ZM216.353 122.725V133.3H219.728V136H208.64V133.3H212.403V125.425H208.765V122.725H216.353ZM213.965 115.775C214.649 115.775 215.207 115.987 215.64 116.412C216.074 116.837 216.29 117.367 216.29 118C216.29 118.633 216.074 119.167 215.64 119.6C215.207 120.025 214.649 120.237 213.965 120.237C213.274 120.237 212.707 120.025 212.265 119.6C211.832 119.167 211.615 118.633 211.615 118C211.615 117.367 211.832 116.837 212.265 116.412C212.707 115.987 213.274 115.775 213.965 115.775ZM223.23 136V122.725H226.68L226.955 124.262C227.547 123.596 228.172 123.1 228.83 122.775C229.489 122.45 230.239 122.287 231.08 122.287C232.214 122.287 233.105 122.633 233.755 123.325C234.405 124.017 234.73 124.992 234.73 126.25V136H230.78V127.387C230.78 126.846 230.743 126.417 230.668 126.1C230.593 125.775 230.46 125.546 230.268 125.412C230.085 125.271 229.83 125.2 229.505 125.2C229.23 125.2 228.96 125.262 228.693 125.387C228.435 125.504 228.18 125.675 227.93 125.9C227.68 126.125 227.43 126.396 227.18 126.712V136H223.23Z" fill="#FF6266"/>
<path d="M331 129H333.5V126.5H331V129ZM331 182H328.5V184.5H331V182ZM380.5 182L355.5 167.566V196.434L380.5 182ZM279 131.5H331V126.5H279V131.5ZM328.5 129V182H333.5V129H328.5ZM331 184.5H358V179.5H331V184.5Z" fill="#FF6266"/>
<defs>
<filter id="filter0_d_1_33" x="384" y="149" width="300" height="206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="7" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_1_33"/>
<feOffset/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.384314 0 0 0 0 0.4 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_33"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_33" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>the1mason.com/posts/</title>
<link rel="canonical" href="the1mason.com/posts/">
<meta name="robots" content="noindex">
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=the1mason.com/posts/">
</head>
</html>

BIN
public/semitcrab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

28
public/sitemap.xml Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>the1mason.com/</loc>
<lastmod>2024-01-20T00:00:00+00:00</lastmod>
</url><url>
<loc>the1mason.com/posts/modular-app-1/</loc>
<lastmod>2024-01-20T00:00:00+00:00</lastmod>
</url><url>
<loc>the1mason.com/tags/dotnet/</loc>
<lastmod>2024-01-20T00:00:00+00:00</lastmod>
</url><url>
<loc>the1mason.com/posts/</loc>
<lastmod>2024-01-20T00:00:00+00:00</lastmod>
</url><url>
<loc>the1mason.com/tags/prototype/</loc>
<lastmod>2024-01-20T00:00:00+00:00</lastmod>
</url><url>
<loc>the1mason.com/tags/</loc>
<lastmod>2024-01-20T00:00:00+00:00</lastmod>
</url><url>
<loc>the1mason.com/tags/web/</loc>
<lastmod>2024-01-20T00:00:00+00:00</lastmod>
</url><url>
<loc>the1mason.com/categories/</loc>
</url>
</urlset>

3
public/styles.css Normal file

File diff suppressed because one or more lines are too long

45
public/styles.css.map Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>dotnet :: the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/tags/dotnet/" />
<link rel="stylesheet" href="/the1mason.com/styles.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="website" />
<meta property="og:title" content="dotnet">
<meta property="og:description" content="" />
<meta property="og:url" content="the1mason.com/tags/dotnet/" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/img/favicon/red.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
<link href="the1mason.com/tags/dotnet/index.xml" rel="alternate" type="application/rss+xml" title="the1mason" />
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<div class="posts">
<article class="post on-list">
<h1 class="post-title">
<a href="the1mason.com/posts/modular-app-1/">#1 Plugin-Based Web App in Dotnet - The Idea</a>
</h1>
<div class="post-meta">
<time class="post-date">
2024-01-20 ::
</time>
<span class="post-author">the1mason</span>
</div>
<span class="post-tags">
#<a href="the1mason.com/tags/dotnet/">dotnet</a>&nbsp;
#<a href="the1mason.com/tags/web/">web</a>&nbsp;
#<a href="the1mason.com/tags/prototype/">prototype</a>&nbsp;
</span>
<img src="the1mason.com/posts/modular-app/title.svg"
class="post-cover"
alt="#1 Plugin-Based Web App in Dotnet - The Idea"
title="Cover Image" />
<div class="post-content">
Have you ever thought about making a web application, that could be easily extended by third-party developers? I&rsquo;ve been thinking about making this app for a while, so here&rsquo;s my experience&hellip;
</div>
<div>
<a class="read-more button" href="the1mason.com/posts/modular-app-1/">Read more →</a>
</div>
</article>
<div class="pagination">
<div class="pagination__buttons">
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>dotnet on the1mason</title>
<link>the1mason.com/tags/dotnet/</link>
<description>Recent content in dotnet on the1mason</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<lastBuildDate>Sat, 20 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="the1mason.com/tags/dotnet/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>#1 Plugin-Based Web App in Dotnet - The Idea</title>
<link>the1mason.com/posts/modular-app-1/</link>
<pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate>
<guid>the1mason.com/posts/modular-app-1/</guid>
<description>Chapters Writing those takes time. Expect to see one published per one-two weeks.
Idea, Stack
Loading plugins
PluginBase, IPlugin
Creating plugin, DependencyInjection
Controllers, Views
Hooks and Triggers - better event system
Advanced: Unit tests, unloading plugins
Introduction Have you ever heard of plugins? These are loadable libraries, extending your application.
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development.</description>
<content>&lt;script src=&#34;the1mason.com/js/repo-card.js&#34;&gt;&lt;/script&gt;
&lt;div class=&#34;repo-card&#34; data-repo=&#34;the1mason/Prototype.ModularMVC&#34; data-theme=&#34;dark-theme&#34;&gt;&lt;/div&gt;
&lt;h1 id=&#34;chapters&#34;&gt;Chapters&lt;/h1&gt;
&lt;p&gt;Writing those takes time. Expect to see one published per one-two weeks.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Idea, Stack&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Loading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;PluginBase, IPlugin&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Creating plugin, DependencyInjection&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Controllers, Views&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Hooks and Triggers - better event system&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Advanced: Unit tests, unloading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Have you ever heard of plugins? These are loadable libraries, extending your application.&lt;br&gt;
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development. These articles are a step by step guide to making your own plugin-based web app prototype.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I assume some that readers have some knowledge of C# and design patterns&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;problem&#34;&gt;Problem&lt;/h1&gt;
&lt;p&gt;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. For example, a shopping website might have plugins for different payment systems, or a comment section under the product page. For me this also means, that instead of making one feature-rich website, that would be so specific to my needs, that it wouldn&amp;rsquo;t of any use to anyone but me, I can write a bunch of smaller modules, that could be used by someone, without having to configure other modules.&lt;/p&gt;
&lt;h1 id=&#34;choosing-my-stack&#34;&gt;Choosing my stack&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;the1mason.com/posts/modular-app/stack.svg&#34; alt=&#34;C#, MVC, HTMX&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;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&amp;rsquo;t been described in detail as much online.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t seen such plugin-based sites written in C#. There are some projects, using plugin based architecture&amp;hellip; Well, there&amp;rsquo;s even a &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support&#34;&gt;Microsoft Learn Article&lt;/a&gt; about building such an app!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; You see&amp;hellip; there&amp;rsquo;s a problem: Neither &lt;code&gt;learn.microsoft.com&lt;/code&gt; nor any other webside covers dynamically updating web interface with plugins! If you want to learn about it, it&amp;rsquo;s the right place. Also just loading libraries isn&amp;rsquo;t enough because app also has to provide some ways for plugins to interact with it, which is also covered here!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;MVC with HTMX&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div style=&#34;display: flex; justify-content: center&#34;&gt;
&lt;img src=&#34;the1mason.com/posts/modular-app/createdwith.jpeg&#34; style=&#34;height: 100px;&#34;/&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;HTMX uses &lt;a href=&#34;https://htmx.org/essays/hateoas/&#34;&gt;Hypermedia as the Engine of Application State (HATEOAS)&lt;/a&gt; - it&amp;rsquo;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&amp;rsquo;t show the withdrawal option if balance is 0 or less). With HATEOAS, the server just won&amp;rsquo;t give the link to withdraw money, making this action impossible in the first place.&lt;/p&gt;
&lt;p&gt;HTMX would allow this app to be extended more easily as well. Most of the modern JS frameworks require transpiling, bundling and other sorts of stuff. This means that when a plugin is installed the client is most likely will have to be rebuilt. This is slow and needs additional dependencies.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have you heard about Blazor WASM? You can just write client code in C#!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Blazor WASM does not support dynamic loading for plugins. Because of that, plugins won&amp;rsquo;t be able to extend the client. Also it&amp;rsquo;s initial load time is stupidly slow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The next article will cover the following topocs: Loading plugins in runtime, creating plugin&amp;rsquo;s instances, app-plugin communication. I&amp;rsquo;ll have the link here when I&amp;rsquo;m done writing it!&lt;/p&gt;
&lt;!--
---
# Loading plugins
C#, being a compiled language, can&#39;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.
&gt; Wait, wait, wait! Using... what? I&#39;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&#39;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&#39;s dependencies! That&#39;s where `AssemblyDependencyResolver` comes in. It&#39;ll find all of plugin&#39;s `.dll` dependencies and load those as well.
&gt; 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&#39;s dependencies. This will allow multiple plugins to have same dependencies with different versions.
There&#39;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&#39;ll build communication using those.
**Dependency Injection**
Before the app is built and ran, --&gt;</content>
</item>
</channel>
</rss>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>the1mason.com/tags/dotnet/</title>
<link rel="canonical" href="the1mason.com/tags/dotnet/">
<meta name="robots" content="noindex">
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=the1mason.com/tags/dotnet/">
</head>
</html>

200
public/tags/index.html Normal file
View File

@ -0,0 +1,200 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Tags :: the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/tags/" />
<link rel="stylesheet" href="/the1mason.com/styles.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Tags">
<meta property="og:description" content="" />
<meta property="og:url" content="the1mason.com/tags/" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/img/favicon/red.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
<link href="the1mason.com/tags/index.xml" rel="alternate" type="application/rss+xml" title="the1mason" />
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<div class="terms">
<h1>Tags</h1>
<ul>
<li>
<a class="terms-title" href="the1mason.com/tags/dotnet/">dotnet (1)</a>
</li>
<li>
<a class="terms-title" href="the1mason.com/tags/prototype/">prototype (1)</a>
</li>
<li>
<a class="terms-title" href="the1mason.com/tags/web/">web (1)</a>
</li>
</ul>
</div>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

41
public/tags/index.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Tags on the1mason</title>
<link>the1mason.com/tags/</link>
<description>Recent content in Tags on the1mason</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<lastBuildDate>Sat, 20 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="the1mason.com/tags/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>dotnet</title>
<link>the1mason.com/tags/dotnet/</link>
<pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate>
<guid>the1mason.com/tags/dotnet/</guid>
<description></description>
<content></content>
</item>
<item>
<title>prototype</title>
<link>the1mason.com/tags/prototype/</link>
<pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate>
<guid>the1mason.com/tags/prototype/</guid>
<description></description>
<content></content>
</item>
<item>
<title>web</title>
<link>the1mason.com/tags/web/</link>
<pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate>
<guid>the1mason.com/tags/web/</guid>
<description></description>
<content></content>
</item>
</channel>
</rss>

View File

@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>prototype :: the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/tags/prototype/" />
<link rel="stylesheet" href="/the1mason.com/styles.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="website" />
<meta property="og:title" content="prototype">
<meta property="og:description" content="" />
<meta property="og:url" content="the1mason.com/tags/prototype/" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/img/favicon/red.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
<link href="the1mason.com/tags/prototype/index.xml" rel="alternate" type="application/rss+xml" title="the1mason" />
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<div class="posts">
<article class="post on-list">
<h1 class="post-title">
<a href="the1mason.com/posts/modular-app-1/">#1 Plugin-Based Web App in Dotnet - The Idea</a>
</h1>
<div class="post-meta">
<time class="post-date">
2024-01-20 ::
</time>
<span class="post-author">the1mason</span>
</div>
<span class="post-tags">
#<a href="the1mason.com/tags/dotnet/">dotnet</a>&nbsp;
#<a href="the1mason.com/tags/web/">web</a>&nbsp;
#<a href="the1mason.com/tags/prototype/">prototype</a>&nbsp;
</span>
<img src="the1mason.com/posts/modular-app/title.svg"
class="post-cover"
alt="#1 Plugin-Based Web App in Dotnet - The Idea"
title="Cover Image" />
<div class="post-content">
Have you ever thought about making a web application, that could be easily extended by third-party developers? I&rsquo;ve been thinking about making this app for a while, so here&rsquo;s my experience&hellip;
</div>
<div>
<a class="read-more button" href="the1mason.com/posts/modular-app-1/">Read more →</a>
</div>
</article>
<div class="pagination">
<div class="pagination__buttons">
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>prototype on the1mason</title>
<link>the1mason.com/tags/prototype/</link>
<description>Recent content in prototype on the1mason</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<lastBuildDate>Sat, 20 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="the1mason.com/tags/prototype/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>#1 Plugin-Based Web App in Dotnet - The Idea</title>
<link>the1mason.com/posts/modular-app-1/</link>
<pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate>
<guid>the1mason.com/posts/modular-app-1/</guid>
<description>Chapters Writing those takes time. Expect to see one published per one-two weeks.
Idea, Stack
Loading plugins
PluginBase, IPlugin
Creating plugin, DependencyInjection
Controllers, Views
Hooks and Triggers - better event system
Advanced: Unit tests, unloading plugins
Introduction Have you ever heard of plugins? These are loadable libraries, extending your application.
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development.</description>
<content>&lt;script src=&#34;the1mason.com/js/repo-card.js&#34;&gt;&lt;/script&gt;
&lt;div class=&#34;repo-card&#34; data-repo=&#34;the1mason/Prototype.ModularMVC&#34; data-theme=&#34;dark-theme&#34;&gt;&lt;/div&gt;
&lt;h1 id=&#34;chapters&#34;&gt;Chapters&lt;/h1&gt;
&lt;p&gt;Writing those takes time. Expect to see one published per one-two weeks.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Idea, Stack&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Loading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;PluginBase, IPlugin&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Creating plugin, DependencyInjection&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Controllers, Views&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Hooks and Triggers - better event system&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Advanced: Unit tests, unloading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Have you ever heard of plugins? These are loadable libraries, extending your application.&lt;br&gt;
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development. These articles are a step by step guide to making your own plugin-based web app prototype.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I assume some that readers have some knowledge of C# and design patterns&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;problem&#34;&gt;Problem&lt;/h1&gt;
&lt;p&gt;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. For example, a shopping website might have plugins for different payment systems, or a comment section under the product page. For me this also means, that instead of making one feature-rich website, that would be so specific to my needs, that it wouldn&amp;rsquo;t of any use to anyone but me, I can write a bunch of smaller modules, that could be used by someone, without having to configure other modules.&lt;/p&gt;
&lt;h1 id=&#34;choosing-my-stack&#34;&gt;Choosing my stack&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;the1mason.com/posts/modular-app/stack.svg&#34; alt=&#34;C#, MVC, HTMX&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;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&amp;rsquo;t been described in detail as much online.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t seen such plugin-based sites written in C#. There are some projects, using plugin based architecture&amp;hellip; Well, there&amp;rsquo;s even a &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support&#34;&gt;Microsoft Learn Article&lt;/a&gt; about building such an app!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; You see&amp;hellip; there&amp;rsquo;s a problem: Neither &lt;code&gt;learn.microsoft.com&lt;/code&gt; nor any other webside covers dynamically updating web interface with plugins! If you want to learn about it, it&amp;rsquo;s the right place. Also just loading libraries isn&amp;rsquo;t enough because app also has to provide some ways for plugins to interact with it, which is also covered here!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;MVC with HTMX&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div style=&#34;display: flex; justify-content: center&#34;&gt;
&lt;img src=&#34;the1mason.com/posts/modular-app/createdwith.jpeg&#34; style=&#34;height: 100px;&#34;/&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;HTMX uses &lt;a href=&#34;https://htmx.org/essays/hateoas/&#34;&gt;Hypermedia as the Engine of Application State (HATEOAS)&lt;/a&gt; - it&amp;rsquo;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&amp;rsquo;t show the withdrawal option if balance is 0 or less). With HATEOAS, the server just won&amp;rsquo;t give the link to withdraw money, making this action impossible in the first place.&lt;/p&gt;
&lt;p&gt;HTMX would allow this app to be extended more easily as well. Most of the modern JS frameworks require transpiling, bundling and other sorts of stuff. This means that when a plugin is installed the client is most likely will have to be rebuilt. This is slow and needs additional dependencies.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have you heard about Blazor WASM? You can just write client code in C#!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Blazor WASM does not support dynamic loading for plugins. Because of that, plugins won&amp;rsquo;t be able to extend the client. Also it&amp;rsquo;s initial load time is stupidly slow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The next article will cover the following topocs: Loading plugins in runtime, creating plugin&amp;rsquo;s instances, app-plugin communication. I&amp;rsquo;ll have the link here when I&amp;rsquo;m done writing it!&lt;/p&gt;
&lt;!--
---
# Loading plugins
C#, being a compiled language, can&#39;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.
&gt; Wait, wait, wait! Using... what? I&#39;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&#39;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&#39;s dependencies! That&#39;s where `AssemblyDependencyResolver` comes in. It&#39;ll find all of plugin&#39;s `.dll` dependencies and load those as well.
&gt; 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&#39;s dependencies. This will allow multiple plugins to have same dependencies with different versions.
There&#39;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&#39;ll build communication using those.
**Dependency Injection**
Before the app is built and ran, --&gt;</content>
</item>
</channel>
</rss>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>the1mason.com/tags/prototype/</title>
<link rel="canonical" href="the1mason.com/tags/prototype/">
<meta name="robots" content="noindex">
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=the1mason.com/tags/prototype/">
</head>
</html>

228
public/tags/web/index.html Normal file
View File

@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>web :: the1mason</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="robots" content="noodp" />
<link rel="canonical" href="the1mason.com/tags/web/" />
<link rel="stylesheet" href="/the1mason.com/styles.css">
<link rel="shortcut icon" href="the1mason.com/img/theme-colors/red.png">
<link rel="apple-touch-icon" href="the1mason.com/img/theme-colors/red.png">
<meta name="twitter:card" content="summary" />
<meta property="og:locale" content="en" />
<meta property="og:type" content="website" />
<meta property="og:title" content="web">
<meta property="og:description" content="" />
<meta property="og:url" content="the1mason.com/tags/web/" />
<meta property="og:site_name" content="the1mason" />
<meta property="og:image" content="the1mason.com/img/favicon/red.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
<link href="the1mason.com/tags/web/index.xml" rel="alternate" type="application/rss+xml" title="the1mason" />
</head>
<body class="red">
<div class="container center headings--one-size">
<header class="header">
<div class="header__inner">
<div class="header__logo">
<a href="/">
<div class="logo">
the1mason
</div>
</a>
</div>
<ul class="menu menu--mobile">
<li class="menu__trigger">Menu&nbsp;</li>
<li>
<ul class="menu__dropdown">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</li>
</ul>
</div>
<nav class="navigation-menu">
<ul class="navigation-menu__inner menu--desktop">
<li><a href="https://github.com/the1mason">Github</a></li>
<li><a href="mailto:mail@the1mason.com">Mail</a></li>
<li><a href="the1mason.com/social">Social</a></li>
</ul>
</nav>
</header>
<div class="content">
<div class="posts">
<article class="post on-list">
<h1 class="post-title">
<a href="the1mason.com/posts/modular-app-1/">#1 Plugin-Based Web App in Dotnet - The Idea</a>
</h1>
<div class="post-meta">
<time class="post-date">
2024-01-20 ::
</time>
<span class="post-author">the1mason</span>
</div>
<span class="post-tags">
#<a href="the1mason.com/tags/dotnet/">dotnet</a>&nbsp;
#<a href="the1mason.com/tags/web/">web</a>&nbsp;
#<a href="the1mason.com/tags/prototype/">prototype</a>&nbsp;
</span>
<img src="the1mason.com/posts/modular-app/title.svg"
class="post-cover"
alt="#1 Plugin-Based Web App in Dotnet - The Idea"
title="Cover Image" />
<div class="post-content">
Have you ever thought about making a web application, that could be easily extended by third-party developers? I&rsquo;ve been thinking about making this app for a while, so here&rsquo;s my experience&hellip;
</div>
<div>
<a class="read-more button" href="the1mason.com/posts/modular-app-1/">Read more →</a>
</div>
</article>
<div class="pagination">
<div class="pagination__buttons">
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="footer__inner">
<div class="copyright">
<span>© 2024 Powered by <a href="http://gohugo.io">Hugo</a></span>
<span>:: <a href="https://github.com/panr/hugo-theme-terminal" target="_blank">Theme</a> made by <a href="https://github.com/panr" target="_blank">panr</a></span>
</div>
</div>
</footer>
<script type="text/javascript" src="/the1mason.com/bundle.min.js"></script>
</div>
</body>
</html>

123
public/tags/web/index.xml Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>web on the1mason</title>
<link>the1mason.com/tags/web/</link>
<description>Recent content in web on the1mason</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<lastBuildDate>Sat, 20 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="the1mason.com/tags/web/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>#1 Plugin-Based Web App in Dotnet - The Idea</title>
<link>the1mason.com/posts/modular-app-1/</link>
<pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate>
<guid>the1mason.com/posts/modular-app-1/</guid>
<description>Chapters Writing those takes time. Expect to see one published per one-two weeks.
Idea, Stack
Loading plugins
PluginBase, IPlugin
Creating plugin, DependencyInjection
Controllers, Views
Hooks and Triggers - better event system
Advanced: Unit tests, unloading plugins
Introduction Have you ever heard of plugins? These are loadable libraries, extending your application.
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development.</description>
<content>&lt;script src=&#34;the1mason.com/js/repo-card.js&#34;&gt;&lt;/script&gt;
&lt;div class=&#34;repo-card&#34; data-repo=&#34;the1mason/Prototype.ModularMVC&#34; data-theme=&#34;dark-theme&#34;&gt;&lt;/div&gt;
&lt;h1 id=&#34;chapters&#34;&gt;Chapters&lt;/h1&gt;
&lt;p&gt;Writing those takes time. Expect to see one published per one-two weeks.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Idea, Stack&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Loading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;PluginBase, IPlugin&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Creating plugin, DependencyInjection&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Controllers, Views&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Hooks and Triggers - better event system&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;del&gt;Advanced: Unit tests, unloading plugins&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Have you ever heard of plugins? These are loadable libraries, extending your application.&lt;br&gt;
This series of articles is an overview of my plugin-based web application prototype and mechanisms behind it&amp;rsquo;s features, as well as my thought process and decision making during development. These articles are a step by step guide to making your own plugin-based web app prototype.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I assume some that readers have some knowledge of C# and design patterns&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;problem&#34;&gt;Problem&lt;/h1&gt;
&lt;p&gt;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. For example, a shopping website might have plugins for different payment systems, or a comment section under the product page. For me this also means, that instead of making one feature-rich website, that would be so specific to my needs, that it wouldn&amp;rsquo;t of any use to anyone but me, I can write a bunch of smaller modules, that could be used by someone, without having to configure other modules.&lt;/p&gt;
&lt;h1 id=&#34;choosing-my-stack&#34;&gt;Choosing my stack&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;the1mason.com/posts/modular-app/stack.svg&#34; alt=&#34;C#, MVC, HTMX&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;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&amp;rsquo;t been described in detail as much online.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t seen such plugin-based sites written in C#. There are some projects, using plugin based architecture&amp;hellip; Well, there&amp;rsquo;s even a &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support&#34;&gt;Microsoft Learn Article&lt;/a&gt; about building such an app!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Why would I even bother to write all these posts and making prototypes? Even more: Why would someone be interested in such post?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; You see&amp;hellip; there&amp;rsquo;s a problem: Neither &lt;code&gt;learn.microsoft.com&lt;/code&gt; nor any other webside covers dynamically updating web interface with plugins! If you want to learn about it, it&amp;rsquo;s the right place. Also just loading libraries isn&amp;rsquo;t enough because app also has to provide some ways for plugins to interact with it, which is also covered here!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;MVC with HTMX&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div style=&#34;display: flex; justify-content: center&#34;&gt;
&lt;img src=&#34;the1mason.com/posts/modular-app/createdwith.jpeg&#34; style=&#34;height: 100px;&#34;/&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;HTMX uses &lt;a href=&#34;https://htmx.org/essays/hateoas/&#34;&gt;Hypermedia as the Engine of Application State (HATEOAS)&lt;/a&gt; - it&amp;rsquo;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&amp;rsquo;t show the withdrawal option if balance is 0 or less). With HATEOAS, the server just won&amp;rsquo;t give the link to withdraw money, making this action impossible in the first place.&lt;/p&gt;
&lt;p&gt;HTMX would allow this app to be extended more easily as well. Most of the modern JS frameworks require transpiling, bundling and other sorts of stuff. This means that when a plugin is installed the client is most likely will have to be rebuilt. This is slow and needs additional dependencies.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have you heard about Blazor WASM? You can just write client code in C#!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Blazor WASM does not support dynamic loading for plugins. Because of that, plugins won&amp;rsquo;t be able to extend the client. Also it&amp;rsquo;s initial load time is stupidly slow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The next article will cover the following topocs: Loading plugins in runtime, creating plugin&amp;rsquo;s instances, app-plugin communication. I&amp;rsquo;ll have the link here when I&amp;rsquo;m done writing it!&lt;/p&gt;
&lt;!--
---
# Loading plugins
C#, being a compiled language, can&#39;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.
&gt; Wait, wait, wait! Using... what? I&#39;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&#39;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&#39;s dependencies! That&#39;s where `AssemblyDependencyResolver` comes in. It&#39;ll find all of plugin&#39;s `.dll` dependencies and load those as well.
&gt; 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&#39;s dependencies. This will allow multiple plugins to have same dependencies with different versions.
There&#39;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&#39;ll build communication using those.
**Dependency Injection**
Before the app is built and ran, --&gt;</content>
</item>
</channel>
</rss>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>the1mason.com/tags/web/</title>
<link rel="canonical" href="the1mason.com/tags/web/">
<meta name="robots" content="noindex">
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=the1mason.com/tags/web/">
</head>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"Target":"styles.css","MediaType":"text/css","Data":{}}

View File

@ -0,0 +1 @@
{"Target":"css/red-local.css","MediaType":"text/css","Data":{}}