Anudwigna.StaticMarkdownEngine 1.0.0-beta.2

This is a prerelease version of Anudwigna.StaticMarkdownEngine.
dotnet add package Anudwigna.StaticMarkdownEngine --version 1.0.0-beta.2
                    
NuGet\Install-Package Anudwigna.StaticMarkdownEngine -Version 1.0.0-beta.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Anudwigna.StaticMarkdownEngine" Version="1.0.0-beta.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Anudwigna.StaticMarkdownEngine" Version="1.0.0-beta.2" />
                    
Directory.Packages.props
<PackageReference Include="Anudwigna.StaticMarkdownEngine" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Anudwigna.StaticMarkdownEngine --version 1.0.0-beta.2
                    
#r "nuget: Anudwigna.StaticMarkdownEngine, 1.0.0-beta.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Anudwigna.StaticMarkdownEngine@1.0.0-beta.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Anudwigna.StaticMarkdownEngine&version=1.0.0-beta.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Anudwigna.StaticMarkdownEngine&version=1.0.0-beta.2&prerelease
                    
Install as a Cake Tool

Anudwigna.StaticMarkdownEngine

NuGet License: MIT Target Frameworks

Beta release — Core API is stable. Feedback welcome before 1.0.0 GA.

A zero-configuration .NET library that turns a folder of Markdown files with YAML front matter into a fully-routed ASP.NET Core website — with a built-in content query API inspired by Nuxt Content.


Table of Contents


Features

Feature Details
Markdown rendering Powered by Markdig with all advanced extensions: tables, footnotes, code blocks, task lists, and more
YAML front matter Supports title, description, layout, date, and any custom key/value pairs via Extra
Internal link rewriting [About](about.md) links are automatically rewritten to href="/about"
Two-tier content model input/ for static pages, content/ for queryable articles — just like Nuxt Content
Content query API contentRepo.Query() returns IEnumerable<ContentItem> — filter, sort, and paginate with standard LINQ
Recursive folder sync Mirrors your entire input/ tree to output/, preserving directory structure
ASP.NET Core routing app.MapMarkdownPages() one-liner wires up dynamic routes via DynamicRouteValueTransformer
Razor Tag Helper <markdown-content> renders content from a file path or inline HTML string
CLI tool Built-in generate command via System.CommandLine for standalone builds
Multi-target Targets net8.0, net9.0, and net10.0

How It Works

The library distinguishes two types of Markdown content:

myapp/
├── input/            ← static pages (about, docs, etc.)
│   ├── index.md      →  output/index.html      →  GET /
│   ├── about.md      →  output/about.html      →  GET /about
│   └── docs/
│       └── guide.md  →  output/docs/guide.html →  GET /docs/guide
│
└── content/          ← articles (queryable via ContentRepository)
    ├── my-post.md    →  output/my-post.html    →  GET /my-post
    └── 2025/
        └── news.md   →  output/2025/news.html  →  GET /2025/news

At startup, SiteGenerator.Generate() converts pages, and ContentRepository.Build() converts articles. Both write pre-generated HTML to the same output directory. The existing route transformer serves all of it with a single catch-all route.

.md files  ──►  FrontMatterParser  ──►  MarkdownConverter  ──►  .html files
                                                                       │
                                              MarkdownPageRouteTransformer
                                                                       │
                                                          ASP.NET Core routes

Requirements

  • .NET 8, 9, or 10
  • ASP.NET Core (Microsoft.AspNetCore.App framework reference)

Installation

dotnet add package Anudwigna.StaticMarkdownEngine --prerelease

Or in your .csproj:

<PackageReference Include="Anudwigna.StaticMarkdownEngine" Version="1.0.0-beta.2" />

Quick Start

1. Register services in Program.cs

using Anudwigna.StaticMarkdownEngine.AspNetCore;
using Anudwigna.StaticMarkdownEngine.Content;
using Anudwigna.StaticMarkdownEngine.Core;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

// Pages engine
builder.Services.AddMarkdownEngine(options =>
{
    options.OutputDirectory = Path.Combine(builder.Environment.ContentRootPath, "output");
});

// Content query API (optional — only needed if you use the content/ folder)
builder.Services.AddContentRepository(options =>
{
    options.ContentDirectory = Path.Combine(builder.Environment.ContentRootPath, "content");
    options.OutputDirectory  = Path.Combine(builder.Environment.ContentRootPath, "output");
});

var app = builder.Build();

2. Generate HTML at startup

// Pages: input/ → output/
var generator = app.Services.GetRequiredService<SiteGenerator>();
generator.Generate(
    inputDir:  Path.Combine(app.Environment.ContentRootPath, "input"),
    outputDir: Path.Combine(app.Environment.ContentRootPath, "output"));

