A .NET 10 application framework — a framework core plus a catalog of pluggable modules
(IThemiaModule). Its cross-cutting neutral packages additionally target .NET 8, so
non-Themia apps (e.g. a net8 Serenity app) can consume them. All packages ship under the
Themia.* NuGet prefix.
Status: active development, released through
0.5.3. Shipped so far: the build-time tooling (Themia.SourceGenerator,Themia.Analyzers,Themia.Generators.Abstractions); the framework core (Themia.Framework.Core,Themia.Caching,Themia.Logging,Themia.MultiTenancy,Themia.Mediator,Themia.Services,Themia.Framework.AspNetCore); a selectable data layer with EF Core and Dapper as first-class peers (Themia.Framework.Data.*) over a FluentMigrator-owned schema (Themia.Data.Migrations); the neutral cross-cutting packages (Themia.AspNetCore,Themia.DependencyInjection,Themia.Quartz,Themia.Exceptional.*); and the first modules (Themia.Modules.Schedulingwith persistent Quartz,Themia.Modules.Identitytenant-aware Identity core,Themia.Modules.Identity.AspNetCoreJWT + rotating refresh tokens + pluggable external/OAuth login), andThemia.Modules.Storage(tenant-aware object storage over Local + S3/R2 backends). The architecture overview, module specs, and implementation plans live underdocs/; the full release history is in CHANGELOG.md.
Layered, with a strict downward dependency direction:
| Layer | Target frameworks | Packages |
|---|---|---|
| Tooling (build-time) | netstandard2.0 |
Themia.SourceGenerator, Themia.Analyzers, Themia.Generators.Abstractions |
| Framework core | net10.0 |
Themia.Framework.Core, Themia.Framework.AspNetCore, Themia.Framework.Data.Abstractions / .EFCore(.SqlServer/.PostgreSql) / .Dapper(.SqlServer/.MySql/.PostgreSql), Themia.MultiTenancy, Themia.Mediator, Themia.Caching, Themia.Logging, Themia.Services |
| Neutral cores | net8.0;net10.0 |
Themia.DependencyInjection, Themia.Data.Migrations, Themia.Data.Probes, Themia.Quartz, Themia.Totp, Themia.WebAuthn, Themia.Exceptional(.SqlServer/.MySql/.PostgreSql), Themia.AspNetCore |
| Modules | net10.0 |
Themia.Modules.Scheduling, Themia.Modules.Identity(.Abstractions), Themia.Modules.Identity.AspNetCore, Themia.Modules.Storage, .Notifications(+3 engines), .Pdf, .Export, .Audit, .Messaging |
Two rules drive the design:
- Framework / app boundary — only framework + cross-cutting infrastructure lives in Themia. Business domains stay in their own apps.
- Three-layer pattern for each cross-cutting concern — neutral core (
Themia.X, no framework dependency) → Themia module (Themia.Modules.X) → optional, deferred Serenity adapter.
See docs/themia-architecture-overview.md for the full
picture, decisions, and build order.
EF Core (Themia.Framework.Data.EFCore) and Dapper (Themia.Framework.Data.Dapper) are
selectable first-class peers — an adopter picks one and the whole system runs on it; both
enforce tenant isolation, audit, soft-delete, and unit-of-work over the same schema, which is
owned by FluentMigrator (Themia.Data.Migrations) as the single authority for both layers.
Phase 1 targets SQL Server, MySQL 8.0.13+, and PostgreSQL via a dialect strategy and per-provider packages. MariaDB is not supported — see the multi-database note in the architecture overview.
Themia ships many small packages on purpose — native DB drivers (SqlClient/Npgsql/MySqlConnector), heavy optional dependencies (AWS SDK, ClosedXML), and the EF-vs-Dapper data-peer choice are kept in separate packages so your app only pulls what it actually uses. You never assemble them by hand:
Two references:
Themia.Framework— the framework core set (Core, Logging, Caching, Services, MultiTenancy, Mediator, Data.Abstractions, ASP.NET Core integration) in one metapackage.- Exactly one data peer+provider package — your conscious choice, never made for you:
Themia.Framework.Data.EFCore.SqlServerorThemia.Framework.Data.EFCore.PostgreSqlThemia.Framework.Data.Dapper.SqlServer,Themia.Framework.Data.Dapper.PostgreSql, orThemia.Framework.Data.Dapper.MySql- (No
EFCore.MySqlyet — there is no EF Core 10 MySQL provider.)
Then bootstrap: services.AddThemiaAspNetCore() + services.AddThemiaMultiTenancy(...) +
app.UseThemia() (see the smoke test in tests/Themia.Framework.Tests/MetaPackageBootstrapTests.cs
for a minimal working host).
Non-web apps (workers, consoles): skip the metapackage — it includes the ASP.NET Core integration —
and reference the individual Themia.* packages you need instead.
| Scenario | Add |
|---|---|
| Identity (users/roles/JWT/external login) | Themia.Modules.Identity.AspNetCore (umbrella — pulls the Identity core, tokens, and external-auth packages) |
| Scheduling (persistent Quartz) | Themia.Modules.Scheduling |
| Storage (incl. S3) | Themia.Modules.Storage |
| Async/scheduled export (CSV + xlsx) | Themia.Modules.Export — heaviest umbrella: also pulls the Storage, Scheduling, and Notifications modules |
| PDF templates + rendering | Themia.Modules.Pdf |
| Exception logging + dashboard | Themia.Exceptional + one Themia.Exceptional.{SqlServer|PostgreSql|MySql} + Themia.Exceptional.AspNetCore |
| Notifications | Themia.Modules.Notifications + one Themia.Modules.Notifications.{SqlServer|PostgreSql|MySql} |
| Content pages (legal/static, versioned, bilingual) | Themia.Content + one Themia.Content.{SqlServer|PostgreSql|MySql}; optional Themia.Content.AspNetCore |
Peer-coupling caveat: some modules (
Identity,Storage,Notifications,Export) currently reference both the EF Core and Dapper data peers (tracked follow-up,docs/2026-06-14-identity-followups.md), so adding them pulls both stacks regardless of the peer you chose. The metapackage itself never does.
Requires the .NET 10 SDK (with the .NET 8 runtime available for the multi-targeted packages).
dotnet build Themia.sln # builds all TFMs (net8.0 + net10.0)
dotnet test Themia.slnThemia.Modules.Identity.AspNetCore adds a pluggable external/OAuth login system that issues the
same AuthResponse as password login. Register the providers you need, then map the endpoints:
builder.Services
.AddThemiaExternalAuth()
.AddGoogle(o => { o.ClientId = "…"; o.ClientSecret = "…"; }) // standard OIDC
.AddLine(o => { o.ChannelId = "…"; o.ChannelSecret = "…"; }); // OIDC-ish, HS256 channel secret
// opt-in endpoints, grouped under your auth prefix
app.MapGroup("/auth").MapIdentityExternalAuthEndpoints();The flow is headless: the SPA/native client runs the provider authorization step itself, owns
the state value (CSRF), and posts the returned authorization code to the server for exchange.
POST /auth/external/{provider}
{ "code": "…", "redirectUri": "https://app.example/callback", "codeVerifier": "…" } // codeVerifier optional (PKCE)
→ 200 AuthResponse { accessToken, expiresIn, refreshToken } // refresh via POST /auth/refresh
→ 401 on any provider failure (uniform) → 404 only for an unknown provider
The server performs the code→token exchange server-side (the client secret is never exposed or
logged), validates the id-token issuer / audience / signature / expiry, forwards the PKCE
code_verifier, and binds the nonce to the id-token (if the token carries one, a matching client
nonce is required, and vice-versa). Account resolution: an existing external link → auto-link to a
user only when the provider asserts a verified email and the account is active (an unverified
email never links and is never persisted; a deactivated/locked account is never auto-linked) →
otherwise provision a password-less user.
Google ships as standard OIDC and LINE as an HS256 channel-secret provider; Facebook / Microsoft /
Telegram are deferred additive providers. LINE emits no email_verified claim, so by default a LINE
login never auto-links to an existing account (opt in via LineOptions.EmailAlwaysVerified if you
trust LINE's email verification).
Themia.Modules.Storage adds tenant-aware object storage over a Local filesystem or S3/R2 backend.
Every object's physical key is prefixed with the ambient tenant, so isolation holds by
construction; object metadata and a per-tenant quota are tracked in the storage.storage_objects
table (FluentMigrator schema, PostgreSQL + SQL Server) on either the EF Core or Dapper data peer.
builder.Services
.AddThemiaStorage()
.UseLocal(o => { o.RootPath = "/var/themia/blobs"; o.SigningKey = "…"; }) // or .UseS3(…) / .UseR2(…)
.AddValidation() // size + content-type checks via IFileValidator
.AddScanning(); // DI-replaceable IFileScanner seam (NullFileScanner by default)
// opt-in presigned-direct upload/download endpoints
app.MapThemiaStorageEndpoints();Uploads are size- and content-type-validated before a write; the opt-in MapThemiaStorageEndpoints
flow issues presigned URLs so clients transfer bytes straight to/from the backend, keeping object
content off the application server. Secrets, credentials, and presigned URLs are never logged.
- Architecture overview & module catalog
- Dapper data-layer design
- Identity core design
- Identity JWT design (0.5.1)
- Identity external/OAuth login design (0.5.2)
- Storage design (0.5.3) · implementation plan
- Scheduling (Quartz) design
- Exception logging design
- Release strategy & CHANGELOG conventions
Themia.AspNetCoreimplementation plan