Back to "Markdown für Blogging verwenden"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Markdown

Markdown für Blogging verwenden

Friday, 02 August 2024

Einleitung

Markdown ist eine leichte Markup-Sprache, die Sie verwenden können, um Formatierungselemente zu Klartexttextdokumenten hinzuzufügen... Erstellt von John Gruber in -2004,. Markdown gehört jetzt zu den beliebtesten Markupsprachen der Welt.

Auf dieser Website benutze ich einen super einfachen Ansatz, um Bloggen . . , . nachdem versucht und versäumt, ein Blog in der Vergangenheit zu pflegen Ich wollte es so einfach wie möglich zu schreiben und veröffentlichen Beiträge . ich verwende Markdown zu meinen Beiträgen schreiben und diese Seite hat einen einzigen Service mit Markdig zum Konvertieren des Markdowns in HTML...

Warum nicht ein statischer Standortgenerator?

In einem Wort Einfachheit Dieses ist ,''t wird eine super hohe Verkehrs-Website sein , ,' Ich benutze ASP , .'NET OutPutCache um die Seiten zu verbergen und I''m nicht wird es aktualisieren, dass oft ,.' Ich wollte die Website so einfach wie möglich zu halten und nicht über den Overhead eines statischen Standortgenerators sowohl in Bezug auf den Bauprozess und die Komplexität der Website sorgen , .'

Um zu klären statische Standortgeneratoren wie Hugo / Jekyll etc. ... kann eine gute Lösung für viele Standorte sein, aber für diese wollte ich es so einfach halten für mich Wie möglich, .-I'-m ein Jahr ASP..-NET Veteran so verstehen Sie es innen und außen ?.- Dieses Website-Design fügt Komplexität hinzu ?;- Ich habe Ansichten ? ,-Dienste und eine große Anzahl von manuellen HTML-Versionen, aber I'-m komfortabel mit, dass -.-

Warum nicht eine Datenbank?

  1. Simplicty of design-Datenbanken sind leistungsfähige Systeme für die Speicherung von Daten.(. und IM'.ll fügen Sie eine für Kommentare in Kürze hinzu, aber sie fügen auch Komplexität hinzu. korrekt Verwenden Sie Datenbanken vor allem in einer ASP-.-NET-Anwendung, die Sie eine LOT von Code hinzufügen, egal ob Sie verwenden EF-Kern, Dapper oder rohes SQL mit ADO-.-NET) Ich wollte die Website so einfach wie möglich halten zu Beginn mit.
  2. Einfache Aktualisierung und Bereitstellung Diese Website soll zeigen, wie einfache Docker-Version sein kann, um eine Website laufen zu lassen... Ich kann die Website aktualisieren, indem ich den neuen Code einchecke.MSC3.include content.). to GitHub.MS K5. the Action runs...,.builts the image then the Watchtower method in my docker compound file updates the site image automatically.
  3. Das Ausführen von Duplikaten, da ich ZERO-Daten habe, die im Docker-Bild nicht enthalten sind, bedeutet es, dass ich EASILY genaue Duplicate lokal ausführen kann.... auf meinem kleinen Ubuntu-Cluster hier zu Hause. wenn ich die ImageSharp-Änderungen vorgenommen habe Vor dem Einsatz auf der Live-Site
  4. Weil ich nicht wollte, dass !, Ich wollte sehen, wie weit ich mit einem einfachen Website-Design zu bekommen und bisher I', ziemlich glücklich damit

Wie schreiben Sie Ihre Beiträge?

Ich lege einfach eine neue Datei in den Markdown-Ordner und die Website nimmt sie auf und rendert sie, wenn ich mich daran erinnere, sie als Inhalt zu aet.

Dann, wenn ich checkin der Website zu GitHub die Aktion läuft und die Website aktualisiert wird

flowchart LR
    A[Write New Markdown File] -->|Checkin To Github| B(Github Action Triggers) -->  C(Builds Docker Image) --> D(Watchtower Pulls new Image) --> E(Site Updated)
   
  

setascontent.png

Wie fügest du Bilder hinzu?

Da ich gerade das Bild hier hinzugefügt habe, zeige ich Ihnen, wie ich es gemacht habe. Ich habe einfach das Bild in den Ordner wwwroot./.articleimages aufgenommen und in der Markdown-Datei wie folgt referenziert:

![setascontent.png](setascontent.png)

Ich füge dann eine Erweiterung zu meiner Markdig Pipeline hinzu, die diese zur korrekten URL umschreibt. Siehe hier für den Quellcode für die Erweiterung.

using Markdig;
using Markdig.Renderers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;

namespace Mostlylucid.MarkDigExtensions;

