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 内容的动态渲染,适用于文档系统、博客引擎等场景。

相关推荐
uzong3 小时前
技术故障复盘模版
后端
GetcharZp4 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程4 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研4 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi5 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国6 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy6 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack6 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9657 小时前
pip install 已经不再安全
后端
寻月隐君7 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github