This documentation is also published as Markdown for efficient machine reading: the whole site is indexed at /llms.txt, and every page has a clean Markdown copy at the same URL with .md appended. These are generated from the same source and cost far fewer tokens to read than this rendered HTML.

Skip to main content Skip to navigation
Getting Started

Author a custom Razor component for markdown

Author a PricingCard Razor component inside a DocSite, register it with AddMdazorComponent<T>(), and render it from a markdown page with two parameter sets.

By the end of this tutorial you'll have a running DocSite at http://localhost:5000/pricing that renders two styled <PricingCard /> cards — a standard "Basic" tier and a highlighted "Pro" tier — both driven by tag attributes inside a plain markdown file.

Along the way, the tutorial covers authoring a Razor component with [Parameter]-decorated properties, wiring it into Mdazor's component registry with one AddMdazorComponent<T>() line, and consuming it from markdown with self-closing tag syntax whose attribute values bind case-insensitively to the component's parameters.

Prerequisites

  • .NET 10 SDK installed
  • Completed Scaffold a documentation site with DocSite (provides the AddDocSite / UseDocSite / RunDocSiteAsync host shape this tutorial extends)
  • Basic Razor familiarity — a .razor file with @code {} and [Parameter] properties should feel routine

The finished code for this tutorial lives in examples/BeyondCustomRazorComponentExample.


1. Author the PricingCard component

Before Mdazor can render a custom tag from markdown, a real Razor component has to exist in your project. This unit adds Components/PricingCard.razor and a top-level _Imports.razor so [Parameter] is in scope without per-file @using lines.

1

Add a project-wide _Imports.razor

Drop an _Imports.razor file at your project root so every .razor file in the project gets the Blazor component namespaces. This is the same file a Blazor template ships with — the @using lines are what make [Parameter] resolve inside the component file in the next step, and the last line brings your Components/ folder into scope so markdown can reference PricingCard by name.

razor
@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Web
@using <your root namespace>.Components

Use your own root namespace. Replace <your root namespace> with your project's default namespace (the .csproj name).

2

Create Components/PricingCard.razor

Create a Components/ folder and add PricingCard.razor with four [Parameter] properties — Tier, Price, Features, and Highlighted — and markup that renders a pricing card, switching to a thicker accent border when Highlighted is set. The Features parameter is a pipe-delimited string because Mdazor binds only primitive parameter types from markdown attributes; lists arrive as strings and are split inside the component.

razor
<div class="not-prose my-6">
    <div class="@CardClasses">
        <h3 class="text-xl font-bold">@Tier</h3>
        <div class="mt-2 flex items-baseline gap-1">
            <span class="text-4xl font-extrabold">$@Price</span>
            <span class="text-sm">/ month</span>
        </div>
        <ul class="mt-4 space-y-2 text-sm">
            @foreach (var feature in ParsedFeatures)
            {
                <li>@feature</li>
            }
        </ul>
    </div>
</div>
  
@code {
    [Parameter] public string Tier { get; set; } = "Basic";
    [Parameter] public string Price { get; set; } = "0";
    [Parameter] public string Features { get; set; } = "";
    [Parameter] public bool Highlighted { get; set; }
  
    private IEnumerable<string> ParsedFeatures =>
        (Features ?? "").Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  
    private string CardClasses => Highlighted
        ? "rounded-xl border-2 border-primary-500 p-6"
        : "rounded-xl border border-base-200 p-6";
}

The file is a regular Blazor component — nothing Pennington-specific yet.

Checkpoint

Run dotnet build from your project root. The build succeeds. The PricingCard type now exists at <your root namespace>.Components.PricingCard, but it is not yet wired to Mdazor, so a <PricingCard /> tag in markdown would still render as a literal custom element.


2. Register the component with Mdazor

DocSite already calls AddMdazor() and registers the built-in Pennington.UI components. The only remaining step is one AddMdazorComponent<PricingCard>() line so Mdazor's registry knows about the new type.

1

Add AddMdazorComponent<PricingCard>() to Program.cs

Open Program.cs and add a single builder.Services.AddMdazorComponent<PricingCard>() line after the AddDocSite block. The extension lives in the Mdazor namespace and ships from the Mdazor NuGet package, already transitively referenced through Pennington.DocSite — no package add required.

csharp
var builder = WebApplication.CreateBuilder(args);
  
builder.Services.AddDocSite(() => new DocSiteOptions
{
    SiteTitle = "Beyond Custom Razor Component",
    SiteDescription = "Authoring a Razor component and rendering it inline from markdown.",
    GitHubUrl = "https://github.com/usepennington/pennington",
    HeaderContent = """<a href="/">Beyond Custom Razor Component</a>""",
    FooterContent = """<footer class="mt-16 py-8 text-center text-sm text-base-500">Built with Pennington DocSite.</footer>""",
});
  