public class ImgExtension : IMarkdownExtension
{
    public void Setup(MarkdownPipelineBuilder pipeline)
    {
        pipeline.DocumentProcessed += ChangeImgPath;
    }

    public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
    {
    }

    public void ChangeImgPath(MarkdownDocument document)
    {
        foreach (var link in document.Descendants<LinkInline>())
            if (link.IsImage)
                link.Url = "/articleimages/" + link.Url;
    }
}

Der BlogService - .

Der BlogService ist ein einfacher Dienst, der die Markdown-Dateien aus dem Markdown Ordner liest und sie mit Markdig®. .

Die vollständige Quelle dafür ist unten und Hierher.

Click to see the source code for the BlogService

using System.Globalization;
using System.Text.RegularExpressions;
using Markdig;
using Microsoft.Extensions.Caching.Memory;
using Mostlylucid.MarkDigExtensions;
using Mostlylucid.Models.Blog;

namespace Mostlylucid.Services;

public class BlogService
{
private const string Path = "Markdown";
private const string CacheKey = "Categories";

    private static readonly Regex DateRegex = new(
        @"<datetime class=""hidden"">(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})</datetime>",
        RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking);

    private static readonly Regex WordCoountRegex = new(@"\b\w+\b",
        RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking);

    private static readonly Regex CategoryRegex = new(@"<!--\s*category\s*--\s*([^,]+?)\s*(?:,\s*([^,]+?)\s*)?-->",
        RegexOptions.Compiled | RegexOptions.Singleline);

    private readonly ILogger<BlogService> _logger;

    private readonly IMemoryCache _memoryCache;

    private readonly MarkdownPipeline pipeline;

    public BlogService(IMemoryCache memoryCache, ILogger<BlogService> logger)
    {
        _logger = logger;
        _memoryCache = memoryCache;
        pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Use<ImgExtension>().Build();
        ListCategories();
    }


    private Dictionary<string, List<string>> GetFromCache()
    {
        return _memoryCache.Get<Dictionary<string, List<string>>>(CacheKey) ?? new Dictionary<string, List<string>>();
    }

    private void SetCache(Dictionary<string, List<string>> categories)
    {
        _memoryCache.Set(CacheKey, categories, new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12)
        });
    }

    private void ListCategories()
    {
        var cacheCats = GetFromCache();
        var pages = Directory.GetFiles("Markdown", "*.md");
        var count = 0;

        foreach (var page in pages)
        {
            var pageAlreadyAdded = cacheCats.Values.Any(x => x.Contains(page));

            if (pageAlreadyAdded) continue;


            var text = File.ReadAllText(page);
            var categories = GetCategories(text);
            if (!categories.Any()) continue;
            count++;
            foreach (var category in categories)
                if (cacheCats.TryGetValue(category, out var pagesList))
                {
                    pagesList.Add(page);
                    cacheCats[category] = pagesList;
                    _logger.LogInformation("Added category {Category} for {Page}", category, page);
                }
                else
                {
                    cacheCats.Add(category, new List<string> { page });
                    _logger.LogInformation("Created category {Category} for {Page}", category, page);
                }
        }

        if (count > 0) SetCache(cacheCats);
    }

    public List<string> GetCategories()
    {
        var cacheCats = GetFromCache();
        return cacheCats.Keys.ToList();
    }


    public List<PostListModel> GetPostsByCategory(string category)
    {
        var pages = GetFromCache()[category];
        return GetPosts(pages.ToArray());
    }

    public BlogPostViewModel? GetPost(string postName)
    {
        try
        {
            var path = System.IO.Path.Combine(Path, postName + ".md");
            var page = GetPage(path, true);
            return new BlogPostViewModel
            {
                Categories = page.categories, WordCount = WordCount(page.restOfTheLines), Content = page.processed,
                PublishedDate = page.publishDate, Slug = page.slug, Title = page.title
            };
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Error getting post {PostName}", postName);
            return null;
        }
    }

    private int WordCount(string text)
    {
        return WordCoountRegex.Matches(text).Count;
    }


    private string GetSlug(string fileName)
    {
        var slug = System.IO.Path.GetFileNameWithoutExtension(fileName);
        return slug.ToLowerInvariant();
    }

    private static string[] GetCategories(string markdownText)
    {
        var matches = CategoryRegex.Matches(markdownText);
        var categories = matches
            .SelectMany(match => match.Groups.Cast<Group>()
                .Skip(1) // Skip the entire match group
                .Where(group => group.Success) // Ensure the group matched
                .Select(group => group.Value.Trim()))
            .ToArray();
        return categories;
    }

    public (string title, string slug, DateTime publishDate, string processed, string[] categories, string
        restOfTheLines) GetPage(string page, bool html)
    {
        var fileInfo = new FileInfo(page);

        // Ensure the file exists
        if (!fileInfo.Exists) throw new FileNotFoundException("The specified file does not exist.", page);

        // Read all lines from the file
        var lines = File.ReadAllLines(page);

        // Get the title from the first line
        var title = lines.Length > 0 ? Markdown.ToPlainText(lines[0].Trim()) : string.Empty;

        // Concatenate the rest of the lines with newline characters
        var restOfTheLines = string.Join(Environment.NewLine, lines.Skip(1));

        // Extract categories from the text
        var categories = GetCategories(restOfTheLines);

        var publishedDate = fileInfo.CreationTime;
        var publishDate = DateRegex.Match(restOfTheLines).Groups[1].Value;
        if (!string.IsNullOrWhiteSpace(publishDate))
            publishedDate = DateTime.ParseExact(publishDate, "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture);

        // Remove category tags from the text
        restOfTheLines = CategoryRegex.Replace(restOfTheLines, "");
        restOfTheLines = DateRegex.Replace(restOfTheLines, "");
        // Process the rest of the lines as either HTML or plain text
        var processed =
            html ? Markdown.ToHtml(restOfTheLines, pipeline) : Markdown.ToPlainText(restOfTheLines, pipeline);

        // Generate the slug from the page filename
        var slug = GetSlug(page);


        // Return the parsed and processed content
        return (title, slug, publishedDate, processed, categories, restOfTheLines);
    }

    public List<PostListModel> GetPosts(string[] pages)
    {
        List<PostListModel> pageModels = new();

        foreach (var page in pages)
        {
            var pageInfo = GetPage(page, false);

            var summary = Markdown.ToPlainText(pageInfo.restOfTheLines).Substring(0, 100) + "...";
            pageModels.Add(new PostListModel
            {
                Categories = pageInfo.categories, Title = pageInfo.title,
                Slug = pageInfo.slug, WordCount = WordCount(pageInfo.restOfTheLines),
                PublishedDate = pageInfo.publishDate, Summary = summary
            });
        }

        pageModels = pageModels.OrderByDescending(x => x.PublishedDate).ToList();
        return pageModels;
    }


    public List<PostListModel> GetPostsForFiles()
    {
        var pages = Directory.GetFiles("Markdown", "*.md");
        return GetPosts(pages);
    }
}
Wie du sehen kannst, hat dies ein paar Elemente.