// Articles: content/ → output/ + in-memory index
var contentRepo = app.Services.GetRequiredService<ContentRepository>();
contentRepo.Build();

app.UseRouting();
app.MapMarkdownPages();
app.Run();

3. Register the Tag Helper

In Views/_ViewImports.cshtml:

@addTagHelper *, Anudwigna.StaticMarkdownEngine

4. Create a Razor view for Markdown pages

In Views/MarkdownPage/Render.cshtml:

@{
    Layout = "_Layout";
    ViewData["Title"] = ViewBag.Title;
}

<article class="markdown-body">
    <markdown-content html-content="@ViewBag.HtmlContent" />
</article>

5. Write Markdown content

input/about.md — a static page:

---
title: "About"
description: "Learn more about this site"
---

# About

Welcome! Check out our [home page](index.md).

content/my-first-post.md — a queryable article:

---
title: "My First Post"
description: "Hello from the content folder"
date: 2025-06-01
tags: intro
---

# My First Post

This article is queryable via `ContentRepository.Query()`.

Content API

The ContentRepository service provides a LINQ-based query API over all articles in your content/ folder. Routes for articles are derived directly from the file path with no prefix — content/my-post.md/my-post.

Querying articles

// Inject ContentRepository into a controller or Razor Page
public class IndexModel : PageModel
{
    private readonly ContentRepository _content;

    public IReadOnlyList<ContentItem> TopPosts { get; private set; } = [];

    public IndexModel(ContentRepository content) => _content = content;

    public void OnGet()
    {
        // Top 5 articles by date, newest first
        TopPosts = _content.Query()
            .OrderByDescending(p => p.FrontMatter.Date ?? DateTime.MinValue)
            .Take(5)
            .ToList();
    }
}

Because Query() returns a plain IEnumerable<ContentItem>, any LINQ operator works:

// Filter by a custom front matter field
var tagged = _content.Query()
    .Where(p => p.FrontMatter.Extra.TryGetValue("tags", out var t)
             && t?.ToString()?.Contains("dotnet") == true)
    .ToList();

// Look up a single article by its URL
var post = _content.GetByRoute("/my-first-post");
@foreach (var post in Model.TopPosts)
{
    <article>
        <h2><a href="@post.Route">@post.FrontMatter.Title</a></h2>
        <time>@post.FrontMatter.Date?.ToString("MMM d, yyyy")</time>
        <p>@post.Excerpt</p>
        <a href="@post.Route">Read more →</a>
    </article>
}

ContentItem properties

Property Type Description
FrontMatter FrontMatter Parsed YAML metadata (Title, Description, Date, Extra, …)
Route string URL route — e.g. /my-post, /2025/news
Slug string Route without the leading /
HtmlContent string Fully rendered HTML body
Excerpt string Plain-text preview (first 200 characters, configurable)

Configuration Reference

AddMarkdownEngine

builder.Services.AddMarkdownEngine(options =>
{
    options.OutputDirectory = Path.Combine(builder.Environment.ContentRootPath, "output");
});
Property Type Default Description
OutputDirectory string "output" Directory containing pre-generated .html files, read by the route transformer

AddContentRepository

builder.Services.AddContentRepository(options =>
{
    options.ContentDirectory = Path.Combine(builder.Environment.ContentRootPath, "content");
    options.OutputDirectory  = Path.Combine(builder.Environment.ContentRootPath, "output");
    options.ExcerptLength    = 200; // optional, default is 200
});
Property Type Default Description
ContentDirectory string "content" Absolute path to the folder containing article .md files
OutputDirectory string "output" Where generated .html files are written (should match AddMarkdownEngine)
ExcerptLength int 200 Maximum plain-text characters included in ContentItem.Excerpt

YAML Front Matter Reference

Front matter is a YAML block delimited by --- at the top of a Markdown file.

---
title: "Page Title"
description: "A short description shown in meta tags or previews"
layout: "_Layout"
date: 2025-06-15
tags: dotnet, aspnetcore
author: "Ada Lovelace"
---

# Page content starts here
Field Type Default Description
title string "" Page title — available in views as ViewBag.Title
description string "" Short description for SEO or previews
layout string "_Layout" Razor layout name to use when rendering
date DateTime? null Publication date — used for sorting in the content API
Extra fields Dictionary<string, object> {} Any additional key/value pairs land in FrontMatter.Extra

Accessing custom fields:

var author = item.FrontMatter.Extra["author"]?.ToString();

Tag Helper Reference

The <markdown-content> tag helper renders HTML content inside a Razor view. It produces no wrapper element — it outputs only the HTML.

<markdown-content html-content="@ViewBag.HtmlContent" />
Attribute Type Description
html-content string Pre-rendered HTML string to output directly

From a file path

