Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

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:

  1. DDD DSL -- aggregates, entities, value objects, commands, events, sagas
  2. Content DSL -- content parts, content blocks, StreamFields
  3. Admin DSL -- admin module generation
  4. Pages DSL -- page tree, layouts, zones, widgets, dynamic routing
  5. Workflow DSL -- editorial pipelines with stages, gates, roles, translations
  6. 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
Diagram
The CMF stack in one picture — an M3 meta-metamodel at the base, six M2 DSLs above it, and the compiled application surface (entities, EF Core, API, admin and page widgets) on top.
⬇ Download