Skip to content

Chaleshka/ModuWeb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

64 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ModuWeb

ModuWeb is a .NET web application that supports dynamic runtime loading, reloading, and unloading of external modules (.dll files). Each module is self-contained and can expose custom HTTP routes, CORS policies, and request handlers.


🧩 Features

  • πŸ”„ Hot-reloadable modules – automatically reloads modules when their .dll files are updated or replaced.
  • πŸ“ File system watching – monitors the modules/ folder for .dll changes using FileSystemWatcher.
  • 🌐 Per-module CORS – modules define their own CORS rules.
  • πŸ”€ Custom middleware routing – routes HTTP requests to appropriate modules based on URL.
  • πŸ’Ύ Session support – every module can create and/or use session storage.
  • ⚑ Event system – allows modules to subscribe to and react to system events.
  • πŸ’¬ Message system – enables modules to communicate with each other.
  • 🧾 Built-in logger – simple color-coded console logger for info, warnings, and errors.
  • πŸ–ΌοΈ Razor view engine – runtime Razor compilation via RazorLight for HTML pages with models.
  • πŸ“‘ Server-Sent Events (SSE) – built-in support for real-time server-to-client streaming with a fluent Razor helper.

πŸ“ Project Structure

ModuWeb/
β”‚
β”œβ”€β”€ Properties/
β”‚   └── launchSettings.json                 # Startup settings for dev mode
β”‚
β”œβ”€β”€ Events/
β”‚   β”œβ”€β”€ Events.cs                           # Contains all events
β”‚   β”œβ”€β”€ ModuleLoadedEventArgs.cs            # Args for event about loaded module
β”‚   β”œβ”€β”€ ModuleMessageSentEventArgs.cs       # Args for event about sent message
β”‚   β”œβ”€β”€ ModuleUnloadedEventArgs.cs          # Args for event about unloaded module
β”‚   β”œβ”€β”€ RequestReceivedEventArgs.cs         # Args for event about received http request
β”‚   └── SafeEvent.cs                        # Base and safe class for events
β”‚
β”œβ”€β”€ examples/                               # Examples modules
β”‚
β”œβ”€β”€ Cors/
β”‚   β”œβ”€β”€ DynamicCorsPolicyProvider.cs        # CORS policy provider per module
β”‚   β”œβ”€β”€ Headers.cs                          # CORS headers constants
β”‚   └── ModuleCorsGuardMiddleware.cs        # Middleware for handling CORS per module
β”‚
β”œβ”€β”€ Extensions/
β”‚   β”œβ”€β”€ ArrayExtension.cs                   # Little extension for array
β”‚   β”œβ”€β”€ HttpRequestExtension.cs             # Extension for get request data (from query string or json body)
β”‚   β”œβ”€β”€ HttpResponseExtension.cs            # Extensions for Razor page rendering and SSE streaming
β”‚   β”œβ”€β”€ JsonOptionExtension.cs              # JSON serializer options (camelCase, null handling)
β”‚   β”œβ”€β”€ SessionExtensions.cs                # Session helper extensions for HttpContext
β”‚   β”œβ”€β”€ SseHtmlHelper.cs                    # Fluent SSE helper for Razor views (Sse.Stream(...).Bind(...))
β”‚   └── StringExtension.cs                  # Little extension for string.Replace(old, new, count)
β”‚
β”œβ”€β”€ ModuleLoadSystem/
β”‚   β”œβ”€β”€ ModuleLoadContext.cs                # Custom AssemblyLoadContext
β”‚   β”œβ”€β”€ ModuleManager.cs                    # Loads/unloads modules and handles lifecycle
β”‚   └── ModuleWatcher.cs                    # Watches for module file changes
β”‚
β”œβ”€β”€ ModuleMessenger/
β”‚   β”œβ”€β”€ ModuleMessage.cs                    # Module message that every moudle can create and receive
β”‚   └── ModuleMessenger.cs                  # System handler for module messages
β”‚
β”œβ”€β”€ SessionSystem/
β”‚   β”œβ”€β”€ ISessionService.cs                  # Interface of session service
β”‚   β”œβ”€β”€ LiteDbSessionService.cs             # Session service for create and working with sessions
β”‚   └── SessionData.cs                      # Data that store into database
β”‚
β”œβ”€β”€ Storage/
β”‚   β”œβ”€β”€ IStorageService.cs                  # Interface of storage service
β”‚   └── LiteDbStorageService.cs             # Data that store into database
β”‚
β”œβ”€β”€ ViewEngine/
β”‚   β”œβ”€β”€ IModuleViewEngine.cs                # Interface for module view engine
β”‚   └── ModuleViewEngine.cs                 # RazorLight-based runtime Razor compilation
β”‚
β”œβ”€β”€ appsettings.json                        # Default appsettings
β”œβ”€β”€ LICENSE.txt                             # License for this project
β”œβ”€β”€ Logger.cs                               # Static logger with color output
β”œβ”€β”€ ModuleBase.cs                           # Base class for all modules
β”œβ”€β”€ ModuleMiddleware.cs                     # Middleware for routing requests to modules
β”œβ”€β”€ Program.cs                              # Application entry point
β”œβ”€β”€ QueryParser.cs                          # Tool for parse args from query
└── RouteDictionary.cs                      # Path + method β†’ handler registry

