A low-code web application framework for .NET that eliminates boilerplate code. Inspired by Vidyano, Spark uses a PersistentObject pattern to replace traditional DTOs, repositories, and controllers with a single generic middleware.
- Zero DTOs - Uses
PersistentObjectas a universal data container - Zero Boilerplate - Generic middleware handles all CRUD operations
- Configuration Over Code - Entity definitions stored as JSON files, auto-generated from C# classes
- Dynamic UI - Angular frontend automatically renders forms and lists based on entity metadata
- RavenDB Integration - Document database with index support for optimized queries
| Component | Technology |
|---|---|
| Backend | .NET 10.0 |
| Frontend | Angular 21 |
| Database | RavenDB 6.2+ |
| UI Library | @mintplayer/ng-bootstrap |
The fastest way to get started is with MintPlayer.Spark.AllFeatures. Reference this single package and three source-generated methods handle all the wiring:
builder.Services.AddSparkFull(builder.Configuration);
app.UseRouting();
app.UseSparkFull(args);
app.MapSparkFull();The source generator discovers your SparkContext, SparkUser, Actions, Recipients and Custom Actions at compile time — no generic type parameters needed.
Configure individual features via SparkFullOptions:
builder.Services.AddSparkFull(builder.Configuration, options =>
{
options.Replication = opt =>
{
opt.ModuleName = "Fleet";
opt.ModuleUrl = "https://localhost:5003";
};
});See the AllFeatures documentation for details.
If you only need a subset of features, use the individual packages directly:
builder.Services.AddSpark(builder.Configuration, spark =>
{
spark.UseContext<MySparkContext>();
spark.AddActions();
spark.AddMessaging();
spark.AddRecipients();
});
app.UseRouting();
app.UseSpark(o => o.SynchronizeModelsIfRequested<MySparkContext>(args));
app.MapSpark();public class MySparkContext : SparkContext
{
public IRavenQueryable<Person> People => Session.Query<Person>();
}# Generate model files
dotnet run --spark-synchronize-modelMintPlayer.Spark/
├── libs/
│ ├── spark/
│ │ ├── MintPlayer.Spark/ # Core framework library (CRUD)
│ │ └── MintPlayer.Spark.Abstractions/ # Shared interfaces and models
│ ├── authorization/
│ │ └── MintPlayer.Spark.Authorization/ # Optional auth + group-based access control
│ ├── messaging/
│ │ ├── MintPlayer.Spark.Messaging/ # Durable message bus with RavenDB persistence
│ │ └── MintPlayer.Spark.Messaging.Abstractions/ # Messaging interfaces (IMessageBus, IRecipient<T>)
│ ├── replication/
│ │ ├── MintPlayer.Spark.Replication/ # Cross-module ETL replication
│ │ └── MintPlayer.Spark.Replication.Abstractions/ # Replication interfaces and models
│ ├── subscription_worker/
│ │ ├── MintPlayer.Spark.SubscriptionWorker/ # RavenDB subscription-based background workers
│ │ └── MintPlayer.Spark.SubscriptionWorker.Abstractions/
│ ├── cron/
│ │ └── MintPlayer.Spark.Cron/ # Cron-scheduled background jobs (multi-node safe)
│ ├── webhooks/
│ │ ├── MintPlayer.Spark.Webhooks.GitHub/ # GitHub webhook integration
│ │ └── MintPlayer.Spark.Webhooks.GitHub.DevTunnel/ # Dev-only: smee.io tunnel + WebSocket client
│ ├── client/
│ │ ├── MintPlayer.Spark.Client/ # Typed HTTP client SDK
│ │ └── MintPlayer.Spark.Client.Authorization/
│ ├── all_features/
│ │ ├── MintPlayer.Spark.AllFeatures/ # All-in-one package (references all + source generator)
│ │ └── MintPlayer.Spark.AllFeatures.SourceGenerators/ # Generates AddSparkFull/UseSparkFull/MapSparkFull
│ ├── source_generators/
│ │ └── MintPlayer.Spark.SourceGenerators/ # Compile-time DI code generation
│ ├── testing/
│ │ └── MintPlayer.Spark.Testing/ # Test harness: embedded RavenDB driver, in-memory host factory
│ ├── socket_extensions/
│ │ └── MintPlayer.Dotnet.SocketExtensions/ # WebSocket read/write helpers
│ └── node_packages/ # Angular libraries (@mintplayer/ng-spark, ng-spark-auth)
├── tests/ # Test projects (unit, source-generator, client, E2E)
├── Demo/
│ ├── DemoApp/ # Sample ASP.NET Core + Angular application
│ ├── Fleet/ # Fleet management demo (auth, messaging, replication)
│ ├── HR/ # HR demo (auth, messaging, replication)
│ └── WebhooksDemo/ # GitHub webhooks demo application
└── docs/ # Documentation (guides, prd/, codecov/)
| Guide | Description |
|---|---|
| Getting Started | PersistentObject pattern, SparkContext, entity definitions, model synchronization |
| Reference Attributes | Entity-to-entity links, lookup references, reference selection modals |
| AsDetail Attributes | Embedded objects, array/collection AsDetail, inline and modal editing |
| Queries & Sorting | Index-based queries, projections, column sorting, query definitions |
| Attribute Grouping | Two-level Tabs and Groups layout for entity forms and detail pages |
| Custom Attribute Renderers | Replace default attribute display/editing with custom Angular components |
| Custom Actions | Custom business operations on persistent objects with UI integration |
| PO/Query Aliases | Friendly URLs for entities and queries (/po/car instead of /po/{guid}) |
| TranslatedString & i18n | Multi-language support for labels, descriptions, and validation messages |
| Authorization | Optional security package, security.json, groups, permissions, XSRF |
| Manager & Retry Actions | IManager interface, confirmation dialogs, chained retry actions |
| Durable Message Bus | RavenDB-backed messaging with per-handler retry isolation, checkpoint support, and queue isolation |
| Cross-Module Synchronization | Entity replication between modules with write-back support |
| Subscription Workers | RavenDB subscription-based background processing with retry handling |
| Cron Jobs | Cron-scheduled background jobs, UTC schedules, schedule overrides, multi-node compare-exchange locking |
| GitHub Webhooks | React to GitHub events via typed messages, with smee.io and WebSocket dev tunneling |
| GitHub Webhooks — Dev Tunnel | Dev-only: receive real webhook deliveries on localhost via smee.io or WebSocket forwarding from production |
| Docker Deployment | Deploy with Docker Compose, RavenDB configuration, Traefik reverse proxy |
| Testing Harness | Embedded RavenDB driver, in-memory Spark host factory, antiforgery-aware HTTP client, JSON fixtures, Verify defaults |
- HTTP API Specification - Every HTTP endpoint (routes, payloads, auth, retry protocol) exposed by the framework
- Spark Library API - Detailed API reference and usage guide
- Messaging API - Message bus API reference
- Cron Jobs - Cron-scheduled background jobs:
ISparkCronJob, schedule overrides, multi-node compare-exchange locking - Product Requirements Document - Full specification and architecture
- .NET 10.0 SDK
- Node.js 20+
- RavenDB 6.2+ (local instance or Docker)
- IDE: Visual Studio 2025 / VS Code / JetBrains Rider
The repo is an Nx 22 workspace spanning all .NET and Angular projects. Task graph and caching work across both stacks. CI shares build outputs across runs through a self-hosted Nx remote cache — see Nx remote cache for configuration, token gating, and the cache-poisoning consideration that's relevant if write access to this repo ever expands beyond a single maintainer.
# Clone the repository
git clone https://github.com/MintPlayer/MintPlayer.Spark.git
cd MintPlayer.Spark
# Install JS dependencies (once; npm workspaces for all ClientApps and libraries)
npm install
# Build everything the Nx graph knows about (.NET + Angular, cached)
npx nx run-many -t build
# Or just what's changed since the last green main
npx nx affected -t buildIndividual projects:
# Build a specific .csproj
npx nx build Fleet
# Build an Angular library (ng-packagr)
npx nx build @mintplayer/ng-spark
# Visualize the graph
npx nx graphF5 from Visual Studio or plain dotnet run still works — each demo's Program.cs uses MintPlayer.AspNetCore.SpaServices.UseAngularCliServer, which spawns npm run start in ClientApp/. That script now delegates to nx run <app>:serve, so Nx orchestrates the dev-server behind the scenes:
# Start RavenDB (using Docker)
docker run -d -p 8080:8080 -e RAVEN_Security_UnsecuredAccessAllowed=PublicNetwork ravendb/ravendb
# Run the demo application
cd Demo/DemoApp/DemoApp
dotnet runThe application will be available at https://localhost:5001.
RavenDB note: the demos connect to
http://localhost:8080(appsettings.json→Spark:RavenDb:Urls). If you point them at a standalone/local RavenDB instead of the Docker container above, make sure itsPublicServerUrlishttp://localhost:8080— nothttp://host.docker.internal:8080. RavenDB advertisesPublicServerUrlthrough its cluster topology and the client routes all subsequent requests there (caching it underDemo/**/bin/**/*.raven-cluster-topology); ahost.docker.internalvalue the host can't reach makes every request fail withServiceUnavailable.host.docker.internalis only correct when a container must reach a host-installed database.
The cross-module demos (HR + Fleet, which replicate data to each other) are launched together with the MintPlayer.SlnLaunch dotnet tool, driven by the MintPlayer.Spark.slnLaunch profile in the repo root:
dnx MintPlayer.SlnLaunch # runs the "HR + Fleet" profileUse 10.0.1+, which builds the projects sequentially before launching them in parallel (earlier versions could fail intermittently because the concurrent dotnet run builds raced on the MSBuild server pipe / shared output DLLs). The ASP.NET hosts come up on:
| Module | Host (Spark API + app) |
|---|---|
| Fleet | https://localhost:5003 |
| HR | https://localhost:5005 |
The
/spark/*endpoints live on the host port above. Each host also spawns its own Angular dev server on a separate random port (printed as➜ Local: http://localhost:<port>/); hitting that dev-server port directly servesindex.htmlfor every path, so a request like/spark/program-unitslooks like a 404. Always use the host port for API/middleware requests.
Library HMR: edit any file under libs/node_packages/ng-spark/src/** or libs/node_packages/ng-spark-auth/src/** while a demo is running — changes reflect in the browser without a restart, with component state preserved. Libraries are consumed as source during dev (tsconfig path aliases resolve directly to .ts files). The ng-packagr build target on each library produces the publishable dist for npm publish; dev never consumes dist.
When you modify entity classes, regenerate the JSON model files:
cd Demo/DemoApp
dotnet run --spark-synchronize-modelThis updates files in App_Data/Model/ based on your SparkContext properties.
- Fork the repository
- Create a feature branch from
mastergit checkout -b feature/your-feature-name
- Make your changes following the coding standards below
- Test your changes with the demo application
- Commit with clear, descriptive messages
- Push to your fork
- Open a Pull Request against
master
- Follow C# coding conventions
- Use nullable reference types (
<Nullable>enable</Nullable>) - Use
[Register]and[Inject]attributes from MintPlayer.SourceGenerators for DI - Add XML documentation comments to public APIs
- Keep methods focused and testable
- MintPlayer.Spark - Core library, no application-specific code
- MintPlayer.Spark.Abstractions - Interfaces and models shared across projects
- Demo/DemoApp - Sample application for testing features
- Demo/DemoApp.Library - Example of shared entity definitions
This project is licensed under the MIT License - see the LICENSE file for details.