Skip to content

Commit 91e61c5

Browse files
Move inline css and js to external files (#2965)
- Move inline css and js to external files for SwaggerUI and ReDoc - Add/improve unit tests - Define charset when serving js files - Tidy up code
1 parent 64957d8 commit 91e61c5

File tree

15 files changed

+291
-201
lines changed

15 files changed

+291
-201
lines changed

src/Swashbuckle.AspNetCore.ReDoc/ReDocMiddleware.cs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,30 @@ public ReDocMiddleware(
5353
public async Task Invoke(HttpContext httpContext)
5454
{
5555
var httpMethod = httpContext.Request.Method;
56-
var path = httpContext.Request.Path.Value;
5756

58-
// If the RoutePrefix is requested (with or without trailing slash), redirect to index URL
59-
if (httpMethod == "GET" && Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase))
57+
if (HttpMethods.IsGet(httpMethod))
6058
{
61-
// Use relative redirect to support proxy environments
62-
var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/")
63-
? "index.html"
64-
: $"{path.Split('/').Last()}/index.html";
59+
var path = httpContext.Request.Path.Value;
6560

66-
RespondWithRedirect(httpContext.Response, relativeIndexUrl);
67-
return;
68-
}
61+
// If the RoutePrefix is requested (with or without trailing slash), redirect to index URL
62+
if (Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase))
63+
{
64+
// Use relative redirect to support proxy environments
65+
var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/")
66+
? "index.html"
67+
: $"{path.Split('/').Last()}/index.html";
6968

70-
if (httpMethod == "GET" && Regex.IsMatch(path, $"/{_options.RoutePrefix}/?index.html", RegexOptions.IgnoreCase))
71-
{
72-
await RespondWithIndexHtml(httpContext.Response);
73-
return;
69+
RespondWithRedirect(httpContext.Response, relativeIndexUrl);
70+
return;
71+
}
72+
73+
var match = Regex.Match(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?(index.(html|css|js))$", RegexOptions.IgnoreCase);
74+
75+
if (match.Success)
76+
{
77+
await RespondWithFile(httpContext.Response, match.Groups[1].Value);
78+
return;
79+
}
7480
}
7581

7682
await _staticFileMiddleware.Invoke(httpContext);
@@ -97,21 +103,38 @@ private void RespondWithRedirect(HttpResponse response, string location)
97103
response.Headers["Location"] = location;
98104
}
99105

100-
private async Task RespondWithIndexHtml(HttpResponse response)
106+
private async Task RespondWithFile(HttpResponse response, string fileName)
101107
{
102108
response.StatusCode = 200;
103-
response.ContentType = "text/html";
104109

105-
using (var stream = _options.IndexStream())
110+
Stream stream;
111+
112+
switch (fileName)
113+
{
114+
case "index.css":
115+
response.ContentType = "text/css";
116+
stream = ResourceHelper.GetEmbeddedResource(fileName);
117+
break;
118+
case "index.js":
119+
response.ContentType = "application/javascript;charset=utf-8";
120+
stream = ResourceHelper.GetEmbeddedResource(fileName);
121+
break;
122+
default:
123+
response.ContentType = "text/html;charset=utf-8";
124+
stream = _options.IndexStream();
125+
break;
126+
}
127+
128+
using (stream)
106129
{
107130
// Inject arguments before writing to response
108-
var htmlBuilder = new StringBuilder(new StreamReader(stream).ReadToEnd());
131+
var content = new StringBuilder(new StreamReader(stream).ReadToEnd());
109132
foreach (var entry in GetIndexArguments())
110133
{
111-
htmlBuilder.Replace(entry.Key, entry.Value);
134+
content.Replace(entry.Key, entry.Value);
112135
}
113136

114-
await response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8);
137+
await response.WriteAsync(content.ToString(), Encoding.UTF8);
115138
}
116139
}
117140

src/Swashbuckle.AspNetCore.ReDoc/ReDocOptions.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4-
using System.Reflection;
54
using System.Text.Json.Serialization;
65

