Creating Your First Site

In this tutorial, you'll create your first content site using MyLittleContentEngine. By the end, you'll have a working Blazor application that can serve markdown content and generate static HTML files.

What You'll Build

You'll create a simple blog-style website with:

  • A home page displaying recent posts
  • Individual blog post pages

Prerequisites

Before starting, ensure you have:

  • .NET 9 SDK or later installed
  • A code editor (Visual Studio, VS Code, or JetBrains Rider)
  • Familiarity with command-line tools
  1. 1

    Create a New Blazor Project

    Start by creating a new empty ASP.NET project:

    dotnet new web -n MyFirstContentSite
    cd MyFirstContentSite
    
  2. 2

    Add MyLittleContentEngine

    Add the NuGet package references to your project, ensuring you use the --prerelease option:

    dotnet add package MyLittleContentEngine --prerelease 
    dotnet add package MyLittleContentEngine.MonorailCss --prerelease
    

    MyLittleContentEngine contains the core functionality for content management, while MyLittleContentEngine.MonorailCss provides a simple CSS framework for styling.

    The MyLittleContentEngine.MonorailCss package is optional, but MyLittleContentEngine makes a lot of assumptions regarding styling that you'd otherwise have to unravel without it. We'll use it in this example to keep things simple.

  3. 3

    Build Your Metadata Model

    Create a model to define the structure of your blog post metadata. Add a new file BlogFrontMatter.cs:

    public class BlogFrontMatter : IFrontMatter
    {
        public string Title { get; init; } = "Empty title";
        public string Description { get; init; } = string.Empty;
        public string? Uid { get; init; } = null;
    
        public DateTime Date { get; init; } = DateTime.Now;
        public bool IsDraft { get; init; } = false;
        public string[] Tags { get; init; } = [];
        public string? RedirectUrl { get; init; }
        public string? Section { get; init; }
        
        public Metadata AsMetadata()
        {
            return new Metadata()
            {
                Title = Title,
                Description = Description,
                LastMod = Date,
                RssItem = true
            };
        }
    }
  4. 4

    Configure the Content Engine

    Open Program.cs and configure MyLittleContentEngine. Replace the existing content with:

    using MinimalExample;
    using MinimalExample.Components;
    using MyLittleContentEngine;
    using MyLittleContentEngine.MonorailCss;
      
    var builder = WebApplication.CreateBuilder(args);
      
    builder.Services.AddRazorComponents();
      
    // configures site wide settings
    // hot reload note - these will not be reflected until the application restarts
    builder.Services.AddContentEngineService(_ => new ContentEngineOptions
    {
        SiteTitle = "My Little Content Engine",
        SiteDescription = "An Inflexible Content Engine for .NET",
        ContentRootPath = "Content",
    }).WithMarkdownContentService(_ => new MarkdownContentOptions<BlogFrontMatter>()
    {
        ContentPath = "Content",
        BasePageUrl = string.Empty
    });
      
    builder.Services.AddMonorailCss();
      
    var app = builder.Build();
    app.UseAntiforgery();
    app.MapStaticAssets();
    app.MapRazorComponents<App>();
    app.UseMonorailCss();
      
    await app.RunOrBuildContent(args);
  5. 5

    Create the Content Structure

    Create the content directory structure to match what we defined in WithMarkdownContentService:

    mkdir -p Content
    
  6. 6

    Write Your First Blog Post

    Create your first blog post at Content/index.md. Make sure to include the front matter at the top of the file that matches the BlogFrontMatter model you created earlier. Here's an example:

    ---
    title: "Welcome to My Content Site"
    description: "Getting started with MyLittleContentEngine"
    date: 2025-01-15
    tags:
      - introduction
      - welcome
    isDraft: false
    ---
    
    Welcome to my new content site! This is my first post using MyLittleContentEngine.
    
    ## What is MyLittleContentEngine?
    
    MyLittleContentEngine is a static site generator built specifically for .NET Blazor applications. It allows you to:
    
    - Write content in Markdown
    - Use `dotnet watch` to develop your site
    - Generate static HTML for fast loading at deployment
    
    ## Getting Started
    
    Creating content is as simple as writing Markdown files with YAML front matter. The engine handles the rest! Except the
    writing!
    
    ```csharp
    var theAnswer = 30 + 25;
    ```
    
    And that's it! You now have a basic content site up and running with MyLittleContentEngine.
  7. 7

    Create Your Layout

    Create Components/Layout/MainLayout.razor to include basic styling. This uses Tailwind CSS like syntax for styling. Here we are defining a simple layout for our blog posts, centered in the middle of the page:

    @inherits LayoutComponentBase
      
    <div>
        <div class="max-w-4xl mx-auto p-4">
            <div class="flex flex-col">
               <main class="flex-1 w-full">
                    @Body
                </main>
            </div>
        </div>
    </div>
      
    @code{
      
        protected override async Task OnInitializedAsync()
        {
            await base.OnInitializedAsync();
        }
    }
    
  8. 8

    Create the Home Page

    Create Components/Layout/Home.razor to display your blog posts. Here we are calling GetAllContentPagesAsync to retrieve all the blog posts and display them in a list:

    @page "/"
    @using System.Collections.Immutable
    @inject ContentEngineOptions ContentEngineOptions
    @inject IMarkdownContentService<BlogFrontMatter> MarkdownContentService
      
    @if (_pages == null)
    {
        <p>Loading...</p>
        return;
    }
      
    <h1 class="text-4xl font-bold pb-4">Welcome @ContentEngineOptions.SiteTitle!</h1>
      
    <ul class="list-disc pl-6 space-y-2">
        @foreach (var p in _pages.OrderByDescending(i => i.FrontMatter.Date))
        {
            <li><a href="@p.Url">@p.FrontMatter.Title</a></li>
        }
    </ul>
      
    @code {
        private ImmutableList<MarkdownContentPage<BlogFrontMatter>>? _pages;
      
        protected override async Task OnInitializedAsync()
        {
            _pages = await MarkdownContentService.GetAllContentPagesAsync();
            await base.OnInitializedAsync();
        }
      
    }
  9. 9

    Create a Page Displaying Component

    Create Components/Layout/Pages.razor to display individual blog posts. Here we are calling GetRenderedContentPageByUrlOrDefault to retrieve the blog post by its URL:

    @page "/{*fileName:nonfile}"
      
    @inject ContentEngineOptions ContentEngineOptions
    @inject IMarkdownContentService<BlogFrontMatter> MarkdownContentService
      
    @if (_postContent == null || _post == null)
    {
        <PageTitle>@ContentEngineOptions.SiteTitle</PageTitle>
        <p>Not found</p>
        return;
      
    }
    <PageTitle>@ContentEngineOptions.SiteTitle - @_post.FrontMatter.Title</PageTitle>
    <article>
        <header>
            <h1 class="text-4xl font-bold"> @_post.FrontMatter.Title</h1>
        </header>
      
        <div class="prose max-w-full">
            @((MarkupString)_postContent)
        </div>
    </article>
      
    @code {
        private MarkdownContentPage<BlogFrontMatter>? _post;
        private string? _postContent;
      
        [Parameter] public required string FileName { get; init; } = string.Empty;
      
        protected override async Task OnInitializedAsync()
        {
            var fileName = FileName;
            if (string.IsNullOrWhiteSpace(fileName))
            {
                fileName = "index";
            }
      
            var page = await MarkdownContentService.GetRenderedContentPageByUrlOrDefault(fileName);
            if (page == null)
            {
                return;
            }
      
            _post = page.Value.Page;
            _postContent = page.Value.HtmlContent;
        }
    }
  10. 10

    Configure dotnet watch Support

    The last step we need to do is to ensure that the content files are watched for changes during development. Add the following to your .csproj file:

    <ItemGroup>
        <Watch Include="Content\**\*.*" />
    </ItemGroup>
  11. 11

    Test Your Site

    Run your site in development mode:

    dotnet watch
    

    Navigate to https://localhost:5001 (or the URL shown in your terminal) to see your site in action!

While the page is open, try editing the Content/index.md file. You should see the changes reflected immediately without needing to restart the server. Not just editing, but adding, renaming and deleting files should also work seamlessly.