πŸš€ Getting Started

To run the project, make sure you have the .NET Runtime (Microsoft.AspNetCore.App) or SDK version 9.0.2 or higher installed.

How can you check if SDK is installed?

dotnet --list-sdks

If it's installed, you must see something like that:

9.0.200 [C:\Program Files\dotnet\sdk]

If it's not installed, you need to install it there.


How can you check if Runtime is installed?

dotnet --list-runtimes

If it's installed, you must see something like that:

Microsoft.AspNetCore.App 9.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]

If it's not installed, you need to install it there. Choose Run server apps.



🚦 Running the Application

Option 1: Build from Source

  1. Clone the repository:
git clone https://github.com/Chaleshka/ModuWeb.git
cd ModuWeb
  1. Build the solution using .NET SDK 9.0.2+.
dotnet build
  1. Run the app:
dotnet run

Option 2: Run from Release

  1. Download the latest release from the Releases page
  2. Extract the archive to your preferred directory
  3. Launch the app:
# Windows
ModuWeb.exe

# Linux/macOS:
dotnet ModuWeb.dll

How to load modules?

After launching the program, the modules folder will be created. You need to put all the modules you need in it.
Also, if dependencies are required, drop them in the modules/dependencies folder.
If everything is fine with the modules, they will be loaded automatically.


πŸ”§ Module Development

Firstly create project:

dotnet new classlib -n ModuleName
cd ModuleName

Then you need to add to dependencies ModuWeb.dll.
Important: To use HttpContext and other ASP.NET Core types in your module, add a FrameworkReference to your .csproj file:

<ItemGroup>
  <!-- Add this to get HttpContext and ASP.NET Core dependencies -->
  <FrameworkReference Include="Microsoft.AspNetCore.App" />
  
  <!-- Reference to ModuWeb.dll -->
  <Reference Include="ModuWeb">
    <HintPath>path/to/ModuWeb.dll</HintPath>
  </Reference>
</ItemGroup>

This way you don't need to manually add NuGet packages for ASP.NET Core dependencies.


A module must inherit from ModuleBase and override methods such as:

public class HelloWorldModule : ModuleBase
{
    public override async Task OnModuleLoad()
    {
        Map("hello", "GET", HelloWorldHandler);
    }

    public async Task HelloWorldHandler(HttpContext context)
    {
        context.Response.StatusCode = 200;
        await context.Response.WriteAsync("Hello World!");
    }
}
  • Map(string path, string method, Func<HttpContext, Task> handler) β€” maps a route.
  • Handle(...) β€” receives and routes the request.
  • WithOriginsCors, WithHeadersCors, BlockFailedCorsRequests β€” specify CORS policies.
  • ModuleName β€” name of module that will used for some system tools.
  • OnModuleLoad() β€” optional initialization logic.
  • OnModuleUnLoad() β€” optional cleanup logic.

Module files may have unique names: - index.dll β€” special module name used to handle the main page (/ or /index).


You can also see the examples in examples.


πŸ–ΌοΈ Razor Views

Modules can render HTML pages using Razor (.cshtml) templates via RazorLight. Views are embedded as resources in the module DLL.

Setup

  1. Mark .cshtml files as Embedded Resource in your .csproj:
<ItemGroup>
  <EmbeddedResource Include="Views\**\*.cshtml" />
</ItemGroup>

Views are registered automatically when the module is loaded β€” no extra code needed.

  1. Render a page from a handler:
private async Task PageHandler(HttpContext context)
{
    var model = new { Title = "Hello", Message = "World" };
    await context.Response.WriteRazorPageAsync("Views/Index.cshtml", model);
}
  1. Access model data in .cshtml:
<h1>@Model.Title</h1>
<p>@Model.Message</p>

GetInitialViewData β€” shared data for all views

Override GetInitialViewData in your module to provide common data that will be automatically available in every Razor view β€” without passing it manually each time. Useful for base paths, locale, user info, app settings, etc.

