315 lines
12 KiB
HTML
315 lines
12 KiB
HTML
<!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've been thinking about making this app for a while, so here'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've been thinking about making this app for a while, so here'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 +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 ▾</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>
|
|
|
|
#<a href="the1mason.com/tags/web/">web</a>
|
|
|
|
#<a href="the1mason.com/tags/prototype/">prototype</a>
|
|
|
|
</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">⌗</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">⌗</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’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">⌗</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’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">⌗</a> </h1>
|
|
<p><img src="/posts/modular-app/stack.svg" alt="C#, MVC, HTMX"></p>
|
|
<hr>
|
|
<p><strong>C#</strong></p>
|
|
<p>I’m a dotnet developer and I write C# code for living. This project is as much of an excersise for me as it is an interesting design prototype for C#, that hasn’t been described in detail as much online.</p>
|
|
<p>I haven’t seen such plugin-based sites written in C#. There are some projects, using plugin based architecture… Well, there’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… there’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’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!</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’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.</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’t be able to extend the client. Also it’s initial load time is stupidly slow.</p>
|
|
<hr>
|
|
<p>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!</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>
|