ASP.NET Core 中实现 Markdown 渲染中间件

文章目录


前言

Markdown 内容的动态渲染,适用于文档系统、博客引擎等场景。

一、核心功能

  • 自动识别请求路径 :将 .md.markdown 结尾的请求视为 Markdown 文件请求。

  • 实时转换 :将 Markdown 内容转换为 HTML

  • 支持模板嵌入 :将渲染后的 HTML 嵌入统一布局模板。

  • 异常处理:处理文件不存在或转换错误。

二、实现步骤

1)安装依赖包

  1. 使用 NuGet 安装 Markdown 解析库(推荐 Markdig ):

    bash 复制代码
    Install-Package Markdig

2)创建中间件类

  1. MarkdownRenderingMiddleware.cs

    bash 复制代码
    using Microsoft.Extensions.FileProviders;
    
    namespace MarkDownMiddleware.Middleware
    {
        public class MarkdownRenderingMiddleware
        {
            private readonly RequestDelegate next;
            private readonly IFileProvider _fileProvider;
            private readonly string _template;
    
            public MarkdownRenderingMiddleware(RequestDelegate next, 
                IFileProvider fileProvider, 
                string template=null)
            {
                this.next = next;
                _fileProvider = fileProvider;
                _template = template ?? "<html><body>{0}</body></html>";
            }
    
    
            public async Task InvokeAsync(HttpContext context)
            {
                var path=context.Request.Path.Value;
                if (!path.EndsWith(".md")&&!path.EndsWith(".markdown"))
                {
                    await next(context);
                    return;
                }
                var fileInfo=_fileProvider.GetFileInfo(path);
                if (!fileInfo.Exists)
                {
                    context.Response.StatusCode = 404;
                    await context.Response.WriteAsync($"Markdown file ({path}) not found");
                    return;
                }
                // 读取 Markdown 内容
                using var stream = fileInfo.CreateReadStream();
                using var reader = new StreamReader(stream);
                var markdown = await reader.ReadToEndAsync();
    
                // 转换为 HTML
                var html = Markdig.Markdown.ToHtml(markdown);
    
                // 嵌入模板
                var fullHtml = string.Format(_template, html);
    
                // 返回响应
                context.Response.ContentType = "text/html";
                await context.Response.WriteAsync(fullHtml);
            }
        }
    }

3)中间件扩展方法

  1. MarkdownRenderingMiddlewareExtensions.cs

    bash 复制代码
    using MarkDownMiddleware.Middleware;
    using Microsoft.Extensions.FileProviders;
    
    namespace MarkDownMiddleware.Extensions
    {
        public static class MarkdownRenderingMiddlewareExtensions
        {
            public static IApplicationBuilder UseMarkdownRendering(
                this IApplicationBuilder app,
                string templatePath = null,
                string fileProviderRoot="wwwroot")
            {
                var fileProvider=new PhysicalFileProvider(
                    Path.Combine(Directory.GetCurrentDirectory(),fileProviderRoot));
                string template = null;
                if (!string.IsNullOrEmpty(templatePath))
                {
                    var templateFile=fileProvider.GetFileInfo(templatePath);
                    if (templateFile.Exists)
                    {
                        using var stream=templateFile.CreateReadStream();
                        using var reader=new StreamReader(stream);
                        template = reader.ReadToEnd();
                    }
                }
                return app.UseMiddleware<MarkdownRenderingMiddleware>(fileProvider,template);
            }
        }
    }

4)在Program.cs配置

  1. Program.cs

    bash 复制代码
    using MarkDownMiddleware.Extensions;
    using MarkDownMiddleware.Middleware;
    using Microsoft.AspNetCore.Mvc.ApplicationParts;
    using Microsoft.Extensions.FileProviders;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews();
    // 注册 IFileProvider 服务(指向 wwwroot 目录)
    builder.Services.AddSingleton<IFileProvider>(
        new PhysicalFileProvider(builder.Environment.WebRootPath)
    );
    var app = builder.Build();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    //app.UseMiddleware<MarkdownRenderingMiddleware>();
    app.UseMarkdownRendering(
        templatePath: "/template/layout.html",
        fileProviderRoot:"Content");
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    
    app.Run();

5)模板文件示例

  1. layout.html

    html 复制代码
    <!-- Content/template/layout.html -->
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>Markdown Render</title>
        <link rel="stylesheet" href="/styles/markdown.css">
    </head>
    <body>
        <div class="markdown-body">
            {0} <!-- Markdown 内容插入位置 -->
        </div>
    </body>
    </html>

6)*.md文件示例

  1. test.md

7)缓存优化

  1. 在中间件添加内存缓存
csharp 复制代码
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Caching.Memory;

namespace MarkDownMiddleware.Middleware
{
    public class MarkdownRenderingMiddleware
    {
        private readonly RequestDelegate next;
        private readonly IFileProvider _fileProvider;
        private readonly string _template;
        private readonly IMemoryCache _memoryCache;

        public MarkdownRenderingMiddleware(RequestDelegate next,
            IFileProvider fileProvider,
            string template = null,
            IMemoryCache memoryCache = null)
        {
            this.next = next;
            _fileProvider = fileProvider;
            _template = template ?? "<html><body>{0}</body></html>";
            _memoryCache = memoryCache;
        }


        public async Task InvokeAsync(HttpContext context)
        {
            
            var path=context.Request.Path.Value;
            if (!path.EndsWith(".md")&&!path.EndsWith(".markdown"))
            {
                await next(context);
                return;
            }
            var cacheKey = $"markdown_{path}";
            if (_memoryCache.TryGetValue(cacheKey, out string cachedHtml))
            {
                await context.Response.WriteAsync(cachedHtml);
                return;
            }
            var fileInfo=_fileProvider.GetFileInfo(path);
            if (!fileInfo.Exists)
            {
                context.Response.StatusCode = 404;
                await context.Response.WriteAsync($"Markdown file ({path}) not found");
                return;
            }
            // 读取 Markdown 内容
            using var stream = fileInfo.CreateReadStream();
            using var reader = new StreamReader(stream);
            var markdown = await reader.ReadToEndAsync();

            // 转换为 HTML
            var html = Markdig.Markdown.ToHtml(markdown);

            // 嵌入模板
            var fullHtml = string.Format(_template, html);

            // 返回响应
            context.Response.ContentType = "text/html";
            await context.Response.WriteAsync(fullHtml);
            _memoryCache.Set(cacheKey, fullHtml, TimeSpan.FromMinutes(10));
        }
    }
}

8)使用示例

  1. 访问https://localhost:7066/test.md

三、注意事项

  • 安全性:限制文件目录,避免路径遍历攻击。

  • 性能:对高频访问的 Markdown 文件启用缓存。

  • SEO 优化:在模板中添加 标签增强搜索引擎友好性。


总结

通过此中间件,可快速实现 Markdown 内容的动态渲染,适用于文档系统、博客引擎等场景。

相关推荐
星辰徐哥2 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥2 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约2 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee2 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐2 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs2 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐2 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司2 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
码农阿豪2 小时前
从零到一:Spring Boot快速接入金仓数据库实战
数据库·spring boot·后端
追逐时光者2 小时前
一个基于 .NET 与 Avalonia 构建、面向 TrinityCore 的开源 WoW 数据库编辑器
后端·.net