<markdown-content html-file="/absolute/path/to/output/about.html" />
Attribute Type Description
html-file string Absolute path to a .html file. If the file does not exist, nothing is rendered.

CLI Reference

CliRunner provides a generate command for use in apps that expose a CLI entry point.

Wiring up in Program.cs

using Anudwigna.StaticMarkdownEngine.Cli;

if (args.Length > 0)
    return await CliRunner.RunAsync(args);

// Otherwise start the web app as normal

generate command

dotnet run -- generate [--input <path>] [--output <path>]
Option Short Default Description
--input -i input Path to the directory containing .md source files
--output -o output Path to the directory where .html files will be written

Example:

dotnet run -- generate -i content -o wwwroot/pages

Output:

Generating site from 'C:\myapp\content' → 'C:\myapp\wwwroot\pages'...
  ✓  /          →  wwwroot/pages/index.html
  ✓  /about     →  wwwroot/pages/about.html

Done! Generated 2 page(s).

API Reference

SiteGenerator

Recursively converts all .md files in a source directory to .html files.

var generator = new SiteGenerator(); // or inject via DI

IReadOnlyList<MarkdownPage> pages = generator.Generate(
    inputDir:  "/path/to/input",
    outputDir: "/path/to/output");

ContentRepository

Scans the content/ directory, generates HTML, and provides a LINQ-queryable in-memory index.

// Call once at startup
contentRepo.Build();

// LINQ query
var recent = contentRepo.Query()
    .OrderByDescending(p => p.FrontMatter.Date)
    .Take(5)
    .ToList();

// Direct lookup
ContentItem? post = contentRepo.GetByRoute("/my-post");
Method Returns Description
Build() void Scans ContentDirectory, generates HTML, populates the index. Safe to call even if the directory does not exist.
Query() IEnumerable<ContentItem> Returns all indexed articles for LINQ chaining
GetByRoute(string) ContentItem? Looks up an article by its exact route (case-insensitive)

MarkdownConverter

Converts a single Markdown string to a MarkdownPage.

var converter = new MarkdownConverter(); // or inject via DI

MarkdownPage page = converter.Convert(rawMarkdown);
Console.WriteLine(page.FrontMatter.Title);
Console.WriteLine(page.HtmlContent);

MarkdownPage

Property Type Description
FrontMatter FrontMatter Parsed YAML metadata
HtmlContent string Rendered HTML body (front matter stripped)
Route string Derived URL route, e.g. /docs/guide
OutputFilePath string Absolute path to the written .html file

FrontMatterParser

var parser = new FrontMatterParser();
var (frontMatter, body) = parser.Parse(rawMarkdown);

Project Structure

static_markdown_engine/
├── src/
│   └── Anudwigna.StaticMarkdownEngine/      # NuGet package
│       ├── AspNetCore/
│       │   ├── MarkdownEngineExtensions.cs  # AddMarkdownEngine / AddContentRepository / MapMarkdownPages
│       │   ├── MarkdownPageRouteTransformer.cs
│       │   ├── MarkdownPageRouteOptions.cs
│       │   └── MarkdownContentTagHelper.cs  # <markdown-content> tag helper
│       ├── Cli/
│       │   └── CliRunner.cs                 # System.CommandLine entry point
│       ├── Content/
│       │   ├── ContentRepository.cs         # Query API + HTML generation for articles
│       │   └── ContentRepositoryOptions.cs
│       ├── Core/
│       │   ├── FrontMatterParser.cs         # YAML extraction
│       │   ├── LinkRewriter.cs              # .md → route link rewriting
│       │   ├── MarkdownConverter.cs         # Markdig rendering pipeline
│       │   └── SiteGenerator.cs            # Folder sync: input/ → output/
│       └── Models/
│           ├── ContentItem.cs               # Article model for the query API
│           ├── FrontMatter.cs
│           └── MarkdownPage.cs
├── tests/
│   └── Anudwigna.StaticMarkdownEngine.Tests/ # xUnit + FluentAssertions (42 tests)
└── samples/
    └── Anudwigna.SampleWeb/                  # ASP.NET Core demo application
        ├── content/                          # Sample articles (queryable)
        └── input/                            # Sample static pages

Dependencies

Package Version Purpose
Markdig 0.40.0 Markdown-to-HTML rendering with all advanced extensions
YamlDotNet 16.3.0 YAML front matter parsing
System.CommandLine 2.0.0-beta4 CLI generate command
Microsoft.AspNetCore.App (framework ref) ASP.NET Core routing, Tag Helpers, DI

License

This project is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-beta.2 90 2/26/2026
1.0.0-beta.1 68 2/26/2026

1.0.0-beta.2 — Added ContentRepository: LINQ-queryable content API for the content/ folder, ContentItem model with Excerpt, and AddContentRepository() DI extension.