76
namespace Swashbuckle.AspNetCore.ReDoc
@@ -16,8 +15,7 @@ public class ReDocOptions
1615
/// <summary>
1716
/// Gets or sets a Stream function for retrieving the redoc page
1817
/// </summary>
19-
public Func<Stream> IndexStream { get; set; } = () => typeof(ReDocOptions).GetTypeInfo().Assembly
20-
.GetManifestResourceStream("Swashbuckle.AspNetCore.ReDoc.index.html");
18+
public Func<Stream> IndexStream { get; set; } = () => ResourceHelper.GetEmbeddedResource("index.html");
2119

2220
/// <summary>
2321
/// Gets or sets a title for the redoc page
@@ -111,4 +109,4 @@ public class ConfigObject
111109
[JsonExtensionData]
112110
public Dictionary<string, object> AdditionalItems { get; set; } = new Dictionary<string, object>();
113111
}
114-
}
112+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.IO;
2+
using System.Reflection;
3+
4+
namespace Swashbuckle.AspNetCore.ReDoc;
5+
6+
internal static class ResourceHelper
7+
{
8+
public static Stream GetEmbeddedResource(string fileName)
9+
{
10+
return typeof(ResourceHelper).GetTypeInfo().Assembly
11+
.GetManifestResourceStream($"Swashbuckle.AspNetCore.ReDoc.{fileName}");
12+
}
13+
}

src/Swashbuckle.AspNetCore.ReDoc/Swashbuckle.AspNetCore.ReDoc.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14+
<None Remove="index.css" />
15+
<None Remove="index.js" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<EmbeddedResource Include="index.css" />
1420
<EmbeddedResource Include="index.html" />
21+
<EmbeddedResource Include="index.js" />
1522
<EmbeddedResource Include="node_modules/redoc/bundles/redoc.standalone.js" />
1623
</ItemGroup>
1724

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
body {
2+
margin: 0;
3+
padding: 0;
4+
}

src/Swashbuckle.AspNetCore.ReDoc/index.html

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,12 @@
1010
<!--
1111
Redoc doesn't change outer page styles
1212
-->
13-
<style>
14-
body {
15-
margin: 0;
16-
padding: 0;
17-
}
18-
</style>
13+
<link href="index.css" rel="stylesheet" type="text/css">
1914
%(HeadContent)
2015
</head>
2116
<body>
2217
<div id="redoc-container"></div>
2318
<script src="redoc.standalone.js"></script>
24-
<script type="text/javascript">
25-
Redoc.init('%(SpecUrl)', JSON.parse('%(ConfigObject)'), document.getElementById('redoc-container'))
26-
</script>
19+
<script src="index.js"></script>
2720
</body>
28-
</html>
21+
</html>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Redoc.init('%(SpecUrl)', JSON.parse('%(ConfigObject)'), document.getElementById('redoc-container'));
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.IO;
2+
using System.Reflection;
3+
4+
namespace Swashbuckle.AspNetCore.SwaggerUI;
5+
6+
internal static class ResourceHelper
7+
{
8+
public static Stream GetEmbeddedResource(string fileName)
9+
{
10+
return typeof(ResourceHelper).GetTypeInfo().Assembly
11+
.GetManifestResourceStream($"Swashbuckle.AspNetCore.SwaggerUI.{fileName}");
12+
}
13+
}

