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 8 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 Blazor Server project:

    dotnet new blazorserver-empty -n MyFirstContentSite
    cd MyFirstContentSite
    
  2. 2

    Add MyLittleContentEngine

    Add the NuGet package references to your project:

    dotnet add package MyLittleContentEngine
    dotnet add package MyLittleContentEngine.MonorailCss
    

    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

    Add Static Content Service

    Create a model to define the structure of your blog post metadata. Add a new file BlogPost.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 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. We'll break down what each components does bit by bit in the guides, but for now 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",
        BaseUrl =  Environment.GetEnvironmentVariable("BaseHref") ?? "/",
        ContentRootPath = "Content",
    });
    
    // configures individual sections of the blog. PageUrl should match the configured razor pages route,
    // and contentPath should match the location on disk.
    // you can have multiple of these per site.
    builder.Services.AddContentEngineStaticContentService(_ => new ContentEngineContentOptions<BlogFrontMatter>()
    {
        ContentPath = "Content",
        BasePageUrl = string.Empty
    });
    
    builder.Services.AddMonorailCss();
    
    var app = builder.Build();
    app.UseAntiforgery();
    app.UseStaticFiles();
    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 AddContentEngineStaticContentService:

    mkdir -p Content
    
  6. 6

    Write Your First Blog Post

    Create your first blog post at Content/index.md:

    ---
    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. It uses a flexbox layout which we can later use to extend the design with a sidebar or other components. For now, it will just center the content.

    @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/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
    @using MyLittleContentEngine
    @using MyLittleContentEngine.Models
    @using MyLittleContentEngine.Services.Content
    @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.NavigateUrl">@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 Page

    Create Components/Page.razor to display your blog posts. Here we are calling GetRenderedContentPageByUrlOrDefault to retrieve the blog post by its URL.

    @page "/{*fileName:nonfile}"
    
    @using MyLittleContentEngine.Models
    @using MyLittleContentEngine.Services.Content
    @using MyLittleContentEngine
    @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. 6

    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.