// The one new line vs stage 1: tell Mdazor about the PricingCard type.
// AddMdazorComponent<T>() is an IServiceCollection extension in the
// Mdazor namespace (from the Mdazor NuGet package, transitively
// referenced through Pennington.DocSite). It returns the same
// IServiceCollection so it chains with further registrations.
builder.Services.AddMdazorComponent<PricingCard>();
  
var app = builder.Build();
  
app.UseDocSite();
  
await app.RunDocSiteAsync(args);

Checkpoint

Run dotnet build from your project root again. The build still succeeds, and Mdazor's registry now contains PricingCard. Nothing renders differently yet — the next section adds a markdown page that uses the tag.


3. Consume the component from markdown

Now let's add a markdown page that uses <PricingCard /> twice with different attribute values, exercising both the default and highlighted visual states of the component.

1

Create Content/pricing.md

Add a new markdown page under Content/ with front matter (title: Pricing, description:, order: 20) and two <PricingCard ... /> tags between headings. The first card uses Tier="Basic" Price="9"; the second adds Highlighted="true" and richer feature text.

markdown
---
title: Pricing
description: Two PricingCard components rendered from markdown with distinct parameter values.
order: 20
---
  
Pick a plan that fits your team. Both tiers below are rendered from a single
Razor component, `PricingCard`, authored in this example's `Components/`
folder and registered via `AddMdazorComponent<PricingCard>()` in
`Program.cs`. The markdown below consumes the component by name — Mdazor
intercepts tags that look like registered components, binds their
attributes as parameters, and hands the resulting HTML back to the Markdig
pipeline.
  
## Plans
  
<PricingCard Tier="Basic" Price="9" Features="1 project|5 GB storage|Community support" />
  
<PricingCard Tier="Pro" Price="49" Features="Unlimited projects|100 GB storage|Priority email support|Team seats included" Highlighted="true" />
  
## Why two cards?
  
Rendering the component twice with different attribute values proves that
Mdazor resolves `<PricingCard />` tags on every occurrence, not just the
first. The second card passes `Highlighted="true"`, which flips the
component into its emphasised visual state — a thicker accent border.
  
## How the wiring works
  
1. The component is a regular Razor component with `[Parameter]`-decorated
   properties for `Tier`, `Price`, `Features`, and `Highlighted`.
2. `services.AddMdazorComponent<PricingCard>()` adds the type to Mdazor's
   component registry.
3. When the markdown renderer encounters `<PricingCard ... />`, it looks up
   the registered type, instantiates it, assigns parameters via
   case-insensitive reflection, renders the component through Blazor's
   server-side `HtmlRenderer`, and inlines the resulting HTML into the page.
  
Self-closing (`<PricingCard ... />`) and open/close (`<PricingCard ...></PricingCard>`)
forms are both supported; the open/close form lets the component receive
`ChildContent` populated by any markdown between the tags.

Mdazor matches tag names case-sensitively on the leading character — <PricingCard> must start with a capital letter — and binds attribute values to parameters case-insensitively. See Content components for the full binding rules.

2

Run the site

Start the host with dotnet run from your project root, then open http://localhost:5000/pricing. Mdazor intercepts each <PricingCard ... /> tag, looks up the registered type, instantiates it, binds the attributes to its parameters, and inlines the rendered HTML in place of the tag.

Checkpoint

Visit http://localhost:5000/pricing. Two pricing cards appear: a Basic card at $9 / month with a thin border, and a Pro card at $49 / month with a thicker accent border (its Highlighted="true" attribute). View the page source — <PricingCard> has been replaced by real HTML (a <div> tree with the card classes), not left as a literal custom element.


4. Pass more parameters and verify binding

Now let's confirm the markdown-to-parameter binding is real by editing attribute values in the markdown and watching the rendered output change — this is the whole authoring loop.

1

Edit the Pro card to change Price and Features

In Content/pricing.md, change Price="49" to Price="99" and extend the Features="" string with an extra pipe-separated entry (for example, "...|24/7 chat support"). Save the file.

2

Flip Highlighted on the Basic card

Add Highlighted="true" to the first <PricingCard Tier="Basic" ... /> tag. Boolean attribute values from markdown bind with case-insensitive true / falseHighlighted="True" and Highlighted="true" both flip the card into its emphasized state.

Checkpoint

Reload http://localhost:5000/pricing. The dev host picks up markdown changes as you save, so no rebuild is required.

  • The Pro card now reads $99 / month and lists the extra feature bullet
  • The Basic card now renders with the thicker accent border instead of its plain one
  • Open the browser's dev tools — the generated HTML under each <PricingCard> has changed to match

Summary

  • A Razor component lives under Components/ with [Parameter]-decorated properties and is consumed from markdown by name.
  • Any component type registers with Mdazor in one line: services.AddMdazorComponent<T>() after AddDocSite (or after AddPennington on a custom host).
  • Two binding rules govern markdown-driven consumption: tag names start with a capital letter, and attribute values bind case-insensitively to parameter properties of primitive types (string, bool, numbers).
  • Built-in Pennington.UI components and custom components mix freely in the same markdown page — both go through the same Mdazor registry.