文章目录
前言
Markdown 内容的动态渲染,适用于文档系统、博客引擎等场景。
一、核心功能
-
自动识别请求路径 :将 .md 或 .markdown 结尾的请求视为 Markdown 文件请求。
-
实时转换 :将 Markdown 内容转换为 HTML。
-
支持模板嵌入 :将渲染后的 HTML 嵌入统一布局模板。
-
异常处理:处理文件不存在或转换错误。
二、实现步骤
1)安装依赖包
-
使用 NuGet 安装 Markdown 解析库(推荐 Markdig ):
bashInstall-Package Markdig
2)创建中间件类
-
MarkdownRenderingMiddleware.cs
bashusing 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)中间件扩展方法
-
MarkdownRenderingMiddlewareExtensions.cs
bashusing 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
bashusing 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
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文件示例
7)缓存优化
- 在中间件添加内存缓存
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)使用示例
三、注意事项
-
安全性:限制文件目录,避免路径遍历攻击。
-
性能:对高频访问的 Markdown 文件启用缓存。
-
SEO 优化:在模板中添加 标签增强搜索引擎友好性。
总结
通过此中间件,可快速实现 Markdown 内容的动态渲染,适用于文档系统、博客引擎等场景。