Verarbeitung von Dateien

Der Code, um die Markdown-Dateien in HTML zu verarbeiten ist ziemlich einfach. Ich benutze die Markdig-Bibliothek, um den Markdown in HTML umzuwandeln und dann verwende ich ein paar reguläre Ausdrücke zum Extrahieren der Kategorien und des veröffentlichten Datums aus der Markdowndatei.

Die GetPage Methode wird verwendet, um den Inhalt der Markdown-Datei zu extrahieren.

  1. Den Titel extrahieren Durch Konvention verwende ich die erste Zeile der Markdown-Datei als den Titel des Posts, also kann ich einfach tun.
        var lines = File.ReadAllLines(page);

        // Get the title from the first line
        var title = lines.Length > 0 ? Markdown.ToPlainText(lines[0].Trim()) : string.Empty;

Da der Titel mit dem Präfix "#" versehen ist, verwende ich die Markdown-Methode .ToPlainText, um das "MSC2" vom Titel zu entfernen.

  1. Die Kategorien extrahieren Jeder Beitrag kann bis zu zwei Kategorien haben diese Methode extrahiert diese dann ich entfernen, dass Tag aus der Markdown-Datei
// Concatenate the rest of the lines with newline characters
        var restOfTheLines = string.Join(Environment.NewLine, lines.Skip(1));

        // Extract categories from the text
        var categories = GetCategories(restOfTheLines);

   // Remove category tags from the text
        restOfTheLines = CategoryRegex.Replace(restOfTheLines, "");

