Strongly-Typed Markdown for ASP.NET Core Content Apps (2025)

Every development career has milestone moments. One we all likely share is building a custom content management system, or CMS, as developers like to refer to it. A common approach to melding metadata and content is utilizing the old reliable Markdown format, which fuses YAML frontmatter with a simple content format. While YAML is flexible, it can be less than ideal when wanting to use that embedded data in your ASP.NET Core applications.

In this post, I’ll show you a quick experiment around processing Markdown files and their YAML into a strongly-type C# object. The example allows you to easily modify content while still having access to instances of data that you can strongly type.

The Magnificent Markdown

Markdown is a very flexible format whose strength comes from its simplicity. Let’s examine a document that defines a person’s profile.

---name: "Khalid Abuhakmeh"profession: "Software Developer"hobbies: ["video games", "movies", "boxing"]---## SummaryI am writing a little about myself here and this should appearin the page. Cool! Check me out at my [personal blog](https://khalidabuhakmeh.com).

The top of the document defines a data model with properties for Name, Profession, and Hobbies. The C# data model for this YAML would consist of three properties.

public class Asset{ public string Name { get; set; } = ""; public string Profession { get; set; } = ""; public string[] Hobbies { get; set; } = [];}

Let’s build an object that will parse the Markdown file’s front matter while helping us render the content into HTML for use on a Razor page.

The MarkdownObject and Parsing Files

For my experiment, I created a MarkdownObject<T> class that takes a content string and parses the document into its parts. The T argument is up to the developer to determine.

To continue with the code, you must add the Markdig package and the YamlDotNet package.

<ItemGroup> <PackageReference Include="Markdig" Version="0.40.0" /> <PackageReference Include="YamlDotNet" Version="16.3.0" /></ItemGroup>

Let’s look at the implementation next.

using Markdig;using Markdig.Extensions.Yaml;using Markdig.Syntax;using Microsoft.AspNetCore.Html;using YamlDotNet.Serialization;using Md = Markdig.Markdown;namespace SuperContent.Models;public class MarkdownObject<T>{ private static readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder() .UseYamlFrontMatter() .UseAdvancedExtensions() .Build(); private static readonly IDeserializer Deserializer = new DeserializerBuilder() .WithYamlFormatter(new YamlFormatter()) .WithCaseInsensitivePropertyMatching() .Build(); public MarkdownObject(string content) { var doc = Md.Parse(content, MarkdownPipeline); FrontMatter = default; if (doc.Descendants<YamlFrontMatterBlock>().FirstOrDefault() is { } fm) { var yaml = fm.Lines.ToSlice(); FrontMatter = Deserializer.Deserialize<T>(yaml.Text); // we don't want front matter after it's processed doc.Remove(fm); } // turn it into HTML once Html = new HtmlString(doc.ToHtml()); } public T? FrontMatter { get; private set; } public IHtmlContent Html { get; private set; }}

In the case of this demo, we’ll create an instance of MarkdownObject<Asset>. Let’s see how to use this type in a Razor Page.

MarkdownObject in a Razor Page

In my demo, I have all my Markdown files in a Data directory. Each file in the data directory has a unique file name that we’ll use in our Razor Page as a slug. We’ll also use the Model to output the data and the processed HTML into a structured layout.

@page "/profile/{slug}"@model SuperContent.Pages.Profile<div class="row"> <div class="col-12"> <h1>@Model.Asset.FrontMatter?.Name</h1> </div></div><div class="row"> <div class="col-3"> <dl> <dt>Profession</dt> <dd>@Model.Asset.FrontMatter?.Profession</dd> <dt>Hobbies</dt> <dd> <ul> @if (Model.Asset is { FrontMatter.Hobbies : { } hobbies }) { @foreach (var hobby in hobbies) { <li>@hobby</li> } } </ul> </dd> </dl> </div> <div class="col-9"> @Model.Asset.Html </div></div>

So, what does the page’s model look like?

using System.Text.RegularExpressions;using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.RazorPages;using SuperContent.Models;namespace SuperContent.Pages;public partial class Profile : PageModel{ [BindProperty(SupportsGet = true)] public string Slug { get; set; } = ""; public MarkdownObject<Asset> Asset { get; set; } = null!; public IActionResult OnGet() { // read a file from the Data directory based on the slug // sanitize the slug first because people are mean var sanitizedSlug = SlugRegex.Replace(Slug, ""); var path = Path.Combine("Data", $"{sanitizedSlug}.md"); if (System.IO.File.Exists(path)) { var content = System.IO.File.ReadAllText($"Data/{sanitizedSlug}.md"); Asset = new(content); return Page(); } return NotFound(); } [GeneratedRegex("[^a-zA-Z0-9_-]")] private static partial Regex SlugRegex { get; }}public class Asset{ public string Name { get; set; } = ""; public string Profession { get; set; } = ""; public string[] Hobbies { get; set; } = [];}

The OnGet method contains some protective code to prevent access to other files, but it’s ultimately pretty straightforward. When you go to /profile/Khalid, you’ll see a nicely formatted page that mixes data and content into predetermined HTML because we use the new MarkdownObject class. Sweet!

I’ve pushed the code to my GitHub repository so you can try this sample for yourself. Please give it a try and let me know what you think. As always, thanks for reading and sharing my posts. Cheers.

Strongly-Typed Markdown for ASP.NET Core Content Apps (2025)

References

Top Articles
Latest Posts
Recommended Articles
Article information

Author: Neely Ledner

Last Updated:

Views: 6344

Rating: 4.1 / 5 (62 voted)

Reviews: 85% of readers found this page helpful

Author information

Name: Neely Ledner

Birthday: 1998-06-09

Address: 443 Barrows Terrace, New Jodyberg, CO 57462-5329

Phone: +2433516856029

Job: Central Legal Facilitator

Hobby: Backpacking, Jogging, Magic, Driving, Macrame, Embroidery, Foraging

Introduction: My name is Neely Ledner, I am a bright, determined, beautiful, adventurous, adventurous, spotless, calm person who loves writing and wants to share my knowledge and understanding with you.