protected override Dictionary<string, object> GetInitialViewData(HttpContext context) => new()
{
    int i = 0;
    ["Title"] = $"Some title #" + (++i).ToString(),
    ["Lang"] = context.Request.Headers["Accept-Language"].FirstOrDefault() ?? "en",
    ["Year"] = DateTime.Now.Year
};

These values are merged into the model and accessible in .cshtml as @Model.BasePath, @Model.Lang, etc.:

<html lang="@Model.Lang">
<head>
    <title>@Model.Title</title>
</head>
<body>
    <footer>Β© @Model.Year</footer>
</body>
</html>

This is called automatically by WriteRazorPageAsync when no explicit viewData parameter is passed. If you pass viewData manually, GetInitialViewData is skipped.


πŸ“‘ Server-Sent Events (SSE)

ModuWeb has built-in SSE support on both sides: a server-side extension for streaming data and a client-side Razor helper for receiving it β€” no jQuery or manual JavaScript needed.

Server side β€” WriteSseAsync

In your module handler, use WriteSseAsync to push data to the client on a fixed interval:

using ModuWeb.Extensions;

// Simple (synchronous generator)
private async Task StreamHandler(HttpContext context)
{
    await context.Response.WriteSseAsync(() => new
    {
        time = DateTime.Now.ToString("HH:mm:ss"),
        date = DateTime.Now.ToString("yyyy-MM-dd")
    }, intervalMs: 5000);
}

// Async generator (for DB queries, HTTP calls, etc.)
private async Task StreamHandler(HttpContext context)
{
    await context.Response.WriteSseAsync(async ct =>
    {
        var data = await GetSensorDataAsync(ct);
        return new { temperature = data.Temp, humidity = data.Hum };
    }, intervalMs: 2000);
}

The extension handles Content-Type, Cache-Control, flushing, JSON serialization, and client disconnect automatically.

You can also send named events:

await context.Response.WriteSseAsync(() => payload, intervalMs: 1000, eventName: "sensor-update");

Client side β€” Sse.Stream() Razor helper

Instead of writing JavaScript manually, use the fluent Sse helper directly in .cshtml:

@using ModuWeb.Extensions

<p id="serverTime">Loading...</p>
<p id="lastUpdate"></p>

@(Sse.Stream("time-stream")
    .Bind("#serverTime", "time")
    .Bind("#lastUpdate", "date", "Updated: {0}")
    .Render())

This generates all the EventSource JavaScript automatically. No jQuery, no <script> blocks.

Available methods

Method Description
.Bind("#id", "field") Sets element's textContent to the JSON field value
.Bind("#id", "field", "Format: {0}") Same, with a format string
.OnMessage("js code") Raw JS executed on each message (has access to data)
.OnOpen("js code") Raw JS executed when connection opens
.OnError("js code") Raw JS executed on connection error
.On("eventName", e => e.Bind(...)) Bindings for a named SSE event

Full example

@using ModuWeb.Extensions

@(Sse.Stream("time-stream")
    .Bind("#serverTime", "time")
    .Bind("#lastUpdate", "datetime", "Updated: {0}")
    .OnOpen("document.getElementById('status').textContent='Connected'")
    .OnError("document.getElementById('status').textContent='Reconnecting...'")
    .Render())

Note: Always wrap in @(...) for multi-line expressions. Render() returns raw HTML that won't be escaped by Razor.

Using jQuery instead of SSE

SSE is optional. ModuWeb ships with jQuery (/jquery-4.0.0.js) available for all modules. You can use classic polling with $.get / $.ajax if you prefer β€” or combine both approaches in the same project:

<script src="/jquery-4.0.0.js"></script>
<script>
    function updateTime() {
        $.get('time').done(function (data) {
            $('#serverTime').text(data.time);
            $('#lastUpdate').text('Updated: ' + data.datetime);
        });
    }
    setInterval(updateTime, 1000);
    updateTime();
</script>

πŸ“Œ Notes

  • Dependencies should be placed in modules/dependencies/. They will be copied automatically.
  • Modules are loaded into memory. Dependencies only as
  • A failed module load is logged but does not crash the host.
  • The middleware checks the base API path (from configuration) and maps requests accordingly.
  • Empty string into path in Map will mean base url with some method.

πŸ“‚ Example

After placing a sample DLL in modules/, you can access its route via:

http://localhost:5000/{ModuleName}/{Route}

For example, with a module named HelloWorld:

GET http://localhost:5000/HelloWorld/hello

πŸ§ͺ Example Folder

The example/ folder includes working example modules you can compile and test.


πŸ“ƒ License

This project is open-source and free to use, modify, and distribute.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages