|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
4 | 4 | using System.Diagnostics;
|
| 5 | +using System.Diagnostics.CodeAnalysis; |
5 | 6 | using System.Runtime.InteropServices;
|
6 | 7 | using Docfx.Common;
|
7 | 8 | using Docfx.Plugins;
|
8 | 9 | using Microsoft.AspNetCore.Builder;
|
9 | 10 | using Microsoft.AspNetCore.Hosting;
|
| 11 | +using Microsoft.AspNetCore.Http; |
10 | 12 | using Microsoft.Extensions.FileProviders;
|
11 | 13 | using Microsoft.Extensions.Hosting;
|
12 | 14 | using Microsoft.Extensions.Logging;
|
13 | 15 |
|
| 16 | +#nullable enable |
| 17 | + |
14 | 18 | namespace Docfx;
|
15 | 19 |
|
16 | 20 | /// <summary>
|
@@ -44,6 +48,7 @@ public static void Exec(string folder, string host, int? port, bool openBrowser,
|
44 | 48 | Console.WriteLine($"Serving \"{folder}\" on {url}");
|
45 | 49 | Console.WriteLine("Press Ctrl+C to shut down");
|
46 | 50 | using var app = builder.Build();
|
| 51 | + app.UseExtensionlessHtmlUrl(); |
47 | 52 | app.UseServe(folder);
|
48 | 53 |
|
49 | 54 | if (openBrowser || !string.IsNullOrEmpty(openFile))
|
@@ -161,4 +166,41 @@ private static void LaunchBrowser(string url)
|
161 | 166 | Logger.LogError($"Could not launch the browser process. with error - {ex.Message}");
|
162 | 167 | }
|
163 | 168 | }
|
| 169 | + |
| 170 | + /// <summary> |
| 171 | + /// Enable HTML content access with extensionless URL. |
| 172 | + /// This extension method must be called before `UseFileServer` or `UseStaticFiles`. |
| 173 | + /// </summary> |
| 174 | + private static IApplicationBuilder UseExtensionlessHtmlUrl(this WebApplication app) |
| 175 | + { |
| 176 | + // Configure middleware that rewrite extensionless url to physical HTML file path. |
| 177 | + return app.Use(async (context, next) => |
| 178 | + { |
| 179 | + if (IsGetOrHeadMethod(context.Request.Method) |
| 180 | + && TryResolveHtmlFilePath(context.Request.Path, out var htmlFilePath)) |
| 181 | + { |
| 182 | + context.Request.Path = htmlFilePath; |
| 183 | + } |
| 184 | + |
| 185 | + await next(); |
| 186 | + }); |
| 187 | + |
| 188 | + static bool IsGetOrHeadMethod(string method) => HttpMethods.IsGet(method) || HttpMethods.IsHead(method); |
| 189 | + |
| 190 | + // Try to resolve HTML file path. |
| 191 | + bool TryResolveHtmlFilePath(PathString pathString, [NotNullWhen(true)] out string? htmlPath) |
| 192 | + { |
| 193 | + var path = pathString.Value; |
| 194 | + if (!string.IsNullOrEmpty(path) && !Path.HasExtension(path) && !path.EndsWith('/')) |
| 195 | + { |
| 196 | + htmlPath = $"{path}.html"; |
| 197 | + var fileInfo = app.Environment.WebRootFileProvider.GetFileInfo(htmlPath); |
| 198 | + if (fileInfo != null) |
| 199 | + return true; |
| 200 | + } |
| 201 | + |
| 202 | + htmlPath = null; |
| 203 | + return false; |
| 204 | + } |
| 205 | + } |
164 | 206 | }
|
0 commit comments