src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,30 @@ public SwaggerUIMiddleware(
6464
public async Task Invoke(HttpContext httpContext)
6565
{
6666
var httpMethod = httpContext.Request.Method;
67-
var path = httpContext.Request.Path.Value;
6867

69-
var isGet = HttpMethods.IsGet(httpMethod);
70-
71-
// If the RoutePrefix is requested (with or without trailing slash), redirect to index URL
72-
if (isGet && Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase))
68+
if (HttpMethods.IsGet(httpMethod))
7369
{
74-
// Use relative redirect to support proxy environments
75-
var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/")
76-
? "index.html"
77-
: $"{path.Split('/').Last()}/index.html";
70+
var path = httpContext.Request.Path.Value;
7871

79-
RespondWithRedirect(httpContext.Response, relativeIndexUrl);
80-
return;
81-
}
72+
// If the RoutePrefix is requested (with or without trailing slash), redirect to index URL
73+
if (Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase))
74+
{
75+
// Use relative redirect to support proxy environments
76+
var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/")
77+
? "index.html"
78+
: $"{path.Split('/').Last()}/index.html";
8279

83-
if (isGet && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase))
84-
{
85-
await RespondWithIndexHtml(httpContext.Response);
86-
return;
80+
RespondWithRedirect(httpContext.Response, relativeIndexUrl);
81+
return;
82+
}
83+
84+
var match = Regex.Match(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?(index.(html|js))$", RegexOptions.IgnoreCase);
85+
86+
if (match.Success)
87+
{
88+
await RespondWithFile(httpContext.Response, match.Groups[1].Value);
89+
return;
90+
}
8791
}
8892

8993
await _staticFileMiddleware.Invoke(httpContext);
@@ -110,23 +114,35 @@ private static void RespondWithRedirect(HttpResponse response, string location)
110114
response.Headers["Location"] = location;
111115
}
112116

113-
private async Task RespondWithIndexHtml(HttpResponse response)
117+
private async Task RespondWithFile(HttpResponse response, string fileName)
114118
{
115119
response.StatusCode = 200;
116-
response.ContentType = "text/html;charset=utf-8";
117120

118-
using (var stream = _options.IndexStream())
121+
Stream stream;
122+
123+
if (fileName == "index.js")
124+
{
125+
response.ContentType = "application/javascript;charset=utf-8";
126+
stream = ResourceHelper.GetEmbeddedResource(fileName);
127+
}
128+
else
129+
{
130+
response.ContentType = "text/html;charset=utf-8";
131+
stream = _options.IndexStream();
132+
}
133+
134+
using (stream)
119135
{
120136
using var reader = new StreamReader(stream);
121137

122138
// Inject arguments before writing to response
123-
var htmlBuilder = new StringBuilder(await reader.ReadToEndAsync());
139+
var content = new StringBuilder(await reader.ReadToEndAsync());
124140
foreach (var entry in GetIndexArguments())
125141
{
126-
htmlBuilder.Replace(entry.Key, entry.Value);
142+
content.Replace(entry.Key, entry.Value);
127143
}
128144

129-
await response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8);
145+
await response.WriteAsync(content.ToString(), Encoding.UTF8);
130146
}
131147
}
132148

src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5-
using System.Reflection;
65
using System.Text.Json;
76
using System.Text.Json.Serialization;
87

@@ -18,8 +17,7 @@ public class SwaggerUIOptions
1817
/// <summary>
1918
/// Gets or sets a Stream function for retrieving the swagger-ui page
2019
/// </summary>
21-
public Func<Stream> IndexStream { get; set; } = () => typeof(SwaggerUIOptions).GetTypeInfo().Assembly
22-
.GetManifestResourceStream("Swashbuckle.AspNetCore.SwaggerUI.index.html");
20+
public Func<Stream> IndexStream { get; set; } = () => ResourceHelper.GetEmbeddedResource("index.html");
2321

2422
/// <summary>
2523
/// Gets or sets a title for the swagger-ui page
@@ -34,7 +32,7 @@ public class SwaggerUIOptions
3432
/// <summary>
3533
/// Gets the JavaScript config object, represented as JSON, that will be passed to the SwaggerUI
3634
/// </summary>
37-
public ConfigObject ConfigObject { get; set; } = new ConfigObject();
35+
public ConfigObject ConfigObject { get; set; } = new ConfigObject();
3836

3937
/// <summary>
4038
/// Gets the JavaScript config object, represented as JSON, that will be passed to the initOAuth method

0 commit comments

Comments
 (0)