ASP.NET Core 中实现 Markdown 渲染中间件
文章目录
- 前言
- 一、核心功能
- 二、实现步骤
- 1)安装依赖包
- 2)创建中间件类
- 3)中间件扩展方法
- 4)在Program.cs配置
- 5)模板文件示例
- 6)*.md文件示例
- 7)缓存优化
- 8)使用示例
- 三、注意事项
- 总结
前言
Markdown 内容的动态渲染,适用于文档系统、博客引擎等场景。
一、核心功能
-
自动识别请求路径:将 .md 或 .markdown 结尾的请求视为 Markdown 文件请求。
-
实时转换:将 Markdown 内容转换为 HTML。
-
支持模板嵌入:将渲染后的 HTML 嵌入统一布局模板。
-
异常处理:处理文件不存在或转换错误。
二、实现步骤
1)安装依赖包
- 使用 NuGet 安装 Markdown 解析库(推荐 Markdig):
Install-Package Markdig
2)创建中间件类
- MarkdownRenderingMiddleware.cs
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();// 转换为 HTMLvar html = Markdig.Markdown.ToHtml(markdown);// 嵌入模板var fullHtml = string.Format(_template, html);// 返回响应context.Response.ContentType = "text/html";await context.Response.WriteAsync(fullHtml);}} }
3)中间件扩展方法
- MarkdownRenderingMiddlewareExtensions.cs
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配置
- Program.cs
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)模板文件示例
- layout.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文件示例
- test.md
7)缓存优化
- 在中间件添加内存缓存
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();// 转换为 HTMLvar 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)使用示例
- 访问https://localhost:7066/test.md
三、注意事项
-
安全性:限制文件目录,避免路径遍历攻击。
-
性能:对高频访问的 Markdown 文件启用缓存。
-
SEO 优化:在模板中添加 标签增强搜索引擎友好性。
总结
通过此中间件,可快速实现 Markdown 内容的动态渲染,适用于文档系统、博客引擎等场景。