Die GetCategories Methode verwendet einen regulären Ausdruck, um die Kategorien aus der Markdown-Datei zu extrahieren.

    private static readonly Regex CategoryRegex = new(@"<!--\s*category\s*--\s*([^,]+?)\s*(?:,\s*([^,]+?)\s*)?-->",
        RegexOptions.Compiled | RegexOptions.Singleline);

    private static string[] GetCategories(string markdownText)
    {
        var matches = CategoryRegex.Matches(markdownText);
        var categories = matches
            .SelectMany(match => match.Groups.Cast<Group>()
                .Skip(1) // Skip the entire match group
                .Where(group => group.Success) // Ensure the group matched
                .Select(group => group.Value.Trim()))
            .ToArray();
        return categories;
        
        
    }
  1. Auszug des veröffentlichten Datums Ich extrahiere dann das Datum aus dem Beitrag -( WAR mit dem erstellten Datum, aber wie ich dies mit einem ganzen Docker-Image zu implementieren bedeutet, dass dies wirklich nützlich mehr ist. Dieses parsiert ein Tag in der Form, die in jeder Datei ist.
 
     private static readonly Regex DateRegex = new(
        @"<datetime class=""hidden"">(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})</datetime>",
        RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking);
     
           var publishedDate = fileInfo.CreationTime;
        var publishDate = DateRegex.Match(restOfTheLines).Groups[1].Value;
        if (!string.IsNullOrWhiteSpace(publishDate))
            publishedDate = DateTime.ParseExact(publishDate, "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture);

     
        restOfTheLines = DateRegex.Replace(restOfTheLines, "");
  1. Inhalt extrahieren Eigentlich den Inhalt zu bekommen ist ziemlich einfach, dies verwendet eine Pipeline für das Bild Tag Ersatz oben erwähnt . . ) . dann gibt mir optional Klartext für die Liste der Beiträge oder HTML für die tatsächliche post .
    pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Use<ImgExtension>().Build();
    
   var processed =
            html ? Markdown.ToHtml(restOfTheLines, pipeline) : Markdown.ToPlainText(restOfTheLines, pipeline);
  1. Holen Sie sich die .' .slug . Dies ist einfach der Dateiname ohne die Erweiterung.

        private string GetSlug(string fileName)
        {
            var slug = System.IO.Path.GetFileNameWithoutExtension(fileName);
            return slug.ToLowerInvariant();
        }
    
  2. Inhalt zurückgeben Jetzt haben wir Seiteninhalte, die wir für den Blog anzeigen können.

The GetPage Method
public (string title, string slug, DateTime publishDate, string processed, string[] categories, string
        restOfTheLines) GetPage(string page, bool html)
    {
        var fileInfo = new FileInfo(page);

        // Ensure the file exists
        if (!fileInfo.Exists) throw new FileNotFoundException("The specified file does not exist.", page);

        // Read all lines from the file
        var lines = File.ReadAllLines(page);

        // Get the title from the first line
        var title = lines.Length > 0 ? Markdown.ToPlainText(lines[0].Trim()) : string.Empty;

        // Concatenate the rest of the lines with newline characters
        var restOfTheLines = string.Join(Environment.NewLine, lines.Skip(1));

        // Extract categories from the text
        var categories = GetCategories(restOfTheLines);

        var publishedDate = fileInfo.CreationTime;
        var publishDate = DateRegex.Match(restOfTheLines).Groups[1].Value;
        if (!string.IsNullOrWhiteSpace(publishDate))
            publishedDate = DateTime.ParseExact(publishDate, "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture);

        // Remove category tags from the text
        restOfTheLines = CategoryRegex.Replace(restOfTheLines, "");
        restOfTheLines = DateRegex.Replace(restOfTheLines, "");
        // Process the rest of the lines as either HTML or plain text
        var processed =
            html ? Markdown.ToHtml(restOfTheLines, pipeline) : Markdown.ToPlainText(restOfTheLines, pipeline);

        // Generate the slug from the page filename
        var slug = GetSlug(page);


        // Return the parsed and processed content
        return (title, slug, publishedDate, processed, categories, restOfTheLines);
    }
Der Code unten zeigt, wie ich die Liste der Blogposts erstelle. `GetPage(page, false)` Methode, um den Titel zu extrahieren,-Kategorien, veröffentlichten Datum und den verarbeiteten Inhalt
     public List<PostListModel> GetPosts(string[] pages)
    {
        List<PostListModel> pageModels = new();

        foreach (var page in pages)
        {
            var pageInfo = GetPage(page, false);

            var summary = Markdown.ToPlainText(pageInfo.restOfTheLines).Substring(0, 100) + "...";
            pageModels.Add(new PostListModel
            {
                Categories = pageInfo.categories, Title = pageInfo.title,
                Slug = pageInfo.slug, WordCount = WordCount(pageInfo.restOfTheLines),
                PublishedDate = pageInfo.publishDate, Summary = summary
            });
        }

        pageModels = pageModels.OrderByDescending(x => x.PublishedDate).ToList();
        return pageModels;
    }


    public List<PostListModel> GetPostsForFiles()
    {
        var pages = Directory.GetFiles("Markdown", "*.md");
        return GetPosts(pages);
    }
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.