Skip to content

Commit 0ddb208

Browse files
committed
Refactor Markdown content negotiation into dedicated middleware
1 parent 583f27f commit 0ddb208

2 files changed

Lines changed: 48 additions & 29 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace Docs.Web;
2+
3+
/// <summary>
4+
/// Middleware that serves .md files when the client sends Accept: text/markdown.
5+
/// </summary>
6+
public class ContentNegotiationMiddleware : IMiddleware
7+
{
8+
private readonly IWebHostEnvironment _environment;
9+
10+
public ContentNegotiationMiddleware(IWebHostEnvironment environment)
11+
{
12+
_environment = environment;
13+
}
14+
15+
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
16+
{
17+
var accept = context.Request.Headers.Accept.ToString();
18+
if (accept.Contains("text/markdown", StringComparison.OrdinalIgnoreCase))
19+
{
20+
var requestPath = context.Request.Path.Value?.TrimEnd('/') ?? "";
21+
22+
// Try the exact path with .md extension, then index.md inside the directory
23+
var candidates = new[]
24+
{
25+
Path.Combine(_environment.WebRootPath, requestPath.TrimStart('/') + ".md"),
26+
Path.Combine(_environment.WebRootPath, requestPath.TrimStart('/'), "index.md")
27+
};
28+
29+
foreach (var mdPath in candidates)
30+
{
31+
if (File.Exists(mdPath))
32+
{
33+
context.Response.ContentType = "text/markdown; charset=utf-8";
34+
context.Response.Headers.TryAdd("content-signal", "ai-train=yes, search=yes, ai-input=yes");
35+
await context.Response.SendFileAsync(mdPath);
36+
return;
37+
}
38+
}
39+
}
40+
41+
await next(context);
42+
}
43+
}

server/src/Docs.Web/Program.cs

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.Json;
2+
using Docs.Web;
23

34
var builder = WebApplication.CreateBuilder(args);
45

@@ -7,6 +8,9 @@
78
// Add response compression
89
builder.Services.AddResponseCompression();
910

11+
// Content negotiation middleware
12+
builder.Services.AddTransient<ContentNegotiationMiddleware>();
13+
1014
var app = builder.Build();
1115

1216
app.MapDefaultEndpoints();
@@ -52,35 +56,7 @@
5256
app.UseResponseCompression();
5357

5458
// Content negotiation: serve .md file when Accept: text/markdown
55-
app.Use(async (context, next) =>
56-
{
57-
var accept = context.Request.Headers.Accept.ToString();
58-
if (accept.Contains("text/markdown", StringComparison.OrdinalIgnoreCase))
59-
{
60-
var webHostEnvironment = context.RequestServices.GetRequiredService<IWebHostEnvironment>();
61-
var requestPath = context.Request.Path.Value?.TrimEnd('/') ?? "";
62-
63-
// Try the exact path with .md extension, then index.md inside the directory
64-
var candidates = new[]
65-
{
66-
Path.Combine(webHostEnvironment.WebRootPath, requestPath.TrimStart('/') + ".md"),
67-
Path.Combine(webHostEnvironment.WebRootPath, requestPath.TrimStart('/'), "index.md")
68-
};
69-
70-
foreach (var mdPath in candidates)
71-
{
72-
if (File.Exists(mdPath))
73-
{
74-
context.Response.ContentType = "text/markdown; charset=utf-8";
75-
context.Response.Headers["content-signal"] = "ai-train=yes, search=yes, ai-input=yes";
76-
await context.Response.SendFileAsync(mdPath);
77-
return;
78-
}
79-
}
80-
}
81-
82-
await next();
83-
});
59+
app.UseMiddleware<ContentNegotiationMiddleware>();
8460

8561
// Add trailing slash redirect middleware (replicate nginx behavior)
8662
app.Use(async (context, next) =>

0 commit comments

Comments
 (0)