Introduction -- CMF, Not CMS
In 2010, the Diem Project was a Content Management Framework built on Symfony 1.4 and Doctrine. It was not WordPress. It was not Drupal. It started empty -- no blog, no comments, no preinstalled modules. The developer defined a data schema in schema.yml, declared modules in modules.yml, and Diem generated code that was 100% specific to the site's needs. The frontend composed pages from Layouts, Zones, and Widgets. The backend auto-generated admin interfaces from the schema.
Diem was a CMF -- a Content Management Framework. The distinction matters.
A CMS (Content Management System) is an application. You install it, configure it, extend it with plugins. WordPress, Drupal, Strapi -- these are applications that manage content. A developer adapts the application to the domain.
A CMF (Content Management Framework) is a framework in the developer sense. It provides primitives, DSLs, and code generation to build domain-specific applications from scratch. The developer defines the domain; the framework generates the application. Diem, Symfony CMF, and what this article describes all follow this philosophy.
This article applies the metamodeling theory from Modeling, Metamodeling, and Meta-Metamodeling in C# to build a CMF for .NET. The M3 meta-metamodel provides the foundation. On top of it, we define six M2 DSLs -- each expressed as C# attributes, each compiled by Roslyn source generators:
- DDD DSL -- aggregates, entities, value objects, commands, events, sagas
- Content DSL -- content parts, content blocks, StreamFields
- Admin DSL -- admin module generation
- Pages DSL -- page tree, layouts, zones, widgets, dynamic routing
- Workflow DSL -- editorial pipelines with stages, gates, roles, translations
- Requirements DSL -- features, epics, stories, tasks, bugs, acceptance criteria, test coverage
From a handful of attribute-decorated partial classes, the compiler produces the entire stack: domain entities, EF Core persistence, REST and GraphQL APIs, CQRS handlers, Blazor admin components, Blazor WebAssembly page widgets, and a content workflow engine.
The transformation from Diem to this CMF is:
| Diem (PHP / Symfony 1.4) | CMF (C# / .NET 10) |
|---|---|
schema.yml → Doctrine ORM |
[AggregateRoot], [Entity], [ValueObject] → Roslyn → EF Core |
modules.yml → admin + front modules |
[AdminModule] + [PageWidget] → Roslyn → Blazor components |
| Page → Layout → Zone → Widget (server-rendered) | Page → Layout → Zone → Widget (Blazor WASM SPA + API) |
| Doctrine model classes | Shared C# kernel (compiled to both server + WASM) |
dm:setup CLI |
cmf new, cmf add aggregate, cmf generate |
Admin generator (generator.yml) |
Source-generated Blazor admin CRUD |
| List/Show widgets | Source-generated Blazor WASM components consuming API |
What Diem did not have -- and what this CMF adds by learning from the broader CMF landscape:
| Origin | Pattern | CMF Attribute |
|---|---|---|
| Orchard Core (.NET) | Content Parts -- composable cross-cutting concerns | [ContentPart], [HasPart] |
| Wagtail (Django) | StreamField -- composable typed content blocks as JSON | [ContentBlock], [StreamField] |
| Drupal | Content versioning, draft/publish, taxonomy | [Versionable], [Taxonomy] |
| Symfony CMF | Dynamic routing from content tree | Page tree → URL resolution |
| Strapi | Auto REST + GraphQL API from schema | Generated API controllers + GraphQL schema |