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 under /_llms/. 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.